mirror of
https://github.com/kanaka/mal.git
synced 2024-11-10 12:47:45 +03:00
Add read/print functionality up to quote (step 1)
This commit is contained in:
parent
51c2c1fea7
commit
b5aff35fe8
13
elixir/lib/mal/printer.ex
Normal file
13
elixir/lib/mal/printer.ex
Normal file
@ -0,0 +1,13 @@
|
||||
defmodule Mal.Printer do
|
||||
def print_str(mal) when is_atom(mal), do: Atom.to_string(mal)
|
||||
def print_str(mal) when is_integer(mal), do: Integer.to_string(mal)
|
||||
def print_str(mal) when is_bitstring(mal), do: mal
|
||||
def print_str({ :symbol, value }), do: value
|
||||
def print_str(mal) when is_list(mal) do
|
||||
output = mal
|
||||
|> Enum.map(fn(x) -> print_str(x) end)
|
||||
|> Enum.join(" ")
|
||||
|
||||
"(#{output})"
|
||||
end
|
||||
end
|
58
elixir/lib/mal/reader.ex
Normal file
58
elixir/lib/mal/reader.ex
Normal file
@ -0,0 +1,58 @@
|
||||
# TODO: def -> defp for everything but read_str
|
||||
defmodule Mal.Reader do
|
||||
import Mal.Types
|
||||
|
||||
def read_str(input) do
|
||||
output = tokenize(input)
|
||||
|> read_form
|
||||
|> elem(0)
|
||||
{:ok, output}
|
||||
catch
|
||||
{:invalid, message} -> {:error, message}
|
||||
end
|
||||
|
||||
def tokenize(input) do
|
||||
regex = ~r/[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/
|
||||
Regex.scan(regex, input, capture: :all_but_first)
|
||||
|> List.flatten
|
||||
|> List.delete_at(-1) # Remove the last match, which is an empty string
|
||||
end
|
||||
|
||||
def read_form([next | rest] = tokens) do
|
||||
case next do
|
||||
"(" <> _ ->
|
||||
read_list(tokens)
|
||||
_ ->
|
||||
token = read_atom(next)
|
||||
{token, rest}
|
||||
end
|
||||
end
|
||||
|
||||
def read_list([_ | tokens]), do: do_read_list(tokens, [])
|
||||
|
||||
defp do_read_list([], acc), do: throw({:invalid, "expected ')', got EOF"})
|
||||
defp do_read_list([head | tail] = tokens, acc) do
|
||||
case head do
|
||||
")" <> _ -> {Enum.reverse(acc), tail}
|
||||
_ ->
|
||||
{token, rest} = read_form(tokens)
|
||||
do_read_list(rest, [token | acc])
|
||||
end
|
||||
end
|
||||
|
||||
def read_atom("nil"), do: nil
|
||||
def read_atom("true"), do: true
|
||||
def read_atom("false"), do: false
|
||||
def read_atom(":" <> rest), do: String.to_atom(rest)
|
||||
def read_atom(token) do
|
||||
cond do
|
||||
String.starts_with?(token, "\"") -> token
|
||||
String.starts_with?(token, "'") -> token
|
||||
integer?(token) ->
|
||||
Integer.parse(token)
|
||||
|> elem(0)
|
||||
|
||||
true -> {:symbol, token}
|
||||
end
|
||||
end
|
||||
end
|
9
elixir/lib/mal/types.ex
Normal file
9
elixir/lib/mal/types.ex
Normal file
@ -0,0 +1,9 @@
|
||||
defmodule Mal.Types do
|
||||
def integer?(input) do
|
||||
Regex.match?(~r/^-?[0-9]+$/, input)
|
||||
end
|
||||
|
||||
def float?(input) do
|
||||
Regex.match?(~r/^-?[0-9][0-9.]*$/, input)
|
||||
end
|
||||
end
|
32
elixir/lib/mix/tasks/step1_read_print.ex
Normal file
32
elixir/lib/mix/tasks/step1_read_print.ex
Normal file
@ -0,0 +1,32 @@
|
||||
defmodule Mix.Tasks.Step1ReadPrint do
|
||||
def run(_), do: main
|
||||
|
||||
def main do
|
||||
IO.write(:stdio, "user> ")
|
||||
IO.read(:stdio, :line)
|
||||
|> read_eval_print
|
||||
|
||||
main
|
||||
end
|
||||
|
||||
def read(input) do
|
||||
Mal.Reader.read_str(input)
|
||||
end
|
||||
|
||||
def eval({:ok, input}), do: {:ok, input}
|
||||
def eval({:error, message}), do: {:error, message}
|
||||
|
||||
def print({:ok, output}) do
|
||||
IO.puts(Mal.Printer.print_str(output))
|
||||
end
|
||||
def print({:error, message}) do
|
||||
IO.puts(message)
|
||||
end
|
||||
|
||||
def read_eval_print(:eof), do: exit(0)
|
||||
def read_eval_print(line) do
|
||||
read(line)
|
||||
|> eval
|
||||
|> print
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user