Add map core functions, step 1 to 9 passing
[jackhill/mal.git] / elixir / lib / mal / core.ex
1 defmodule Mal.Core do
2 def namespace do
3 %{
4 "+" => fn [a, b] -> a + b end,
5 "-" => fn [a, b] -> a - b end,
6 "*" => fn [a, b] -> a * b end,
7 "/" => fn [a, b] -> div(a, b) end,
8 ">" => fn [a, b] -> a > b end,
9 "<" => fn [a, b] -> a < b end,
10 "<=" => fn [a, b] -> a <= b end,
11 ">=" => fn [a, b] -> a >= b end,
12 "concat" => &concat/1,
13 "=" => &equal/1,
14 "list?" => &list?/1,
15 "empty?" => &empty?/1,
16 "count" => &count/1,
17 "pr-str" => &pr_str/1,
18 "str" => &str/1,
19 "prn" => &prn/1,
20 "println" => &println/1,
21 "slurp" => &slurp/1,
22 "nth" => &nth/1,
23 "first" => &first/1,
24 "rest" => &rest/1,
25 "map" => &map/1,
26 "apply" => &apply/1,
27 "keyword" => &keyword/1,
28 "symbol?" => &symbol?/1,
29 "cons" => &cons/1,
30 "vector?" => &vector?/1,
31 "assoc" => &assoc/1,
32 "dissoc" => &dissoc/1,
33 "get" => &get/1,
34 "hash-map" => &Mal.Types.hash_map/1,
35 "sequential?" => fn arg -> vector?(arg) or list?(arg) end,
36 "vector" => fn list -> {:vector, list} end,
37 "keyword?" => fn [type] -> is_atom(type) end,
38 "map?" => fn [type] -> is_map(type) end,
39 "nil?" => fn [type] -> type == nil end,
40 "true?" => fn [type] -> type == true end,
41 "false?" => fn [type] -> type == false end,
42 "symbol" => fn [name] -> {:symbol, name} end,
43 "list" => fn args -> args end,
44 "read-string" => fn [input] -> Mal.Reader.read_str(input) end,
45 "throw" => fn [arg] -> throw({:error, arg}) end,
46 "contains?" => fn [map, key] -> Map.has_key?(map, key) end,
47 "keys" => fn [map] -> Map.keys(map) end,
48 "vals" => fn [map] -> Map.values(map) end
49 }
50 end
51
52 defp convert_vector({:vector, list}), do: list
53 defp convert_vector(other), do: other
54
55 def equal([a, b]) do
56 convert_vector(a) == convert_vector(b)
57 end
58
59 def list?([arg]) when is_list(arg), do: true
60 def list?([_arg]), do: false
61
62 def empty?([[]]), do: true
63 def empty?([{:vector, []}]), do: true
64 def empty?(_), do: false
65
66 def count([arg]) when is_list(arg), do: length(arg)
67 def count([{:vector, arg}]), do: length(arg)
68 def count(_), do: 0
69
70 def pr_str(args) do
71 args
72 |> Enum.map(&Mal.Printer.print_str/1)
73 |> Enum.join(" ")
74 end
75
76 def str(args) do
77 args
78 |> Enum.map(&(Mal.Printer.print_str(&1, false)))
79 |> Enum.join("")
80 end
81
82 def prn(args) do
83 args
84 |> pr_str
85 |> IO.puts
86 nil
87 end
88
89 def println(args) do
90 args
91 |> Enum.map(&(Mal.Printer.print_str(&1, false)))
92 |> Enum.join(" ")
93 |> IO.puts
94 nil
95 end
96
97 def slurp([file_name]) do
98 case File.read(file_name) do
99 {:ok, content} -> content
100 {:error, :enoent} -> throw({:error, "can't find file #{file_name}"})
101 {:error, :eisdir} -> throw({:error, "can't read directory #{file_name}"})
102 {:error, :eaccess} -> throw({:error, "missing permissions #{file_name}"})
103 {:error, reason} -> throw({:error, "can't read file #{file_name}, #{reason}"})
104 end
105 end
106
107 def nth([list, index]) do
108 case Enum.at(convert_vector(list), index, :error) do
109 :error -> throw({:error, "index out of bounds"})
110 any -> any
111 end
112 end
113
114 def first([{:vector, [head | tail]}]), do: head
115 def first([[head | tail]]), do: head
116 def first(_), do: nil
117
118 def rest([{:vector, list}]), do: do_rest(list)
119 def rest([list]), do: do_rest(list)
120
121 defp do_rest([head | tail]), do: tail
122 defp do_rest([]), do: []
123
124 def map([{_function_type, function}, list]), do: do_map(function, list)
125 def map([function, list]), do: do_map(function, list)
126
127 defp do_map(function, list) do
128 convert_vector(list)
129 |> Enum.map(fn arg -> function.([arg]) end)
130 end
131
132 def apply([{_function_type, function} | tail]), do: do_apply(function, tail)
133 def apply([function | tail]), do: do_apply(function, tail)
134
135 def do_apply(function, tail) do
136 [list | reversed_args] = Enum.reverse(tail)
137 args = Enum.reverse(reversed_args)
138 func_args = Enum.concat(args, convert_vector(list))
139 function.(func_args)
140 end
141
142 def symbol?([{:symbol, _}]), do: true
143 def symbol?(_), do: false
144
145 def vector?([{:vector, _}]), do: true
146 def vector?(_), do: false
147
148 def keyword([atom]) when is_atom(atom), do: atom
149 def keyword([atom]), do: String.to_atom(atom)
150
151 def cons([prepend, {:vector, list}]), do: [prepend | list]
152 def cons([prepend, list]), do: [prepend | list]
153
154 def concat(args) do
155 Enum.map(args, &convert_vector/1)
156 |> Enum.concat
157 end
158
159 def assoc([hash_map | pairs]) do
160 Map.merge(hash_map, Mal.Types.hash_map(pairs))
161 end
162
163 def dissoc([hash_map | keys]) do
164 Map.drop(hash_map, keys)
165 end
166
167 def get([map, key]) when is_map(map), do: Map.get(map, key, nil)
168 def get([_map, _key]), do: nil
169 end