Add map core functions, step 1 to 9 passing
[jackhill/mal.git] / elixir / lib / mal / core.ex
CommitLineData
fffde7d7 1defmodule Mal.Core do
2 def namespace do
3 %{
d34837dd 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,
d34837dd 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,
1bc4ac2b 12 "concat" => &concat/1,
13 "=" => &equal/1,
d34837dd 14 "list?" => &list?/1,
15 "empty?" => &empty?/1,
a355b219 16 "count" => &count/1,
17 "pr-str" => &pr_str/1,
18 "str" => &str/1,
19 "prn" => &prn/1,
68ce2eea 20 "println" => &println/1,
4115c430 21 "slurp" => &slurp/1,
0887f470 22 "nth" => &nth/1,
23 "first" => &first/1,
24 "rest" => &rest/1,
0a272a6b 25 "map" => &map/1,
ed5a059b 26 "apply" => &apply/1,
2b8ec8ad 27 "keyword" => &keyword/1,
ed5a059b 28 "symbol?" => &symbol?/1,
1bc4ac2b 29 "cons" => &cons/1,
30 "vector?" => &vector?/1,
87e39e76 31 "assoc" => &assoc/1,
32 "dissoc" => &dissoc/1,
33 "get" => &get/1,
34 "hash-map" => &Mal.Types.hash_map/1,
1bc4ac2b 35 "sequential?" => fn arg -> vector?(arg) or list?(arg) end,
36 "vector" => fn list -> {:vector, list} end,
2b8ec8ad 37 "keyword?" => fn [type] -> is_atom(type) end,
87e39e76 38 "map?" => fn [type] -> is_map(type) end,
ed5a059b 39 "nil?" => fn [type] -> type == nil end,
40 "true?" => fn [type] -> type == true end,
41 "false?" => fn [type] -> type == false end,
2b8ec8ad 42 "symbol" => fn [name] -> {:symbol, name} end,
43 "list" => fn args -> args end,
0887f470 44 "read-string" => fn [input] -> Mal.Reader.read_str(input) end,
ed5a059b 45 "throw" => fn [arg] -> throw({:error, arg}) end,
87e39e76 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
fffde7d7 49 }
50 end
51
1bc4ac2b 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
d34837dd 59 def list?([arg]) when is_list(arg), do: true
60 def list?([_arg]), do: false
61
62 def empty?([[]]), do: true
1bc4ac2b 63 def empty?([{:vector, []}]), do: true
d34837dd 64 def empty?(_), do: false
65
66 def count([arg]) when is_list(arg), do: length(arg)
1bc4ac2b 67 def count([{:vector, arg}]), do: length(arg)
d34837dd 68 def count(_), do: 0
a355b219 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
68ce2eea 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
0887f470 106
107 def nth([list, index]) do
1bc4ac2b 108 case Enum.at(convert_vector(list), index, :error) do
0887f470 109 :error -> throw({:error, "index out of bounds"})
110 any -> any
111 end
112 end
113
1bc4ac2b 114 def first([{:vector, [head | tail]}]), do: head
0887f470 115 def first([[head | tail]]), do: head
116 def first(_), do: nil
117
1bc4ac2b 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: []
0a272a6b 123
ed5a059b 124 def map([{_function_type, function}, list]), do: do_map(function, list)
0a272a6b 125 def map([function, list]), do: do_map(function, list)
126
127 defp do_map(function, list) do
1bc4ac2b 128 convert_vector(list)
129 |> Enum.map(fn arg -> function.([arg]) end)
0a272a6b 130 end
ed5a059b 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
1bc4ac2b 136 [list | reversed_args] = Enum.reverse(tail)
137 args = Enum.reverse(reversed_args)
138 func_args = Enum.concat(args, convert_vector(list))
ed5a059b 139 function.(func_args)
140 end
141
142 def symbol?([{:symbol, _}]), do: true
143 def symbol?(_), do: false
2b8ec8ad 144
1bc4ac2b 145 def vector?([{:vector, _}]), do: true
146 def vector?(_), do: false
147
2b8ec8ad 148 def keyword([atom]) when is_atom(atom), do: atom
149 def keyword([atom]), do: String.to_atom(atom)
1bc4ac2b 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
87e39e76 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
fffde7d7 169end