2019-05-11 14:00:46 -05:00
|
|
|
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] ->
|
2019-06-16 00:36:12 -05:00
|
|
|
%{state | ast: [{:module, module_name, []} | ast]}
|
2019-05-11 14:00:46 -05:00
|
|
|
|
|
|
|
["fn", fn_name | rest] ->
|
2019-06-16 00:36:12 -05:00
|
|
|
%{state | ast: [{:function, fn_name, []} | ast]}
|
2019-05-11 14:00:46 -05:00
|
|
|
|
|
|
|
expr ->
|
|
|
|
expr |> IO.inspect(label: "lyt expression")
|
|
|
|
%{state | ast: [expr |> Enum.join(" ") | ast]}
|
|
|
|
end
|
|
|
|
|
|
|
|
build_ast(rest, state)
|
|
|
|
end
|
|
|
|
|
2019-06-16 00:55:27 -05:00
|
|
|
def ast_to_elixir(s, state \\ %{elixir_code: []})
|
|
|
|
|
2019-05-11 14:00:46 -05:00
|
|
|
def ast_to_elixir([], state),
|
|
|
|
do: state.elixir_code |> Enum.filter(&(String.trim(&1) != "")) |> Enum.join("\n")
|
|
|
|
|
2019-06-16 00:55:27 -05:00
|
|
|
def ast_to_elixir([leaf | rest], state) do
|
2019-05-11 14:00:46 -05:00
|
|
|
child = fn {head, tail}, state -> [head | ast_to_elixir(rest, state)] ++ [tail] end
|
|
|
|
|
|
|
|
case leaf do
|
|
|
|
:global -> child.({"", ""}, state)
|
2019-06-16 00:36:12 -05:00
|
|
|
{:module, mod, []} -> child.({"defmodule #{mod} do", "end"}, state)
|
|
|
|
{:fn, fname, []} -> child.({"def #{fname} do", "end"}, state)
|
2019-05-11 14:00:46 -05:00
|
|
|
expr when is_binary(expr) -> [expr]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def split_lines(s) do
|
|
|
|
String.split(s, "\n", trim: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
def tokenize(s) do
|
2019-06-16 00:36:12 -05:00
|
|
|
Regex.split(~r/\s/, s)
|
2019-05-11 14:00:46 -05:00
|
|
|
end
|
|
|
|
end
|