~sjm/lixp

51e05906a560fa27dea781576863d83f6ff83361 — Sam Marshall 2 years ago 38bab1b
initial commit
9 files changed, 203 insertions(+), 0 deletions(-)

A .formatter.exs
A .gitignore
A README.md
A lib/lixp.ex
A lib/lixp/parser.ex
A mix.exs
A mix.lock
A test/lixp_test.exs
A test/test_helper.exs
A .formatter.exs => .formatter.exs +4 -0
@@ 0,0 1,4 @@
# Used by "mix format"
[
  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

A .gitignore => .gitignore +27 -0
@@ 0,0 1,27 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
lixp-*.tar


# Temporary files for e.g. tests
/tmp

A README.md => README.md +21 -0
@@ 0,0 1,21 @@
# Lixp

**TODO: Add description**

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `lixp` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:lixp, "~> 0.1.0"}
  ]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/lixp](https://hexdocs.pm/lixp).


A lib/lixp.ex => lib/lixp.ex +36 -0
@@ 0,0 1,36 @@
defmodule Lixp do
  def eval(str) do
    {:ok, [res], _, _, _, _} = Lixp.Parser.program(str)
    eval(res, %{})
  end
  defp eval(num, _env) when is_number(num) do
    num
  end
  defp eval(atom, _env) when is_atom(atom) do
    atom
  end
  defp eval({:symbol, sym}, env) do
    env[sym]
  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)
      eval(body, extend(combination, env))
    end
  end
  defp eval([h | t], env) do
    f = eval(h, env)
    args = Enum.map(t, &eval(&1, env))
  
    f.(args)
  end
  defp extend([{name, val} | rest], env) do
    extend(rest, Map.put(env, name, val))
  end
  
  defp extend([], env) do
    env
  end
end

A lib/lixp/parser.ex => lib/lixp/parser.ex +74 -0
@@ 0,0 1,74 @@
defmodule Lixp.Parser.Help do
  import NimbleParsec
  def digit do
    ascii_char([?0..?9]) |> map(:char_to_string)
  end
  def dot do
    ascii_char([?.]) |> map(:char_to_string)
  end
  def number do
    digit()
    |> optional(repeat(digit()))
    |> optional(dot())
    |> optional(repeat(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)
  
  atom =
    ignore(colon)
    |> concat(valid_atom_chars)
    |> 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)
  
  list =
    ignore(open_list)
    |> optional(repeat(choice([
            atom,
            number(),
            parsec(:list),
            symbol,
            ignore(string(" "))])))
    |> concat(ignore(close_list))
    |> reduce({Enum, :into, [[]]})
    |> label("list")
  
    defparsec(
      :list,
      list
    )
  defparsec(
    :term,
    choice([list, number(), atom, symbol, ignore(string(" "))])
  )
  
  defparsec(
    :program,
    repeat(parsec(:term))
  )
end

A mix.exs => mix.exs +29 -0
@@ 0,0 1,29 @@
defmodule Lixp.MixProject do
  use Mix.Project

  def project do
    [
      app: :lixp,
      version: "0.1.0",
      elixir: "~> 1.11",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
      {:nimble_parsec, "~> 1.0"}
    ]
  end
end

A mix.lock => mix.lock +3 -0
@@ 0,0 1,3 @@
%{
  "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
}

A test/lixp_test.exs => test/lixp_test.exs +8 -0
@@ 0,0 1,8 @@
defmodule LixpTest do
  use ExUnit.Case
  doctest Lixp

  test "greets the world" do
    assert Lixp.hello() == :world
  end
end

A test/test_helper.exs => test/test_helper.exs +1 -0
@@ 0,0 1,1 @@
ExUnit.start()