45c6812bb181e5406f96515625b5d04be6cc04d8
[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 "sequential?" => fn arg -> vector?(arg) or list?(arg) end,
32 "vector" => fn list -> {:vector, list} end,
33 "keyword?" => fn [type] -> is_atom(type) end,
34 "nil?" => fn [type] -> type == nil end,
35 "true?" => fn [type] -> type == true end,
36 "false?" => fn [type] -> type == false end,
37 "symbol" => fn [name] -> {:symbol, name} end,
38 "list" => fn args -> args end,
39 "read-string" => fn [input] -> Mal.Reader.read_str(input) end,
40 "throw" => fn [arg] -> throw({:error, arg}) end,
41 }
42 end
43
44 defp convert_vector({:vector, list}), do: list
45 defp convert_vector(other), do: other
46
47 def equal([a, b]) do
48 convert_vector(a) == convert_vector(b)
49 end
50
51 def list?([arg]) when is_list(arg), do: true
52 def list?([_arg]), do: false
53
54 def empty?([[]]), do: true
55 def empty?([{:vector, []}]), do: true
56 def empty?(_), do: false
57
58 def count([arg]) when is_list(arg), do: length(arg)
59 def count([{:vector, arg}]), do: length(arg)
60 def count(_), do: 0
61
62 def pr_str(args) do
63 args
64 |> Enum.map(&Mal.Printer.print_str/1)
65 |> Enum.join(" ")
66 end
67
68 def str(args) do
69 args
70 |> Enum.map(&(Mal.Printer.print_str(&1, false)))
71 |> Enum.join("")
72 end
73
74 def prn(args) do
75 args
76 |> pr_str
77 |> IO.puts
78 nil
79 end
80
81 def println(args) do
82 args
83 |> Enum.map(&(Mal.Printer.print_str(&1, false)))
84 |> Enum.join(" ")
85 |> IO.puts
86 nil
87 end
88
89 def slurp([file_name]) do
90 case File.read(file_name) do
91 {:ok, content} -> content
92 {:error, :enoent} -> throw({:error, "can't find file #{file_name}"})
93 {:error, :eisdir} -> throw({:error, "can't read directory #{file_name}"})
94 {:error, :eaccess} -> throw({:error, "missing permissions #{file_name}"})
95 {:error, reason} -> throw({:error, "can't read file #{file_name}, #{reason}"})
96 end
97 end
98
99 def nth([list, index]) do
100 case Enum.at(convert_vector(list), index, :error) do
101 :error -> throw({:error, "index out of bounds"})
102 any -> any
103 end
104 end
105
106 def first([{:vector, [head | tail]}]), do: head
107 def first([[head | tail]]), do: head
108 def first(_), do: nil
109
110 def rest([{:vector, list}]), do: do_rest(list)
111 def rest([list]), do: do_rest(list)
112
113 defp do_rest([head | tail]), do: tail
114 defp do_rest([]), do: []
115
116 def map([{_function_type, function}, list]), do: do_map(function, list)
117 def map([function, list]), do: do_map(function, list)
118
119 defp do_map(function, list) do
120 convert_vector(list)
121 |> Enum.map(fn arg -> function.([arg]) end)
122 end
123
124 def apply([{_function_type, function} | tail]), do: do_apply(function, tail)
125 def apply([function | tail]), do: do_apply(function, tail)
126
127 def do_apply(function, tail) do
128 [list | reversed_args] = Enum.reverse(tail)
129 args = Enum.reverse(reversed_args)
130 func_args = Enum.concat(args, convert_vector(list))
131 function.(func_args)
132 end
133
134 def symbol?([{:symbol, _}]), do: true
135 def symbol?(_), do: false
136
137 def vector?([{:vector, _}]), do: true
138 def vector?(_), do: false
139
140 def keyword([atom]) when is_atom(atom), do: atom
141 def keyword([atom]), do: String.to_atom(atom)
142
143 def cons([prepend, {:vector, list}]), do: [prepend | list]
144 def cons([prepend, list]), do: [prepend | list]
145
146 def concat(args) do
147 Enum.map(args, &convert_vector/1)
148 |> Enum.concat
149 end
150 end