1 defmodule Mal
.Reader
do
5 case
tokenize(input
) do
13 def
tokenize(input
) do
14 regex
= ~r
/[\s
,]*(~@|
[\
[\
]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/
15 Regex.scan(regex, input, capture: :all_but_first)
17 |> List.delete_at(-1) # Remove the last match, which is an empty string
18 |> Enum.filter(fn token -> not String.starts_with?(token, ";") end)
21 defp read_form([next | rest] = tokens) do
23 "(" -> read_list(tokens)
24 "[" -> read_vector(tokens)
25 "{" -> read_hash_map(tokens)
26 "'" -> create_quote("quote", rest)
27 "`" -> create_quote("quasiquote", rest)
28 "~" -> create_quote("unquote", rest)
29 "~@" -> create_quote("splice-unquote", rest)
30 "@" -> create_quote("deref", rest)
31 "^" -> create_meta(rest)
32 ")" -> throw({:error, "unexpected )"})
33 "]" -> throw({:error, "unexpected ]"})
34 "}" -> throw({:error, "unexpected }"})
36 token = read_atom(next)
41 defp create_meta(tokens) do
42 {meta, meta_rest} = read_form(tokens)
43 {token, rest_tokens} = read_form(meta_rest)
44 new_token = list([{:symbol, "with-meta"}, token, meta])
45 {new_token, rest_tokens}
48 defp create_quote(quote_type, tokens) do
49 {token, rest_tokens} = read_form(tokens)
50 new_token = list([{:symbol, quote_type}, token])
51 {new_token, rest_tokens}
54 defp read_list([_ | tokens]) do
55 {ast, rest} = do_read_sequence(tokens, [], "(", ")")
59 defp read_vector([_ | tokens]) do
60 {ast, rest} = do_read_sequence(tokens, [], "[", "]")
64 defp read_hash_map([_ | tokens]) do
65 {map, rest} = do_read_sequence(tokens, [], "{", "}")
69 defp do_read_sequence([], _acc, _start_sep, end_sep), do: throw({:error, "expected #{end_sep}, got EOF"})
70 defp do_read_sequence([head | tail] = tokens, acc, start_sep, end_sep) do
72 String.starts_with?(head, end_sep) ->
73 {Enum.reverse(acc), tail}
75 {token, rest} = read_form(tokens)
76 do_read_sequence(rest, [token | acc], start_sep, end_sep)
80 defp read_atom("nil"), do: nil
81 defp read_atom("true"), do: true
82 defp read_atom("false"), do: false
83 defp read_atom(":" <> rest), do: String.to_atom(rest)
84 defp read_atom(token) do
86 String.starts_with?(token, "\"") and String.ends_with?(token, "\"") ->
88 |> String.slice(1..-2)
89 |> String.replace("\\\"", "\"")
95 true -> {:symbol, token}