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