~sjm/lixp

eef1f9a900634f7195385996eb001ecf178b7ad0 — Sam Marshall 3 months ago 71acb16 master
chore: clean up implementation
2 files changed, 120 insertions(+), 85 deletions(-)

M lib/lixp.ex
M lib/lixp/parser.ex
M lib/lixp.ex => lib/lixp.ex +45 -33
@@ 1,33 1,41 @@
defmodule Lixp do
  def eval(str) do
    {:ok, prog, _, _, _, _} = Lixp.Parser.program(str)
  

    {val, _} =
      Enum.reduce(prog, {nil, %{}}, fn p, {_, e} ->
        eval(p, e)
      end)
  

    val
  end

  defp eval(num, env) when is_number(num) do
    {num, env}
  end

  defp eval(atom, env) when is_atom(atom) do
    {atom, env}
  end

  defp eval({:symbol, sym}, env) do
    {env[sym], env}
  end

  defp eval([{:symbol, "quote"} | [rest]], env) do
    rest = Enum.map(rest, fn
      [{:symbol, "unquote"} | [body]] ->
        {val, _} = eval(body, env)
        val
      x -> x
    end)
  
    rest =
      Enum.map(rest, fn
        [{:symbol, "unquote"} | [body]] ->
          {val, _} = eval(body, env)
          val

        x ->
          x
      end)

    {rest, env}
  end

  defp eval([{:symbol, "ffi"} | rest], env) do
    [m, f, a] = rest
    {m, env} = eval(m, env)


@@ 35,36 43,40 @@ defmodule Lixp do
    {a, env} = eval(a, env)
    {apply(m, f, a), env}
  end

  defp eval([{:symbol, "define"} | rest], env) do
    [{:symbol, name}, body] = rest
    {val, env} = eval(body, env)
    env = Map.put(env, name, val)
  

    {val, env}
  end
  defp eval([{:symbol, "lambda"} | rest ], env) do
    [ arg_names | [body] ] = rest
    {fn args ->
      arg_names = Enum.map(arg_names, fn {:symbol, name} -> name end)
      combination = Enum.zip(arg_names, args)
      {val, _} = eval(body, extend(combination, env))
      val
    end, env}
  end
  defp eval([h | t], env) do
    {f, env} = eval(h, env)
    args = Enum.map(t, fn x ->
      {val, _} = eval(x, env)
      val
    end)
  
    {f.(args), env}

  defp eval([{:symbol, "lambda"} | rest], env) do
    [arg_names | [body]] = rest

    {fn arg_values ->
       arg_names = Enum.map(arg_names, fn {:symbol, name} -> name end)
       args = Enum.zip(arg_names, arg_values)

       env =
         Enum.reduce(args, env, fn {key, val}, e ->
           Map.put(e, key, val)
         end)

       eval(body, env) |> elem(0)
     end, env}
  end
  defp extend([{name, val} | rest], env) do
    extend(rest, Map.put(env, name, val))
  end
  
  defp extend([], env) do
    env

  defp eval([function | args], env) do
    {function, _} = eval(function, env)

    args =
      Enum.map(args, fn arg ->
        {val, _} = eval(arg, env)
        val
      end)

    {function.(args), env}
  end
end

M lib/lixp/parser.ex => lib/lixp/parser.ex +75 -52
@@ 1,72 1,95 @@
defmodule Lixp.Parser.Help do
defmodule Lixp.Parser do
  import NimbleParsec
  def digit do
    ascii_char([?0..?9]) |> map(:char_to_string)

  defp parse_float(str) do
    Float.parse(str) |> elem(0)
  end
  def dot do
    ascii_char([?.]) |> map(:char_to_string)

  defp wrap_symbol(sym) do
    {:symbol, sym}
  end
  def number do
    digit()
    |> optional(repeat(digit()))
    |> optional(dot())
    |> optional(repeat(digit()))

  digit = ascii_string([?0..?9], min: 1)
  dot = string(".")

  number =
    digit
    |> optional(dot |> concat(digit))
    |> reduce({Enum, :join, [""]})
    |> map(:parse_float)
    |> label("number")
  end
end
defmodule Lixp.Parser do
  import NimbleParsec
  import Lixp.Parser.Help
  def parse_float(str) do
    {n, _} = Float.parse(str)
    n
  end
  def char_to_string(char) do
    List.to_string([char])
  end

  colon = string(":")
  valid_atom_chars = ascii_string([?^..?z, ?!..?', ?*..?., ?0..?Z], min: 1)
  

  symbol_characters =
    ascii_string(
      [
        # space
        {:not, 32},
        {:not, ?(},
        {:not, ?)},
        {:not, ?:},
        {:not, ?0..9},
        {:not, ?.}
      ],
      min: 1
    )

  symbol =
    symbol_characters
    |> map(:wrap_symbol)
    |> label("symbol")

  atom =
    ignore(colon)
    |> concat(valid_atom_chars)
    |> concat(symbol_characters)
    |> map({String, :to_atom, []})
    |> label("atom")
  
  def wrap_symbol(sym) do
    {:symbol, sym}
  end
  
  symbol =
    valid_atom_chars
    |> map({:wrap_symbol, []})
    |> label("symbol")
  open_list = ascii_char([?(]) |> map(:char_to_string)
  close_list = ascii_char([?)]) |> map(:char_to_string)
  

  open_list = string("(")
  close_list = string(")")

  list =
    ignore(open_list)
    |> optional(repeat(choice([
            atom,
            number(),
            parsec(:list),
            symbol,
            ignore(string(" "))])))
    |> concat(ignore(close_list))
    |> optional(repeat(parsec(:term)))
    |> ignore(close_list)
    |> reduce({Enum, :into, [[]]})
    |> label("list")
  
    defparsec(
      :list,
      list
    )

  q = string("'")
  comma = string(",")

  defp quote_wrap(arg) do
    [{:symbol, "quote"}, arg]
  end

  defp unquote_wrap(arg) do
    [{:symbol, "unquote"}, arg]
  end

  quoted =
    ignore(q)
    |> parsec(:term)
    |> map(:quote_wrap)

  unquoted =
    ignore(comma)
    |> parsec(:term)
    |> map(:unquote_wrap)

  defparsec(
    :term,
    choice([list, number(), atom, symbol, ignore(string(" ")), ignore(string("\n"))])
    choice([
      number,
      list,
      symbol,
      atom,
      quoted,
      unquoted,
      ignore(string(" ")),
      ignore(string("\n"))
    ])
  )
  

  defparsec(
    :program,
    repeat(parsec(:term))