From f09f9d8edf4621807f113b128f5f733c912f4e3c Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Sat, 11 May 2019 14:00:46 -0500 Subject: [PATCH] WIP parsing --- lib/lytlang.ex | 18 +++++---- lib/transpiler.ex | 93 +++++++++++++++++++++++++++++++++++++++++++ test/lytlang_test.exs | 20 +--------- 3 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 lib/transpiler.ex diff --git a/lib/lytlang.ex b/lib/lytlang.ex index 053f300..096e138 100644 --- a/lib/lytlang.ex +++ b/lib/lytlang.ex @@ -16,14 +16,16 @@ defmodule Lytlang do :world end - @spec transpile_file(String.t()) :: String.t() - def transpile_file(file_path) do - file_path - |> File.read!() - |> transpile_block() - end + @doc """ + Echo. - def transpile_block(s) do - "#{s}" + ## Examples + + iex> Lytlang.echo("world") + "world" + + """ + def echo(s) do + s end end diff --git a/lib/transpiler.ex b/lib/transpiler.ex new file mode 100644 index 0000000..b83acbc --- /dev/null +++ b/lib/transpiler.ex @@ -0,0 +1,93 @@ +defmodule Lytlang.Transpiler do + @moduledoc """ + Documentation for Lytlang. + """ + + @spec transpile_file(String.t()) :: String.t() + def transpile_file(file_path) do + file_path + |> File.read!() + |> transpile_block() + end + + @doc """ + Transpiler! + + ## Examples + + iex> Lytlang.Transpiler.transpile_block("!mod EmptyModule") + "defmodule EmptyModule do\\nend" + + iex> Lytlang.Transpiler.transpile_block("!mod EmptyModule\\n\\tfn hello\\n\\t\\t:world") + "defmodule EmptyModule\\n\\tdef hello() do\\n\\t\\t:world\\n\\tend\\nend" + + """ + @spec transpile_block(String.t(), Keyword.t()) :: String.t() + def transpile_block(s, _opts \\ []) do + s + |> split_lines() + |> Enum.map(&tokenize/1) + |> IO.inspect(label: "Tokens") + |> build_ast() + |> IO.inspect(label: "AST") + |> ast_to_elixir() + end + + def initial_ast_state() do + %{ + ast: {:__global, []} + } + end + + def build_ast(token_lines, state \\ nil) + + def build_ast([], state), do: state.ast |> Enum.reverse() + + def build_ast([line_tokens | rest], state) do + state = + if !state do + initial_ast_state() + else + state + end + + ast = state.ast + + state = + case line_tokens do + ["!mod", module_name | rest] -> + %{state | ast: [{:module, String.to_atom(module_name), []} | ast]} + + ["fn", fn_name | rest] -> + %{state | ast: [{:function, String.to_atom(fn_name), []} | ast]} + + expr -> + expr |> IO.inspect(label: "lyt expression") + %{state | ast: [expr |> Enum.join(" ") | ast]} + end + + build_ast(rest, state) + end + + def ast_to_elixir([], state), + do: state.elixir_code |> Enum.filter(&(String.trim(&1) != "")) |> Enum.join("\n") + + def ast_to_elixir([leaf | rest], state \\ %{elixir_code: []}) do + child = fn {head, tail}, state -> [head | ast_to_elixir(rest, state)] ++ [tail] end + + case leaf do + :global -> child.({"", ""}, state) + {:module, mod, []} -> child.({"defmodule #{to_string(mod)} do", "end"}, state) + {:fn, fname, []} -> child.({"def #{to_string(fname)} do", "end"}, state) + expr when is_binary(expr) -> [expr] + end + end + + def split_lines(s) do + String.split(s, "\n", trim: true) + end + + def tokenize(s) do + Regex.split(~r/\s/, s, true) + end +end diff --git a/test/lytlang_test.exs b/test/lytlang_test.exs index 779aee4..d376b88 100644 --- a/test/lytlang_test.exs +++ b/test/lytlang_test.exs @@ -1,27 +1,9 @@ defmodule LytlangTest do use ExUnit.Case doctest Lytlang + doctest Lytlang.Transpiler test "greets the world" do assert Lytlang.hello() == :world end - - test "transpiles module" do - input = """ - !mod EmptyModule - """ - - output = """ - defmodule EmptyModule - end - """ - - assert Lytlang.transpile_block(input) == output - end - - @tag :skip - test "transpiles example tiny module file correctly" do - output = File.read!("./test/fixtures/tiny.exs") - assert Lytlang.transpile_file("./test/fixtures/tiny.lyt") == output - end end