Skip to content

Instantly share code, notes, and snippets.

@roehst
Created October 2, 2018 21:23
Show Gist options
  • Save roehst/6db26f8d8d7cad6b2c96abca2aa33224 to your computer and use it in GitHub Desktop.
Save roehst/6db26f8d8d7cad6b2c96abca2aa33224 to your computer and use it in GitHub Desktop.
Mini language in Elixir w/ REPL
defmodule Parser do
def parse(source) do
source |> Code.string_to_quoted!()
end
def parse_to_expr(source) do
source |> parse |> to_expr
end
def to_expr({:+, _, [a, b]}) do
{:+, to_expr(a), to_expr(b)}
end
def to_expr({:*, _, [a, b]}) do
{:*, to_expr(a), to_expr(b)}
end
def to_expr(num) when is_number(num) do
{:num, num}
end
def to_expr({:let, _, [{:=, _, [{var, _, nil}, {:in, _, [value, body]}]}]}) do
{:let, to_expr(var), to_expr(value), to_expr(body)}
end
def to_expr({:set, _, [{:=, _, [{var, _, nil}, value]}]}) do
{:set, to_expr(var), to_expr(value)}
end
def to_expr({:fun, _, [{arg, _, [{:to, _, [body]}]}]}) when is_atom(arg) do
{:fun, {:var, arg}, to_expr(body)}
end
def to_expr({var, _, nil}) when is_atom(var) do
{:var, var}
end
def to_expr({var, _, [arg]}) do
{:apply, to_expr(var), to_expr(arg)}
end
def to_expr(var) when is_atom(var) do
{:var, var}
end
end
defmodule Interp do
def interp({:+, a, b}, env) do
case interp(a, env) do
{:num, n} when is_number(n) ->
case interp(b, env) do
{:num, m} when is_number(m) ->
{:num, m + n}
_ -> raise "Type error"
end
_ -> raise "Type error"
end
end
def interp({:*, a, b}, env) do
case interp(a, env) do
{:num, n} when is_number(n) ->
case interp(b, env) do
{:num, m} when is_number(m) ->
{:num, m * n}
_ -> raise "Type error"
end
_ -> raise "Type error"
end
end
def interp({:num, num}, _env) do
{:num, num}
end
def interp({:fun, arg, body}, env) do
{:closure, arg, body, env}
end
def interp({:let, {:var, var}, value, body}, env) do
arg = interp(value, env)
extended_env = extend(var, arg, env)
interp(body, extended_env)
end
def interp({:apply, fun, arg}, env) do
fun_ = interp(fun, env)
case fun_ do
{:closure, {:var, var}, body, closure_env} ->
arg_ = interp(arg, env)
interp(body, extend(var, arg_, closure_env))
_ ->
raise "Type error"
end
end
def interp({:var, var}, env) do
case Keyword.fetch(env, var) do
{:ok, value} -> value
_ -> raise "Variable not defined: " <> to_string(var)
end
end
def interp({:set, {:var, var}, value}, env) do
interp_value = interp(value, env)
{:update, interp_value, extend(var, interp_value, env)}
end
def extend(name, value, env) do
[{name, value} | env]
end
end
defmodule REPL do
def show({:closure, {:var, _arg}, _body, _env}) do
"<fun>"
end
def show({:num, num}) do
to_string num
end
def loop(env) do
input = IO.gets(">> ")
case input do
"quit\n" ->
IO.puts("Good-bye!")
"env\n" ->
IO.inspect(env)
loop(env)
_ ->
expr = Parser.parse_to_expr(input)
case Interp.interp(expr, env) do
{:update, result, new_env} ->
IO.puts show(result)
loop(new_env)
result ->
IO.puts show(result)
loop(env)
end
end
end
end
IO.puts """
Examples:
>> set f = fun x to x + x
<fun>
>> f 4
8
>> 1 + 2 + 3
6
>> let y = 10 in y * y
100
"""
REPL.loop []
@roehst
Copy link
Author

roehst commented Oct 2, 2018

Run with "elixir Expr.ex"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment