WIP STUFF
This commit is contained in:
parent
de8c3d620a
commit
afc0738a31
10 changed files with 247 additions and 168 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -30,4 +30,4 @@ lytlang-*.tar
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
# Ignore compiled Erlang files
|
# Ignore compiled Erlang files
|
||||||
/src/lytlang.erl
|
/src/*.erl
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# Lytlang
|
# Lytlang
|
||||||
|
|
||||||
Lytlang's goal is to enable you to write Elixir code more tersely and is itself
|
Lytlang is an opinionated way to write more readable Elixir code.
|
||||||
written in Elixir. Its syntax is a terrible merging of Elm, CoffeeScript,
|
|
||||||
Elixir, a sprinkling of Rust, and some made-up crap. Here's a rough preview:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
!module Leetcode
|
!module Leetcode
|
||||||
|
|
|
@ -1,31 +1,83 @@
|
||||||
defmodule Lytlang do
|
defmodule Lytlang do
|
||||||
@moduledoc """
|
def eval(string) do
|
||||||
Documentation for Lytlang.
|
{value, _} =
|
||||||
"""
|
string
|
||||||
|
|> from_lytlang()
|
||||||
|
|> IO.inspect()
|
||||||
|
|> Code.eval_quoted()
|
||||||
|
|> IO.inspect()
|
||||||
|
|
||||||
@doc """
|
value
|
||||||
Hello world.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
iex> Lytlang.hello()
|
|
||||||
:world
|
|
||||||
|
|
||||||
"""
|
|
||||||
def hello do
|
|
||||||
:world
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
def from_lytlang(string, binding \\ [], opts \\ []) do
|
||||||
Echo.
|
string |> String.to_charlist() |> tokenize() |> parse() |> transform(binding, opts)
|
||||||
|
end
|
||||||
|
|
||||||
## Examples
|
def tokenize(string) do
|
||||||
|
{:ok, tokens, _} =
|
||||||
|
:lytlang_lexer.string(string)
|
||||||
|
|> IO.inspect()
|
||||||
|
|
||||||
iex> Lytlang.echo("world")
|
tokens
|
||||||
"world"
|
end
|
||||||
|
|
||||||
|
def parse(tokens) do
|
||||||
|
{:ok, tree} =
|
||||||
|
:lytlang_parser.parse(tokens)
|
||||||
|
|> IO.inspect()
|
||||||
|
|
||||||
|
tree
|
||||||
|
end
|
||||||
|
|
||||||
|
def transform(ast, binding \\ [], opts \\ [])
|
||||||
|
|
||||||
|
def transform([] = _expr_list, _binding, _opts), do: []
|
||||||
|
|
||||||
|
def transform([expr | rest] = _expr_list, binding, opts) do
|
||||||
|
[transform(expr, binding, opts) | transform(rest, binding, opts)]
|
||||||
|
end
|
||||||
|
|
||||||
|
def transform({:binary_op, _line, op, left, right}, binding, _) do
|
||||||
|
{op, binding, [transform(left), transform(right)]}
|
||||||
|
end
|
||||||
|
|
||||||
|
def transform({:unary_op, line, op, left}, _, _) do
|
||||||
|
{:op, line, op, transform(left)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def transform({:integer, _, n} = _expr, _, _), do: n
|
||||||
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def echo(s) do
|
-module(elixir).
|
||||||
s
|
-export([eval/1, from_elixir/1, from_erlang/1]).
|
||||||
end
|
|
||||||
end
|
eval(String) ->
|
||||||
|
{value, Value, _} = erl_eval:expr(from_elixir(String), []),
|
||||||
|
Value.
|
||||||
|
|
||||||
|
% Temporary to aid debugging
|
||||||
|
from_elixir(String) ->
|
||||||
|
transform(parse(String)).
|
||||||
|
|
||||||
|
% Temporary to aid debugging
|
||||||
|
from_erlang(String) ->
|
||||||
|
{ok, Tokens, _} = erl_scan:string(String),
|
||||||
|
{ok, [Form]} = erl_parse:parse_exprs(Tokens),
|
||||||
|
Form.
|
||||||
|
|
||||||
|
parse(String) ->
|
||||||
|
{ok, Tokens, _} = elixir_lexer:string(String),
|
||||||
|
{ok, ParseTree} = elixir_parser:parse(Tokens),
|
||||||
|
ParseTree.
|
||||||
|
|
||||||
|
transform({ binary_op, Line, Op, Left, Right }) ->
|
||||||
|
{op, Line, Op, transform(Left), transform(Right)};
|
||||||
|
|
||||||
|
transform({ unary_op, Line, Op, Right }) ->
|
||||||
|
{op, Line, Op, transform(Right)};
|
||||||
|
|
||||||
|
|
||||||
|
transform({ integer, _, _ } = Expr) -> Expr.
|
||||||
|
"""
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
defmodule Lytlang.Parser do
|
|
||||||
@doc """
|
|
||||||
Attempts to tokenize an input string to start_tag, end_tag, and char
|
|
||||||
"""
|
|
||||||
@spec parse(binary) :: list
|
|
||||||
def parse(input) do
|
|
||||||
{:ok, tokens, _} = input |> to_char_list |> :lytlang.string()
|
|
||||||
tokens
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,95 +0,0 @@
|
||||||
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, module_name, []} | ast]}
|
|
||||||
|
|
||||||
["fn", fn_name | rest] ->
|
|
||||||
%{state | ast: [{:function, 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(s, state \\ %{elixir_code: []})
|
|
||||||
|
|
||||||
def ast_to_elixir([], state),
|
|
||||||
do: state.elixir_code |> Enum.filter(&(String.trim(&1) != "")) |> Enum.join("\n")
|
|
||||||
|
|
||||||
def ast_to_elixir([leaf | rest], state) do
|
|
||||||
child = fn {head, tail}, state -> [head | ast_to_elixir(rest, state)] ++ [tail] end
|
|
||||||
|
|
||||||
case leaf do
|
|
||||||
:global -> child.({"", ""}, state)
|
|
||||||
{:module, mod, []} -> child.({"defmodule #{mod} do", "end"}, state)
|
|
||||||
{:fn, fname, []} -> child.({"def #{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)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,16 +0,0 @@
|
||||||
Definitions.
|
|
||||||
|
|
||||||
I = \t
|
|
||||||
MODULE_DECL = module\s[a-zA-Z][a-zA-Z0-9_]*
|
|
||||||
FILE_LEVEL_MODULE_DECL = !module\s([a-zA-Z][a-zA-Z0-9_]*)
|
|
||||||
NUMBER = [0-9]+
|
|
||||||
|
|
||||||
Rules.
|
|
||||||
|
|
||||||
%% number
|
|
||||||
{NUMBER} : {token, { number, TokenLine, list_to_integer(TokenChars) } }.
|
|
||||||
{MODULE_DECL} : {token, { module_decl, TokenLine, TokenChars } }.
|
|
||||||
{FILE_LEVEL_MODULE_DECL} : {token, { file_level_module_decl, TokenLine, TokenChars } }.
|
|
||||||
[\s\n\r\t]+ : skip_token.
|
|
||||||
|
|
||||||
Erlang code.
|
|
44
src/lytlang_lexer.xrl
Normal file
44
src/lytlang_lexer.xrl
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
% Lexer syntax for the Elixir language done with leex
|
||||||
|
% Copyright (C) 2011 Jose Valim
|
||||||
|
|
||||||
|
Definitions.
|
||||||
|
|
||||||
|
D = [0-9]
|
||||||
|
U = [A-Z]
|
||||||
|
L = [a-z]
|
||||||
|
WS = [\s]
|
||||||
|
|
||||||
|
Rules.
|
||||||
|
|
||||||
|
{D}+\.{D}+ : { token, { float, TokenLine, list_to_float(TokenChars) } }.
|
||||||
|
{D}+ : { token, { integer, TokenLine, list_to_integer(TokenChars) } }.
|
||||||
|
({L}|_)({U}{L}{D}|_)* : { token, var(TokenChars, TokenLine) }.
|
||||||
|
|
||||||
|
\+ : { token, { '+', TokenLine } }.
|
||||||
|
- : { token, { '-', TokenLine } }.
|
||||||
|
\* : { token, { '*', TokenLine } }.
|
||||||
|
/ : { token, { '/', TokenLine } }.
|
||||||
|
\( : { token, { '(', TokenLine } }.
|
||||||
|
\) : { token, { ')', TokenLine } }.
|
||||||
|
= : { token, { '=', TokenLine } }.
|
||||||
|
-> : { token, { '->', TokenLine } }.
|
||||||
|
; : { token, { ';', TokenLine } }.
|
||||||
|
|
||||||
|
{Comment} : skip_token.
|
||||||
|
{WS}+ : skip_token.
|
||||||
|
% ({Comment}|{Whitespace})*(\n({Comment}|{Whitespace})*)+ : { token, { eol, TokenLine } }.
|
||||||
|
|
||||||
|
Erlang code.
|
||||||
|
|
||||||
|
var(Chars, Line) ->
|
||||||
|
Atom = list_to_atom(Chars),
|
||||||
|
case reserved_word(Atom) of
|
||||||
|
true -> {Atom, Line};
|
||||||
|
false -> {var, Line, Atom}
|
||||||
|
end.
|
||||||
|
|
||||||
|
reserved_word('nil') -> true;
|
||||||
|
reserved_word('true') -> true;
|
||||||
|
reserved_word('false') -> true;
|
||||||
|
reserved_word('module') -> true;
|
||||||
|
reserved_word(_) -> false.
|
90
src/lytlang_parser.yrl
Normal file
90
src/lytlang_parser.yrl
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
Nonterminals
|
||||||
|
grammar
|
||||||
|
expr_list
|
||||||
|
expr
|
||||||
|
assign_expr
|
||||||
|
add_expr
|
||||||
|
mult_expr
|
||||||
|
unary_expr
|
||||||
|
fun_expr
|
||||||
|
body
|
||||||
|
stabber
|
||||||
|
max_expr
|
||||||
|
number
|
||||||
|
unary_op
|
||||||
|
add_op
|
||||||
|
mult_op
|
||||||
|
.
|
||||||
|
|
||||||
|
Terminals
|
||||||
|
var float integer eol
|
||||||
|
'+' '-' '*' '/' '(' ')' '=' '->'
|
||||||
|
.
|
||||||
|
|
||||||
|
Rootsymbol grammar.
|
||||||
|
|
||||||
|
grammar -> expr_list : '$1'.
|
||||||
|
grammar -> '$empty' : [].
|
||||||
|
|
||||||
|
expr_list -> eol : [].
|
||||||
|
expr_list -> expr : ['$1'].
|
||||||
|
expr_list -> expr eol : ['$1'].
|
||||||
|
expr_list -> eol expr_list : '$2'.
|
||||||
|
expr_list -> expr eol expr_list : ['$1'|'$3'].
|
||||||
|
|
||||||
|
expr -> assign_expr : '$1'.
|
||||||
|
|
||||||
|
assign_expr -> add_expr '=' assign_expr :
|
||||||
|
{ match, ?line('$2'), '$1', '$3' }.
|
||||||
|
|
||||||
|
assign_expr -> add_expr : '$1'.
|
||||||
|
|
||||||
|
%% Arithmetic operations
|
||||||
|
add_expr -> add_expr add_op mult_expr :
|
||||||
|
{ binary_op, ?line('$1'), ?op('$2'), '$1', '$3' }.
|
||||||
|
|
||||||
|
add_expr -> mult_expr : '$1'.
|
||||||
|
|
||||||
|
mult_expr -> mult_expr mult_op unary_expr :
|
||||||
|
{ binary_op, ?line('$1'), ?op('$2'), '$1', '$3' }.
|
||||||
|
|
||||||
|
mult_expr -> unary_expr : '$1'.
|
||||||
|
|
||||||
|
unary_expr -> unary_op max_expr :
|
||||||
|
{ unary_op, ?line('$1'), ?op('$1'), '$2' }.
|
||||||
|
|
||||||
|
unary_expr -> max_expr : '$1'.
|
||||||
|
|
||||||
|
fun_expr -> stabber eol body :
|
||||||
|
{ 'fn', ?line('$1')
|
||||||
|
, { clauses, [ { clause, ?line('$1'), [], [], '$3' } ] }
|
||||||
|
}.
|
||||||
|
|
||||||
|
fun_expr -> max_expr : '$1'.
|
||||||
|
|
||||||
|
%% Minimum expressions
|
||||||
|
max_expr -> number : '$1'.
|
||||||
|
max_expr -> '(' expr ')' : '$2'.
|
||||||
|
|
||||||
|
%% Numbers
|
||||||
|
number -> float : '$1'.
|
||||||
|
number -> integer : '$1'.
|
||||||
|
|
||||||
|
%% Unary operator
|
||||||
|
unary_op -> '+' : '$1'.
|
||||||
|
unary_op -> '-' : '$1'.
|
||||||
|
|
||||||
|
%% Addition operators
|
||||||
|
add_op -> '+' : '$1'.
|
||||||
|
add_op -> '-' : '$1'.
|
||||||
|
|
||||||
|
%% Multiplication operators
|
||||||
|
mult_op -> '*' : '$1'.
|
||||||
|
mult_op -> '/' : '$1'.
|
||||||
|
|
||||||
|
Erlang code.
|
||||||
|
|
||||||
|
-define(op(Node), element(1, Node)).
|
||||||
|
-define(line(Node), element(2, Node)).
|
||||||
|
-define(char(Node), element(3, Node)).
|
||||||
|
|
|
@ -1,24 +1,28 @@
|
||||||
mod SimpleApp
|
!project SimpleApp simple_app
|
||||||
entry
|
# should generate a SimpleApp.MixProject_ module
|
||||||
:project
|
version: "0.1.0"
|
||||||
deps:
|
elixir: "~> 1.8"
|
||||||
{:cowboy, "~> 2.6"}
|
lytl: "~> 0.1"
|
||||||
{:plug, "~> 1.8"}
|
start_permanent: Mix.env() == prod
|
||||||
{:plug_cowboy, "~> 2.0"}
|
extra_applications: [logger]
|
||||||
:app
|
deps: []
|
||||||
[extra_applications: [:logger]]
|
|
||||||
|
|
||||||
|
!module
|
||||||
|
# uses project's name as module if not module name specified
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
fn init default_options
|
init = opts -> IO.puts "initializing plug"; opts
|
||||||
IO.puts "initializing plug"
|
|
||||||
default_options
|
|
||||||
|
|
||||||
fn call conn:Plug.Conn.t() _options = []
|
call = conn _options = [] ->
|
||||||
IO.puts "calling plug"
|
IO.puts "calling plug"
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type "text/plain"
|
|> put_resp_content_type "text/plain"
|
||||||
|> send_resp 200 "Hello world"
|
|> send_resp 200 "Hello world"
|
||||||
|
|
||||||
|
long_fn =
|
||||||
|
arg1
|
||||||
|
arg2
|
||||||
|
arg3
|
||||||
|
->
|
||||||
|
|
||||||
# in lytx run `Plug.Adapters.Cowboy.http SimpleApp []`
|
# in lytx run `Plug.Adapters.Cowboy.http SimpleApp []`
|
||||||
|
|
|
@ -1,9 +1,21 @@
|
||||||
defmodule LytlangTest do
|
defmodule LytlangTest do
|
||||||
use ExUnit.Case
|
use ExUnit.Case
|
||||||
doctest Lytlang
|
doctest Lytlang
|
||||||
doctest Lytlang.Transpiler
|
|
||||||
|
|
||||||
test "greets the world" do
|
test "arithmetic" do
|
||||||
assert Lytlang.hello() == :world
|
[
|
||||||
|
{"2 + 3 + 8", 13},
|
||||||
|
{"2 * 3 + 8", 14},
|
||||||
|
{"2 + 3 * 8", 26},
|
||||||
|
{"(2 + 3) * 8", 40},
|
||||||
|
{"2 * (3 + 8)", 22},
|
||||||
|
{"2 + (3 * 8)", 26},
|
||||||
|
{"8 - 3", 5},
|
||||||
|
{"8 / 4", 2.0},
|
||||||
|
{"2 + 3", 5}
|
||||||
|
]
|
||||||
|
|> Enum.map(fn {string, val} ->
|
||||||
|
assert Lytlang.eval(string) == val
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue