WIP STUFF

This commit is contained in:
Daniel Flanagan 2019-11-15 00:51:27 -06:00
parent de8c3d620a
commit afc0738a31
10 changed files with 247 additions and 168 deletions

2
.gitignore vendored
View file

@ -30,4 +30,4 @@ lytlang-*.tar
*.swp *.swp
# Ignore compiled Erlang files # Ignore compiled Erlang files
/src/lytlang.erl /src/*.erl

View file

@ -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

View file

@ -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)
## Examples
iex> Lytlang.echo("world")
"world"
"""
def echo(s) do
s
end end
def tokenize(string) do
{:ok, tokens, _} =
:lytlang_lexer.string(string)
|> IO.inspect()
tokens
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 end
"""
-module(elixir).
-export([eval/1, from_elixir/1, from_erlang/1]).
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.
"""

View file

@ -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

View file

@ -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

View file

@ -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
View 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
View 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)).

View file

@ -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]]
import Plug.Conn !module
# uses project's name as module if not module name specified
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 []`

View file

@ -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