Skip to content

Commit

Permalink
Joy with a minimal set of combinators
Browse files Browse the repository at this point in the history
  • Loading branch information
palm86 committed Nov 12, 2020
1 parent ba9c3cc commit e18ca6e
Show file tree
Hide file tree
Showing 19 changed files with 1,260 additions and 7 deletions.
4 changes: 4 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
27 changes: 21 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
/_build
/cover
/deps
/doc
# 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
*.beam
/config/*.secret.exs

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

.elixir_ls/
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,21 @@
# joy
# Joy

Minimal Joy parser and interpreter

## Installation

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

```elixir
def deps do
[
{:joy, "~> 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/joy](https://hexdocs.pm/joy).

26 changes: 26 additions & 0 deletions lib/joy.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Joy do
@type program :: list(func_or_quot)
@type func_or_quot :: func | quot
@type func :: atom
@type quot :: [program]

@type stack :: list(func_or_quot)

@spec sigil_J(binary, any) :: program | stack
def sigil_J(string, [?p]) do
string
|> Joy.Parser.parse()
end

def sigil_J(string, _) do
string
|> Joy.Parser.parse()
|> Joy.Interpreter.interpret()
end

@spec sigil_Q(binary, any) :: func_or_quot
def sigil_Q(string, _) do
[quotation] = Joy.Parser.parse(string)
quotation
end
end
34 changes: 34 additions & 0 deletions lib/joy/interpreter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defmodule Joy.Interpreter do
@type program :: list(func_or_quot)
@type func_or_quot :: func | quot
@type func :: atom
@type quot :: [program]

@type stack :: list(func_or_quot)

@callback __execute(stack, program) :: stack

defmacro __using__(_opts) do
quote do
def __execute(stack, program) when is_list(stack) and is_list(program) do
Enum.reduce(program, stack, fn
function, stack when is_atom(function) ->
if Kernel.function_exported?(__MODULE__, function, 1) do
Kernel.apply(__MODULE__, function, [stack])
else
[function | stack]
end

quotation, stack when is_list(quotation) ->
[quotation | stack]
end)
end
end
end

@spec interpret(program) :: stack
def interpret(program) when is_list(program) do
impl = Application.get_env(:joy, __MODULE__)[:interpreter] || Joy.Interpreter.Kerby
impl.__execute([], program)
end
end
105 changes: 105 additions & 0 deletions lib/joy/interpreter/kerby.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
defmodule Joy.Interpreter.Kerby do
@moduledoc """
See: http://tunes.org/~iepos/joy.html
"""
use Joy.Interpreter
require Logger

@doc """
[B] [A] swap == [A] [B]
Alt: swap == unit dip
"""
def swap(stack) do
[a, b | rest] = stack

[b, a | rest]
|> IO.inspect(label: "swap")
end

@doc """
[A] dup == [A] [A]
Alt: swap == unit dip
"""
def dup(stack) do
# Extract
[head | stack] = stack

[head, head | stack]
|> IO.inspect(label: "dup")
end

@doc """
[A] zap ==
"""
def zap(stack) do
[_ | stack] = stack

stack
|> IO.inspect(label: "zap")
end

@doc """
# [A] unit == [[A]]
Alt: unit == [] cons
"""
def unit(stack) do
[a | rest] = stack

[[a] | rest]
|> IO.inspect(label: "unit")
end

@doc """
[B] [A] cat == [B A]
Alt (requires transparent quotation): cat == [[i] dip i] cons cons
"""
def cat(stack) do
[a, b | rest] = stack

case {a, b} do
{a, b} when is_list(a) and is_list(b) -> [b ++ a | rest]
end
|> IO.inspect(label: "cat")
end

@doc """
[B] [A] cons == [[B] A]
Alt: cons == [unit] dip cat
"""
def cons(stack) do
[a, b | rest] = stack

case {a, b} do
{a, b} when is_list(a) -> [[b | a] | rest]
end
|> IO.inspect(label: "cons")
end

@doc """
[A] i == A
Alt: i == dup dip zap
"""
def i([a | rest] = _stack) when is_list(a) do
__execute(rest, a)
|> IO.inspect(label: "i")
end

@doc """
[B] [A] dip == A [B]
Also, dip == swap unit cat i
"""
def dip([a, b | rest] = _stack) when is_list(a) and is_list(b) do
[b | __execute(rest, a)]
|> IO.inspect(label: "dip")
end

def id(stack), do: stack |> IO.inspect(label: "id")
end
14 changes: 14 additions & 0 deletions lib/joy/parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule Joy.Parser do
@type program :: list(func_or_quot)
@type func_or_quot :: func | quot
@type func :: atom
@type quot :: [program]

@spec parse(binary) :: program
def parse(str) do
with {:ok, tokens, _} <- str |> to_charlist() |> :joy_lexer.string(),
{:ok, joy} <- :joy_parser.parse(tokens) do
joy
end
end
end
28 changes: 28 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule Joy.MixProject do
use Mix.Project

def project do
[
app: :joy,
version: "0.1.0",
elixir: "~> 1.10",
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"}
]
end
end
Loading

0 comments on commit e18ca6e

Please sign in to comment.