diff --git a/lib/lytlang.ex b/lib/lytlang.ex index c6dce4f..d3d4c7f 100644 --- a/lib/lytlang.ex +++ b/lib/lytlang.ex @@ -1,23 +1,28 @@ defmodule Lytlang do + @root_module :Lytlang + def eval(string) do {value, _} = string |> from_lytlang() - |> IO.inspect() |> Code.eval_quoted() - |> IO.inspect() value end def from_lytlang(string, binding \\ [], opts \\ []) do - string |> String.to_charlist() |> tokenize() |> parse() |> transform(binding, opts) + string + |> String.to_charlist() + |> tokenize() + |> parse() + |> to_ast_node(binding, opts) + |> to_elixir_ast(opts) end def tokenize(string) do {:ok, tokens, _} = :lytlang_lexer.string(string) - |> IO.inspect() + |> IO.inspect(label: "Tokens") tokens end @@ -25,28 +30,77 @@ defmodule Lytlang do def parse(tokens) do {:ok, tree} = :lytlang_parser.parse(tokens) - |> IO.inspect() + |> IO.inspect(label: "Parsed") tree end - def transform(ast, binding \\ [], opts \\ []) + @type astnode :: {any, {keyword, keyword, keyword}, list} + @type ex_astnode :: {any, keyword, any} - def transform([] = _expr_list, _binding, _opts), do: [] + # @spec to_elixir_ast(astnode, keyword) :: ex_astnode + def to_elixir_ast(astnode, opts \\ []) - def transform([expr | rest] = _expr_list, binding, opts) do - [transform(expr, binding, opts) | transform(rest, binding, opts)] + def to_elixir_ast(s, opts) when is_list(s) do + Enum.map(s, fn node -> + to_elixir_ast(node, opts) + end) end - def transform({:binary_op, _line, op, left, right}, binding, _) do - {op, binding, [transform(left), transform(right)]} + def to_elixir_ast({{:literal, expr}, {_meta, _binding, _ast_opts}, _leaves}, _opts) do + expr + |> IO.inspect(label: "EX AST") end - def transform({:unary_op, line, op, left}, _, _) do - {:op, line, op, transform(left)} + def to_elixir_ast({s, {_meta, binding, _ast_opts}, leaves}, _opts) do + {s, binding, to_elixir_ast(leaves)} + |> IO.inspect(label: "EX AST") end - def transform({:integer, _, n} = _expr, _, _), do: n + @spec to_ast_node(any, keyword, keyword) :: astnode + def to_ast_node(ast, binding \\ [], opts \\ []) + + def to_ast_node([], binding, opts), do: {[], {binding, opts}} + + def to_ast_node(expr_list, binding, opts) when is_list(expr_list) do + Enum.reduce(expr_list, {nil, {[], binding, opts}, []}, fn expr, + {_s, {_meta, binding, opts}, + _operands} -> + {s, {nmeta, nbinding, nopts}, operands} = to_ast_node(expr, binding, opts) + + {s, {nmeta, Keyword.merge(binding, nbinding), Keyword.merge(opts, nopts)}, operands} + end) + end + + def to_ast_node({:list_op, line, op, operands}, binding, opts) when is_list(operands) do + {op, {[line: line], binding, opts}, + [Enum.map(operands, fn o -> to_ast_node(o, binding, opts) end)]} + end + + def to_ast_node({:binary_op, line, op, left, right}, binding, opts) do + {op, {[line: line], binding, opts}, [to_ast_node(left), to_ast_node(right)]} + end + + def to_ast_node({:unary_op, line, op, left}, binding, opts) do + {op, {[line: line], binding, opts}, [to_ast_node(left)]} + end + + def to_ast_node({:var, line, left} = _expr, binding, opts) do + # TODO: probably need to look in bindings? or shadowing so maybe not? + to_ast_node({:unary_op, line, left, {:literal, line, @root_module}}, binding, opts) + end + + def to_ast_node({:match, line, left, right} = _expr, binding, opts) do + to_ast_node({:binary_op, line, :=, left, right}, binding, opts) + end + + def to_ast_node({:integer, line, val} = _expr, binding, opts) do + to_ast_node({:literal, line, val}, binding, opts) + end + + def to_ast_node({:literal, line, val} = _expr, binding, opts) do + {{:literal, val}, {[line: line], binding, opts}, []} + end end """ diff --git a/src/lytlang_lexer.xrl b/src/lytlang_lexer.xrl index da8c05f..d0c3fed 100644 --- a/src/lytlang_lexer.xrl +++ b/src/lytlang_lexer.xrl @@ -7,6 +7,7 @@ D = [0-9] U = [A-Z] L = [a-z] WS = [\s] +LF = \n Rules. @@ -22,10 +23,13 @@ Rules. \) : { token, { ')', TokenLine } }. = : { token, { '=', TokenLine } }. -> : { token, { '->', TokenLine } }. -; : { token, { ';', TokenLine } }. +; : { token, { eol, TokenLine } }. +{LF} : { token, { eol, TokenLine } }. {Comment} : skip_token. {WS}+ : skip_token. +% {LF}+ : skip_token. +% {CRLF}+ : skip_token. % ({Comment}|{Whitespace})*(\n({Comment}|{Whitespace})*)+ : { token, { eol, TokenLine } }. Erlang code. diff --git a/src/lytlang_parser.yrl b/src/lytlang_parser.yrl index 22cbc9e..24864e8 100644 --- a/src/lytlang_parser.yrl +++ b/src/lytlang_parser.yrl @@ -63,6 +63,7 @@ fun_expr -> stabber eol body : fun_expr -> max_expr : '$1'. %% Minimum expressions +max_expr -> var : '$1'. max_expr -> number : '$1'. max_expr -> '(' expr ')' : '$2'. diff --git a/test/lytlang_test.exs b/test/lytlang_test.exs index 6d3ceda..ed15710 100644 --- a/test/lytlang_test.exs +++ b/test/lytlang_test.exs @@ -12,6 +12,7 @@ defmodule LytlangTest do {"2 + (3 * 8)", 26}, {"8 - 3", 5}, {"8 / 4", 2.0}, + {"a = 8\na + 9", 17}, {"2 + 3", 5} ] |> Enum.map(fn {string, val} ->