diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e98c57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.eunit/ +deps/ +src/eml_lexer.erl +src/eml_parser.erl +*~ +ebin/*.beam +ebin/*.app +examples/*.erl +examples/*.beam +examples/*.compile +examples/*.parse diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6864788 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +SHELL=/bin/bash +ERL ?= erl +APP := eml + +.PHONY: deps + +all: deps + @./rebar compile + +deps: + @./rebar get-deps + +examples: all + @erl -pa ./ebin -noshell -s eml compile_examples -s init stop + @(cd examples; erlc *.erl) + +clean: + @./rebar clean + @rm -f examples/*.erl examples/*.beam + +distclean: clean + @./rebar delete-deps + +test: local_clean + @./rebar eunit + +ct: all + @./rebar -C rebar.config.test ct + +ct2: all + @./rebar -C rebar.config.test ct + +ctv: all + @./rebar -C rebar.config.test ct verbose=1 + +local_clean: + @rm -f ./ebin/* .eunit/* + +xref: all + @./rebar xref + +docs: + @erl -noshell -run edoc_run application '$(APP)' '"."' '[]' diff --git a/ebin/.gitignore b/ebin/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/examples/add.eml b/examples/add.eml new file mode 100644 index 0000000..2bf2f8b --- /dev/null +++ b/examples/add.eml @@ -0,0 +1 @@ +fun add X Y = X + Y; diff --git a/examples/anon.eml b/examples/anon.eml new file mode 100644 index 0000000..87693bf --- /dev/null +++ b/examples/anon.eml @@ -0,0 +1,10 @@ +fun double L = + let val Mult2 = fn X => X * 2 + in map(Mult2,L); + +fun map F [H|T] = + let val Hd = F(H) + val Tl = map(F,T) + in + [Hd|Tl] + | map _ [] = []; diff --git a/examples/case.eml b/examples/case.eml new file mode 100644 index 0000000..8cd6f3d --- /dev/null +++ b/examples/case.eml @@ -0,0 +1,4 @@ +fun is_even X = + case X rem 2 of + 0 => true + | _ => false; diff --git a/examples/let.eml b/examples/let.eml new file mode 100644 index 0000000..f3eb118 --- /dev/null +++ b/examples/let.eml @@ -0,0 +1,6 @@ + +fun split X = + let val Even = [Y || Y <- X, (Y rem 2) == 0] + val Odd = [Y || Y <- X, (Y rem 2) =/= 0] + in + {Even,Odd}; diff --git a/examples/let2.eml b/examples/let2.eml new file mode 100644 index 0000000..c9d3b6e --- /dev/null +++ b/examples/let2.eml @@ -0,0 +1,4 @@ +fun foo X Y = + let val Z = X + Y + 4 + val W = X - Y + in {Z,W}; diff --git a/examples/let3.eml b/examples/let3.eml new file mode 100644 index 0000000..a2c654b --- /dev/null +++ b/examples/let3.eml @@ -0,0 +1,5 @@ +fun foo X = + let val Z = X + 1 + val Y = Z + X + 1 + in Y; + diff --git a/examples/letrec.eml b/examples/letrec.eml new file mode 100644 index 0000000..f00a07f --- /dev/null +++ b/examples/letrec.eml @@ -0,0 +1,4 @@ +fun foo L = + let rec Len = fn [] => 0 | fn [_|T] => 1 + Len(T) + in Len(L); + diff --git a/examples/letrec2.eml b/examples/letrec2.eml new file mode 100644 index 0000000..ebd6fba --- /dev/null +++ b/examples/letrec2.eml @@ -0,0 +1,6 @@ +fun foo = + let val X = [1,2,3] + val Y = [a,b,c] + rec Zip = fn [] [] => [] | fn [A|B] [H|T] => [{A,H} | Zip(B,T)] + in Zip(X,Y); + diff --git a/examples/qsort.eml b/examples/qsort.eml new file mode 100644 index 0000000..c9f4075 --- /dev/null +++ b/examples/qsort.eml @@ -0,0 +1,6 @@ +fun qsort [H|T] = + let val GrEq = qsort([X || X <- T, X >= H]) + val Le = qsort([X || X <- T, X < H]) + in + GrEq ++ [H] ++ Le + | qsort [] = []; diff --git a/include/eml.hrl b/include/eml.hrl new file mode 100644 index 0000000..e69de29 diff --git a/rebar b/rebar new file mode 100755 index 0000000..61e776f Binary files /dev/null and b/rebar differ diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..81ca4e2 --- /dev/null +++ b/rebar.config @@ -0,0 +1,16 @@ +%%-*- mode: erlang -*- + +{deps_dir, ["deps"]}. + +{deps, [ + {eper, "0.*", + {git, "git://github.com/massemanet/eper.git", + "HEAD"}} + ]}. + +%% Erlang compiler options +%{erl_opts, [{i, "include"}]}. + +%% Add this to a projects rebar.config which is using eml +%{plugins, [ rebar_eml_plugin ]}. +%{plugin_dir, "deps/rebar_eml_plugin/src"}. diff --git a/src/eml.app.src b/src/eml.app.src new file mode 100644 index 0000000..a846cfd --- /dev/null +++ b/src/eml.app.src @@ -0,0 +1,5 @@ +{application, eml, [ + {description, "Erlang flavored by some ML."}, + {vsn, "0.1.0"}, + {env, []} +]}. diff --git a/src/eml.erl b/src/eml.erl new file mode 100644 index 0000000..efa6529 --- /dev/null +++ b/src/eml.erl @@ -0,0 +1,209 @@ +%% ------------------------------------------------------------------- +%% Created: 22 Dec 2011 by etnt@redhoterlang.com +%% +%% @doc Erlang flavoured by Some ML +%% +%% ------------------------------------------------------------------- +-module(eml). + +-export([c/1 + , compiler/1 + , compile_examples/0 + , compile_file/1 + , compile_file/2 + , e/1 + , f/1 + , l/1 + , lexer/1 + , p/1 + , parse/1 + , parser/1 + , typecheck/1 + ]). + +-include("eml.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +compile_examples() -> + Files = string:tokens(os:cmd("ls examples/*.eml"), "\n"), + F = fun(File) -> + {ok,_} = compile_file(File), + io:format("Compiled file ~s~n",[File]) + end, + [F(File) || File <- Files]. + +compile_file(FileName) -> + compile_file(FileName, []). + +compile_file(FileName, Opts) -> + ModName = filename:basename(FileName,".eml"), + DirName = filename:dirname(FileName), + {ok,EmlForms} = parse(FileName), + dump(FileName++".parse", EmlForms, Opts), + CurryForms = eml_compile:curry(EmlForms), + {ok,ErlForms} = compiler(CurryForms), + dump(FileName++".compile", ErlForms, Opts), + String = to_erl(ModName,ErlForms), + file:write_file(filename:join([DirName,ModName++".erl"]), + list_to_binary(String)), + compile:file(filename:join([DirName,ModName]), Opts ++ [{outdir,DirName}]). + +to_erl(Module,Forms) when is_list(Module) -> + Mx = erl_syntax:attribute(erl_syntax:atom("module"), + [erl_syntax:atom(Module)]), + + Es = [erl_syntax:arity_qualifier( + erl_syntax:atom(FunName), + erl_syntax:integer(Arity)) + || {function,_,FunName,Arity,_} <- Forms], + + Ex = erl_syntax:attribute( + erl_syntax:atom("export"), + [erl_syntax:list(Es)]), + + erl_prettypr:format(erl_syntax:form_list([Mx,Ex]++Forms)). + + +dump(FileName, Data, Opts) -> + case lists:keyfind(verbose,1,Opts) of + {verbose,true} -> + {ok,Fd} = file:open(FileName, write), + io:format(Fd, "~p~n", [Data]), + file:close(Fd); + _ -> + false + end. + + +f(FileName) -> + {ok,Forms} = parse(FileName), + eml_compile:curry(Forms). + +c(S) -> e2(compiler(e2(parser(e2(lexer(S)))))). +e(S) -> eml_compile:erl_form(S). +p(S) -> parser(e2(lexer(S))). +l(S) -> lexer(S). + + +lexer(String) when is_list(String) -> + eml_lexer:string(String). + +parser(Tokens) when is_list(Tokens) -> + eml_parser:parse(Tokens). + +typecheck(ParseTree) -> + eml_typecheck:run(ParseTree). + +compiler(ParseTree) -> + eml_compile:run(ParseTree). + + +parse(FileName) -> + {ok, InFile} = file:open(FileName, [read]), + Acc = loop(InFile,[]), + file:close(InFile), + eml_parser:parse(Acc). + +loop(InFile,Acc) -> + case io:request(InFile,{get_until,prompt,eml_lexer,token,[1]}) of + {ok,Token,_EndLine} -> + loop(InFile,Acc ++ [Token]); + {error,token} -> + exit(scanning_error); + {eof,_} -> + Acc + end. + + + +-ifdef(EUNIT). + +compiler_test_() -> + [ + ?_assertEqual([e("add1(X) -> X + 1.")], + c("fun add1 X = X + 1;")) + + ,?_assertEqual([e("len([H|T]) -> 1 + len(T);\nlen([]) -> 0.")], + c("fun len [H|T] = 1 + len(T)\n| len [] = 0;")) + + ,?_assertEqual([e("add3(Y) -> fun (X) -> X + Y end(3).")], + c("fun add3 Y = let val X = 3 in X + Y;")) + + ,?_assertEqual([e("expr(Y) -> ((fun (X) -> fun (Z) -> X*Y+Z end end)(2))(1).")], + c("fun expr Y = let val X = 2 val Z = 1 in X*Y+Z;")) + + ,?_assertEqual([e("add(Y) -> (fun ({X,Z}) -> X + Z end)(Y).")], + c("fun add Y = let val {X,Z} = Y in X + Z;")) + + ,?_assertEqual([e("add(X,Y) -> X + Y.")], + c("fun add X Y = X + Y;")) + + ,?_assertEqual([e("qsort([H | T]) -> (fun (GrEq) -> fun (Le) -> GrEq ++ [H] ++ Le end end(qsort([X || X <- T, X >= H])))(qsort([X || X <- T, X < H])); qsort([]) -> [].")], + c("fun qsort [H|T] = let val GrEq = qsort([X || X <- T, X >= H]) val Le = qsort([X || X <- T, X < H]) in GrEq ++ [H] ++ Le | qsort [] = [];")) + + ,?_assertEqual([eml:e("add(X) -> fun(Y) -> X + Y end.")], + eml:c("fun add X = fn Y => X + Y;")) + + + ]. + + +parser_test_() -> + [ + + +% ?_assertMatch({ok, +% {function,1,len,1, +% [{clause,1, +% [{cons,1,{var,1,'H'},{var,1,'T'}}], +% [], +% [{op,1,'+', +% {integer,1,1}, +% {call,1,[],len,[{var,1,'T'}]}}]}, +% {clause,2,[{nil,2}],[],[{integer,2,0}]}]}}, +% +% p("fun len [H|T] = 1 + len T\n| len [] = 0;")) + + +% ,?_assertMatch({ok,{function,1,add,1, +% [{clause,2, +% [{var,1,'X'}], +% [], +% [{'let',2, +% [{val,2,{var,2,'Y'},{integer,2,2}}], +% [{op,2,'+', +% {var,2,'X'}, +% {var,2,'Y'}}]}]}]}}, +% +% p("fun add X =\n let val Y = 2 in X + Y;")) + + ]. + +lexer_test_() -> + [ + ?_assertMatch({ok,[{atom,1,len}, + {'[',1}, + {atom,1,h}, + {'|',1}, + {atom,1,t}, + {']',1}, + {'=',1}, + {integer,1,1}, + {'+',1}, + {atom,1,len}, + {atom,1,t}, + {atom,2,len}, + {'[',2}, + {']',2}, + {'=',2}, + {integer,2,0}], + 2}, + lexer("len [h|t] = 1 + len t\nlen [] = 0")) + + + ]. + +e2(T) when is_tuple(T) -> element(2, T). + + +-endif. diff --git a/src/eml_compile.erl b/src/eml_compile.erl new file mode 100644 index 0000000..88aa8d1 --- /dev/null +++ b/src/eml_compile.erl @@ -0,0 +1,214 @@ +%% ------------------------------------------------------------------- +%% Created: 22 Dec 2011 by etnt@redhoterlang.com +%% +%% @doc The compiler +%% +%% ------------------------------------------------------------------- + +-module(eml_compile). + +-export([curry/1 + , run/1 + , print/1 + , erl_form/1 + ]). + +-include("eml.hrl"). + +-define(e, erl_syntax). + + + +erl_form(ErlStr) -> + {ok,Form} = erl_parse:parse_form(element(2,erl_scan:string(ErlStr))), + Form. + + +%%8> erl_parse:parse_form(element(2,erl_scan:string("add1(X) -> X + 1."))) +%%. +%%{ok,{function,1,add1,1, +%% [{clause,1, +%% [{var,1,'X'}], +%% [], +%% [{op,1,'+',{var,1,'X'},{integer,1,1}}]}]}} +%% +%%9> element(2,eml:p("fun add1 X = X + 1;")). +%%{function,1,add1,1, +%% [{clause,1, +%% [{var,1,'X'}], +%% [], +%% [{arith,1,'+',{var,1,'X'},{integer,1,1}}]}]} + + +print(Forms) -> + X = erl_prettypr:format(?e:form_list([?e:revert(Forms)]), + [{paper,160},{ribbon,80}]), + io:format("~p~n",[X]). + + + +run(ParseTrees) when is_list(ParseTrees) -> + {ok, [r(ParseTree) || ParseTree <- ParseTrees]}. + +r({function,Line,Name,Arity,Clauses}) -> + put(used_vars, vars(Clauses)), + {function,Line,Name,Arity,[r(C) || C <- Clauses]}; + +r({'fun',Line,{clauses,Clauses}}) -> + {'fun',Line,{clauses,[r(C) || C <- Clauses]}}; + +r({clause,Line,FormalArgs,Guards,Exprs}) -> + {clause,Line,FormalArgs,Guards,[r(E) || E <- Exprs]}; + +r({'let',Line,[{val,_Line,FormalArg,ActualArg}|E],Exprs}) -> + %% + %% We transform a let as described in SPJ: + %% + %% (let v1 = B1, v2 = B2 in E) == + %% (let v1 = B1 + %% in let v2 = B2 in E) == + %% (let v1 = B1 in E' == ((\v1.E')B1) , E' == (let v2 = B2 in E) + %% + {call,Line, + {'fun',Line, + {clauses, + [{clause,Line,[FormalArg],[], %% r(FormalArg1) ?? + return(r({'let',Line,E,Exprs}))}]}}, + [r(ActualArg)]}; + +r({'let',Line,[{rec,_Line,Var,FunExpr}|E],Exprs}) -> + %% + %% We transform a 'let rec' to a let + the use of the Y combinator + %% as described in SPJ: + %% + %% (let rec v = B in E) == , B= + %% let val Y = + %% in let val v = Y(\v.B) in E) + %% + Yvar = get_non_used_var(), + Arity = arity(FunExpr), + Vs = [y(Line,Yvar,Arity), + {val, Line, Var, + {call, Line, {var,Line,Yvar}, + [{'fun', Line, + {clauses, + [{clause, Line, [Var],[], [FunExpr]}]}}]}} + |E], + r({'let',Line,Vs,Exprs}); + +r({'let',_Line,[],Exprs}) -> + [r(E) || E <- Exprs]; + + + +r({'case', Line, Expr, Clauses}) -> + %% + %% We transform case expressions as: + %% + %% (case E of P1 => B1 | P2 => B2 end) == + %% (let v0 = E in (fn P1 = B1 | fn P2 = B2)(v0)) + %% + Var = get_non_used_var(), + R = {'let', Line, + [{val, Line, {var, Line, Var}, Expr}], + [{call, Line, {'fun', Line, {clauses, Clauses}}, [{var, Line, Var}]}]}, + r(R); + +r(ParseTree) -> + ParseTree. + +%% Require ('used_vars',VarList) in process dictionary! +get_non_used_var() -> + true = erlang:is_list(get(used_vars)), % assert! + list_to_atom(get_non_used_var("_EML_", 1)). + +get_non_used_var(V,N) when is_list(V), is_integer(N) -> + Var = V ++ integer_to_list(N), + case lists:member(Var, get(used_vars)) of + false -> Var; + true -> get_non_used_var(V,N+1) + end. + +return(List) when is_list(List) -> List; +return(Term) -> [Term]. + + +%% FIXME: Get non-used variables in Var&FunBody +%% Also, only one Y function is required in case of multiple letrec's +%% +%% The Y-combinator: +%% +%% let val Y = +%% fn M => +%% (let val G = fn F => M(fn A => (F(F))(A)) +%% in G(G)) +%% +y(Line,Var, Arity) -> + Vs = [{var,Line,list_to_atom("A"++integer_to_list(I))} + || I <- lists:seq(1,Arity)], + {val,Line, + {var,Line,Var}, + {'fun',Line, + {clauses, + [{clause,Line, + [{var,Line,'M'}], + [], + [{'let',Line, + [{val,Line, + {var,Line,'G'}, + {'fun',Line, + {clauses, + [{clause,Line, + [{var,Line,'F'}], + [], + [{call,Line, + {var,Line,'M'}, + [{'fun',Line, + {clauses, + [{clause,Line,Vs,[], + [{call,Line, + {call,Line,{var,Line,'F'},[{var,Line,'F'}]}, + Vs}]}]}}]}]}]}}}], + [{call,Line,{var,Line,'G'},[{var,Line,'G'}]}]}]}]}}}. + + +%% Compute the arity of an anonymous function +arity({'fun',_,{clauses,[{clause,_,FormalArgs,_,_}|_]}}) -> length(FormalArgs). + +%%% Extract all variables used +vars(R) -> ordsets:to_list(vars(R,ordsets:new())). + +vars({var,_,Var}, Acc) -> [Var|Acc]; +vars(Tuple,Acc) when is_tuple(Tuple) -> + lists:foldl(fun(X,Acc1) -> ordsets:union(vars(X),Acc1) end, + Acc, tuple_to_list(Tuple)); +vars(List,Acc) when is_list(List) -> + lists:foldl(fun(X,Acc1) -> ordsets:union(vars(X),Acc1) end, + Acc, List); +vars(_,Acc) -> + Acc. + + +curry([H|T]) -> c([H]) ++ curry(T); +curry([]) -> []. + + +c([{function,Line,Name,Arity,_Clauses}=H|T]) when Arity > 0 -> + Vs = [{var,Line,l2a("X"++i2l(N))} || N <- lists:seq(1,Arity)], + [Last|RevFirst] = lists:reverse(Vs), + c([{function,Line,Name,Arity-1, + [{clause,Line,lists:reverse(RevFirst),[], + [{'fun',Line, + {clauses, + [{clause,Line, + [Last],[], + [{call,Line,{atom,1,Name},Vs}]}]}}]}]}, + H|T]); +c(L) -> + L. + +l2a(L) when is_list(L) -> list_to_atom(L). +i2l(I) when is_integer(I) -> integer_to_list(I). + +e3(T) -> element(3,T). +e4(T) -> element(4,T). diff --git a/src/eml_lexer.xrl b/src/eml_lexer.xrl new file mode 100644 index 0000000..b523b4e --- /dev/null +++ b/src/eml_lexer.xrl @@ -0,0 +1,177 @@ +%%% +%%% Slightly modified by etnt@redhoterlang.com +%%% +%%% File : erlang_scan.xrl +%%% Author : Robert Virding +%%% Purpose : Token definitions for Erlang. + +Definitions. +O = [0-7] +D = [0-9] +H = [0-9a-fA-F] +U = [A-Z] +L = [a-z] +A = ({U}|{L}|{D}|_|@) +WS = ([\000-\s]|%.*) + +Rules. +{D}+\.{D}+((E|e)(\+|\-)?{D}+)? : + {token,{float,TokenLine,list_to_float(TokenChars)}}. + +{D}+#{H}+ : base(TokenLine, TokenChars). + +{D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}. + +{L}{A}* : Atom = list_to_atom(TokenChars), + {token,case reserved_word(Atom) of + true -> {Atom,TokenLine}; + false -> {atom,TokenLine,Atom} + end}. + +'(\\\^.|\\.|[^'])*' : + %% Strip quotes. + S = lists:sublist(TokenChars, 2, TokenLen - 2), + case catch list_to_atom(string_gen(S)) of + {'EXIT',_} -> {error,"illegal atom " ++ TokenChars}; + Atom -> {token,{atom,TokenLine,Atom}} + end. + +({U}|_){A}* : {token,{var,TokenLine,list_to_atom(TokenChars)}}. + +"(\\\^.|\\.|[^"])*" : + %% Strip quotes. + S = lists:sublist(TokenChars, 2, TokenLen - 2), + {token,{string,TokenLine,string_gen(S)}}. + +\$(\\{O}{O}{O}|\\\^.|\\.|.) : + {token,{char,TokenLine,cc_convert(TokenChars)}}. + +-> : {token,{'->',TokenLine}}. + +:- : {token,{':-',TokenLine}}. + +\|\| : {token,{'||',TokenLine}}. + +<- : {token,{'<-',TokenLine}}. + +\+\+ : {token,{'++',TokenLine}}. + +-- : {token,{'--',TokenLine}}. + +=/= : {token,{'=/=',TokenLine}}. + +== : {token,{'==',TokenLine}}. + +=:= : {token,{'=:=',TokenLine}}. + +/= : {token,{'/=',TokenLine}}. + +>= : {token,{'>=',TokenLine}}. + +=< : {token,{'=<',TokenLine}}. + +<= : {token,{'<=',TokenLine}}. + +=> : {token,{'=>',TokenLine}}. + +<< : {token,{'<<',TokenLine}}. + +>> : {token,{'>>',TokenLine}}. + +:: : {token,{'::',TokenLine}}. + +[]()[}{|!?/;:,.*+#<>=-] : + {token,{list_to_atom(TokenChars),TokenLine}}. + +\.{WS} : {end_token,{dot,TokenLine}}. + +{WS}+ : skip_token. + +Erlang code. + +-export([reserved_word/1]). + +%% reserved_word(Atom) -> Bool +%% return 'true' if Atom is an Erlang reserved word, else 'false'. + +reserved_word('when') -> true; +reserved_word('let') -> true; +reserved_word('val') -> true; +reserved_word('rec') -> true; +reserved_word('in') -> true; +reserved_word('try') -> true; +reserved_word('catch') -> true; +reserved_word('andalso') -> true; +reserved_word('orelse') -> true; +reserved_word('fun') -> true; +reserved_word('fn') -> true; +reserved_word('case') -> true; +reserved_word('of') -> true; +reserved_word('end') -> true; +reserved_word('bnot') -> true; +reserved_word('not') -> true; +reserved_word('div') -> true; +reserved_word('rem') -> true; +reserved_word('band') -> true; +reserved_word('and') -> true; +reserved_word('bor') -> true; +reserved_word('bxor') -> true; +reserved_word('bsl') -> true; +reserved_word('bsr') -> true; +reserved_word('xor') -> true; +reserved_word(_) -> false. + +base(L, Cs) -> + H = string:chr(Cs, $#), + case list_to_integer(string:substr(Cs, 1, H-1)) of + B when B > 16 -> {error,"illegal base"}; + B -> + case base(string:substr(Cs, H+1), B, 0) of + error -> {error,"illegal based number"}; + N -> {token,{integer,L,N}} + end + end. + +base([C|Cs], Base, SoFar) when C >= $0, C =< $9, C < Base + $0 -> + Next = SoFar * Base + (C - $0), + base(Cs, Base, Next); +base([C|Cs], Base, SoFar) when C >= $a, C =< $f, C < Base + $a - 10 -> + Next = SoFar * Base + (C - $a + 10), + base(Cs, Base, Next); +base([C|Cs], Base, SoFar) when C >= $A, C =< $F, C < Base + $A - 10 -> + Next = SoFar * Base + (C - $A + 10), + base(Cs, Base, Next); +base([_|_], _, _) -> error; %Unknown character +base([], _, N) -> N. + +cc_convert([$$,$\\|Cs]) -> + hd(string_escape(Cs)); +cc_convert([$$,C]) -> C. + +string_gen([$\\|Cs]) -> + string_escape(Cs); +string_gen([C|Cs]) -> + [C|string_gen(Cs)]; +string_gen([]) -> []. + +string_escape([O1,O2,O3|S]) when + O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 -> + [(O1*8 + O2)*8 + O3 - 73*$0|string_gen(S)]; +string_escape([$^,C|Cs]) -> + [C band 31|string_gen(Cs)]; +string_escape([C|Cs]) when C >= $\000, C =< $\s -> + string_gen(Cs); +string_escape([C|Cs]) -> + [escape_char(C)|string_gen(Cs)]. + +escape_char($n) -> $\n; %\n = LF +escape_char($r) -> $\r; %\r = CR +escape_char($t) -> $\t; %\t = TAB +escape_char($v) -> $\v; %\v = VT +escape_char($b) -> $\b; %\b = BS +escape_char($f) -> $\f; %\f = FF +escape_char($e) -> $\e; %\e = ESC +escape_char($s) -> $\s; %\s = SPC +escape_char($d) -> $\d; %\d = DEL +escape_char(C) -> C. + \ No newline at end of file diff --git a/src/eml_parser.yrl b/src/eml_parser.yrl new file mode 100644 index 0000000..c66332a --- /dev/null +++ b/src/eml_parser.yrl @@ -0,0 +1,549 @@ +%% --------------------------------------------------------------------- +%% +%% Modified: 22 Dec 2011 by etnt@redhoterlang.com +%% +%% --------------------------------------------------------------------- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +Nonterminals +prefix_op add_op comp_op list_op +attribute atomic basic_type bif_test +clause_body +clause_guard clause_head +expr expr_100 expr_150 expr_160 expr_200 expr_300 expr_400 expr_500 +expr_600 expr_700 expr_800 expr_900 +expr_max expr_tail +exprs farity farity_list +forms form formal_parameter_list argument_list +case_expr cr_clauses cr_clause +function function_call function_clause +fun_expr fun_clauses fun_clause fun_clause_head fun_cr_clause_body +guard guard_call guard_expr +let_expr val_exprs val_expr +list_comprehension binary_comprehension +lc_exprs lc_expr binary bin_elements bin_element bit_expr +opt_bit_size_expr opt_bit_type_list bit_type_list bit_type bit_size_expr +guard_expr_list guard_exprs guard_expr_tail guard_expr_tuple +guard_parameter_list +guard_tests guard_test list +%if_clause if_clauses +mult_op +pattern patterns comma_patterns pattern_list pattern_tail pattern_tuple +tuple strings. + +Terminals +'(' ')' '*' '+' ',' '-' '/' '/=' ':' ';' '<' '=' '=/=' '=:=' +'<<' '>>' '<-' '<=' '=<' '==' '>' '>=' '[' ']' '.' 'band' 'bnot' +'fun' 'val' 'rec' +'bor' 'bsl' 'bsr' 'bxor' 'div' 'let' 'in' 'fn' '=>' +'case' 'of' +'orelse' 'andalso' 'not' 'and' 'or' 'xor' '++' '--' +'rem' '{' '|' '||' '}' 'when' atom float integer string var. + + +Rootsymbol forms. + +forms -> form : ['$1']. +forms -> form forms : ['$1'|'$2']. + +form -> '-' atom '(' attribute ')' : + {attribute, element(2, '$2'), element(3, '$2'), '$4'}. +form -> function : '$1'. + + +attribute -> atom : element(3, '$1'). +attribute -> '[' farity_list ']' : '$2'. + +farity_list -> farity : ['$1']. +farity_list -> farity ',' farity_list : ['$1' | '$3']. + +farity -> atom '/' integer : {element(3, '$1'), element(3, '$3')}. + + +function -> function_clause ';' : '$1'. +function -> function_clause function : + case '$1' of + {function, Pos1, Name1, Arity1, [Clause]} -> + case '$2' of + {function, _, Name1, Arity2, Clauses} -> + if + Arity1 /= Arity2 -> + throw({error, {Pos1, yecc, + io_lib:format('arity conflict in definition of ~w', + [Name1])}}); + true -> + {function, Pos1, Name1, Arity1, [Clause | Clauses]} + end; + _ -> + throw({error, {Pos1, yecc, + io_lib:format('missing final semicolon in def of ~w/~w', + [Name1, Arity1])}}) + end + end. + +function_clause -> clause_head clause_guard clause_body : + {Name, Line, Arity, Parameters} = '$1', + {function, Line, Name, Arity, + [{clause, element(2, hd('$3')), Parameters, '$2', '$3'}]}. + +clause_head -> 'fun' atom formal_parameter_list : + {element(3, '$2'), element(2, '$2'), length('$3'), '$3'}. + +clause_head -> '|' atom formal_parameter_list : + {element(3, '$2'), element(2, '$2'), length('$3'), '$3'}. + +formal_parameter_list -> patterns : '$1'. +formal_parameter_list -> '$empty' : []. + +clause_guard -> 'when' guard : '$2'. +clause_guard -> '$empty' : []. + +clause_body -> '=' exprs: '$2'. + +patterns -> pattern : ['$1']. +patterns -> pattern patterns : ['$1' | '$2']. + +comma_patterns -> pattern : ['$1']. +comma_patterns -> pattern ',' comma_patterns : ['$1' | '$3']. + +pattern -> basic_type : '$1'. +pattern -> pattern_list : '$1'. +pattern -> pattern_tuple : '$1'. + +pattern_list -> '[' ']' : {nil, ?line('$1')}. +pattern_list -> '[' pattern pattern_tail ']' : + case '$3' of + {nil,0} -> {cons, ?line('$1'), '$2', {nil, ?line('$1')}}; + _ -> {cons, ?line('$1'), '$2', '$3'} + end. + +pattern_tail -> '|' pattern : '$2'. +pattern_tail -> ',' pattern pattern_tail : + case '$3' of + {nil,0} -> {cons, ?line('$2'), '$2', {nil, ?line('$2')}}; + _ -> {cons, ?line('$2'), '$2', '$3'} + end. +pattern_tail -> '$empty' : {nil,0}. + +pattern_tuple -> '{' '}' : {tuple, element(2, '$1'), []}. +pattern_tuple -> '{' comma_patterns '}' : {tuple, element(2, '$1'), '$2'}. + + +exprs -> expr : ['$1']. +exprs -> expr ',' exprs : ['$1' | '$3']. + +expr -> expr_100 : '$1'. + +% No Erlang match expressions are allowed. Use 'let' instead! +%expr_100 -> expr_150 '=' expr_100 : {match,?line('$2'),'$1','$3'}. +%expr_100 -> expr_150 '!' expr_100 : ?mkop2('$1', '$2', '$3'). +expr_100 -> expr_150 : '$1'. + +expr_150 -> expr_160 'orelse' expr_150 : ?mkop2('$1', '$2', '$3'). +expr_150 -> expr_160 : '$1'. + +expr_160 -> expr_200 'andalso' expr_160 : ?mkop2('$1', '$2', '$3'). +expr_160 -> expr_200 : '$1'. + +expr_200 -> expr_300 comp_op expr_300 : + ?mkop2('$1', '$2', '$3'). +expr_200 -> expr_300 : '$1'. + +expr_300 -> expr_400 list_op expr_300 : + ?mkop2('$1', '$2', '$3'). +expr_300 -> expr_400 : '$1'. + +expr_400 -> expr_400 add_op expr_500 : + ?mkop2('$1', '$2', '$3'). +expr_400 -> expr_500 : '$1'. + +expr_500 -> expr_500 mult_op expr_600 : + ?mkop2('$1', '$2', '$3'). +expr_500 -> expr_600 : '$1'. + +expr_600 -> prefix_op expr_700 : + ?mkop1('$1', '$2'). +expr_600 -> expr_700 : '$1'. + +expr_700 -> function_call : '$1'. +%%expr_700 -> record_expr : '$1'. +expr_700 -> expr_800 : '$1'. + +expr_800 -> expr_900 ':' expr_max : + {remote,?line('$2'),'$1','$3'}. +expr_800 -> expr_900 : '$1'. + +expr_900 -> '.' atom : + {record_field,?line('$1'),{atom,?line('$1'),''},'$2'}. +expr_900 -> expr_900 '.' atom : + {record_field,?line('$2'),'$1','$3'}. +expr_900 -> expr_max : '$1'. + +expr_max -> basic_type : '$1'. +expr_max -> list : '$1'. +expr_max -> binary : '$1'. +expr_max -> list_comprehension : '$1'. +expr_max -> binary_comprehension : '$1'. +expr_max -> tuple : '$1'. +%%expr_max -> struct : '$1'. +expr_max -> '(' expr ')' : '$2'. +%%expr_max -> 'begin' exprs 'end' : {block,?line('$1'),'$2'}. +%%expr_max -> if_expr : '$1'. +expr_max -> case_expr : '$1'. +%%expr_max -> receive_expr : '$1'. +expr_max -> fun_expr : '$1'. +%%expr_max -> try_expr : '$1'. +%%expr_max -> query_expr : '$1'. +expr_max -> let_expr : '$1'. + +basic_type -> atomic : '$1'. +basic_type -> var : '$1'. + +list -> '[' ']' : {nil, ?line('$1')}. +list -> '[' expr expr_tail ']' : + case '$3' of + {nil,0} -> {cons, ?line('$1'), '$2', {nil, ?line('$1')}}; + _ -> {cons, ?line('$1'), '$2', '$3'} + end. + +expr_tail -> '|' expr : '$2'. +expr_tail -> ',' expr expr_tail : + case '$3' of + {nil,0} -> {cons, ?line('$2'), '$2', {nil, ?line('$2')}}; + _ -> {cons, ?line('$2'), '$2', '$3'} + end. +expr_tail -> '$empty' : {nil,0}. + +tuple -> '{' '}' : {tuple, ?line('$1'), []}. +tuple -> '{' exprs '}' : {tuple, ?line('$1'), '$2'}. + +%% ----------------------------------------------------------------- +%% CASE EXPRESSION +%% --------------- +%% +%% case Expr of +%% Pat1 => Body1 +%% | Pat2 => Body2 +%% +%% ----------------------------------------------------------------- +case_expr -> 'case' expr 'of' cr_clauses : + {'case',?line('$1'),'$2','$4'}. + +cr_clauses -> cr_clause : ['$1']. +cr_clauses -> cr_clause '|' cr_clauses : ['$1' | '$3']. + +cr_clause -> formal_parameter_list clause_guard fun_cr_clause_body : + {clause,?line(hd('$1')),'$1','$2','$3'}. + + +%% ----------------------------------------------------------------- +%% FUN EXPRESSION +%% --------------- +%% +%% fn Pat1 => Body +%% | fn Pat2 => Body2 +%% +%% ----------------------------------------------------------------- +fun_expr -> fun_clauses : build_fun('$1'). + +fun_clauses -> fun_clause : ['$1']. +fun_clauses -> fun_clause '|' fun_clauses : ['$1'|'$3']. + +fun_clause -> fun_clause_head clause_guard fun_cr_clause_body : + {'fn', Line, Parameters} = '$1', + {clause, Line, 'fun', Parameters, '$2', '$3'}. + +fun_clause_head -> 'fn' formal_parameter_list : + {'fn', ?line('$1'), '$2'}. + +fun_cr_clause_body -> '=>' exprs : '$2'. + + +function_call -> expr_800 argument_list : + {call,?line('$1'),'$1', element(1,'$2')}. + +argument_list -> '(' ')' : {[],?line('$1')}. +argument_list -> '(' exprs ')' : {'$2',?line('$1')}. + + +%if_expr -> 'if' if_clauses 'end' : {'if', element(2, '$1'), '$2'}. +%if_expr -> 'if' if_clauses : {'if', element(2, '$1'), '$2'}. + +%if_clause -> guard clause_body : {clause, element(2, hd('$2')), '$1', '$2'}. + +%if_clauses -> if_clause : ['$1']. +%if_clauses -> if_clause ';' if_clauses : ['$1' | '$3']. + + +let_expr -> 'let' val_exprs 'in' exprs : + {'let', ?line('$1'), '$2', '$4'}. + +val_exprs -> val_expr : ['$1']. +val_exprs -> val_expr val_exprs : ['$1' | '$2']. + +val_expr -> 'val' pattern '=' expr : {'val', ?line('$1'), '$2', '$4'}. +val_expr -> 'rec' var '=' fun_expr : {'rec', ?line('$1'), '$2', '$4'}. + + + +list_comprehension -> '[' expr '||' lc_exprs ']' : + {lc,?line('$1'),'$2','$4'}. +binary_comprehension -> '<<' binary '||' lc_exprs '>>' : + {bc,?line('$1'),'$2','$4'}. +lc_exprs -> lc_expr : ['$1']. +lc_exprs -> lc_expr ',' lc_exprs : ['$1'|'$3']. + +lc_expr -> expr : '$1'. +lc_expr -> expr '<-' expr : {generate,?line('$2'),'$1','$3'}. +lc_expr -> binary '<=' expr : {b_generate,?line('$2'),'$1','$3'}. + +binary -> '<<' '>>' : {bin,?line('$1'),[]}. +binary -> '<<' bin_elements '>>' : {bin,?line('$1'),'$2'}. + +bin_elements -> bin_element : ['$1']. +bin_elements -> bin_element ',' bin_elements : ['$1'|'$3']. + +bin_element -> bit_expr opt_bit_size_expr opt_bit_type_list : + {bin_element,?line('$1'),'$1','$2','$3'}. + +bit_expr -> prefix_op expr_max : ?mkop1('$1', '$2'). +bit_expr -> expr_max : '$1'. + +opt_bit_size_expr -> ':' bit_size_expr : '$2'. +opt_bit_size_expr -> '$empty' : default. + +opt_bit_type_list -> '/' bit_type_list : '$2'. +opt_bit_type_list -> '$empty' : default. + +bit_type_list -> bit_type '-' bit_type_list : ['$1' | '$3']. +bit_type_list -> bit_type : ['$1']. + +bit_type -> atom : element(3,'$1'). +bit_type -> atom ':' integer : { element(3,'$1'), element(3,'$3') }. + +bit_size_expr -> expr_max : '$1'. + + +guard_expr -> basic_type : '$1'. +guard_expr -> guard_expr_list : '$1'. +guard_expr -> guard_expr_tuple : '$1'. +guard_expr -> guard_call : '$1'. +guard_expr -> '(' guard_expr ')' : '$2'. +guard_expr -> guard_expr add_op guard_expr : + {Op, Pos} = '$2', + {arith, Pos, Op, '$1', '$3'}. +guard_expr -> guard_expr mult_op guard_expr : + {Op, Pos} = '$2', + {arith, Pos, Op, '$1', '$3'}. +guard_expr -> prefix_op guard_expr: + case '$2' of + {float, Pos, N} -> + case '$1' of + {'-', _} -> + {float, Pos, -N}; + {'+', _} -> + {float, Pos, N}; + {Op, Pos1} -> + {arith, Pos1, Op, {float, Pos, N}} + end; + {integer, Pos, N} -> + case '$1' of + {'-', _} -> + {integer, Pos, -N}; + {'+', _} -> + {integer, Pos, N}; + {Op, Pos1} -> + {arith, Pos1, Op, {integer, Pos, N}} + end; + _ -> + {Op, Pos} = '$1', + {arith, Pos, Op, '$2'} + end. + +guard_expr_list -> '[' ']' : {nil, ?line('$1')}. +guard_expr_list -> '[' guard_expr guard_expr_tail ']' : + {cons, ?line('$1'), '$2', '$3'}. + +guard_expr_tail -> '|' guard_expr : '$2'. +guard_expr_tail -> ',' guard_expr guard_expr_tail : + case '$3' of + {nil,0} -> {cons, ?line('$2'), '$2', {nil, ?line('$2')}}; + _ -> {cons, ?line('$2'), '$2', '$3'} + end. +guard_expr_tail -> '$empty' : {nil,0}. + +guard_expr_tuple -> '{' '}' : {tuple, element(2, '$1'), []}. +guard_expr_tuple -> '{' guard_exprs '}' : {tuple, element(2, '$1'), '$2'}. + +guard_exprs -> guard_expr : ['$1']. +guard_exprs -> guard_expr ',' guard_exprs : ['$1' | '$3']. + + +guard_call -> atom '(' guard_parameter_list ')' : + case erl_parse:erlang_guard_bif(element(3, '$1'), length('$3')) of + true -> + {bif, element(2, '$1'), element(3, '$1'), '$3'}; + false -> + throw({error, {element(2, '$1'), yecc, "illegal test in guard **"}}) + end. + +guard_parameter_list -> guard_exprs : '$1'. +guard_parameter_list -> '$empty' : []. + + +bif_test -> atom '(' guard_parameter_list ')' : + case erl_parse:erlang_guard_test(element(3, '$1'), length('$3')) of + true -> + {test, element(2, '$1'), element(3, '$1'), '$3'}; + false -> + throw({error, {element(2, '$1'), yecc, "illegal test in guard **"}}) + end. + + +guard_test -> bif_test : '$1'. +guard_test -> guard_expr comp_op guard_expr : + {Op, Pos} = '$2', + {comp, Pos, Op, '$1', '$3'}. + +guard_tests -> guard_test : ['$1']. +guard_tests -> guard_test ',' guard_tests : ['$1' | '$3']. + +% guard -> 'true' : []. +guard -> atom : + case '$1' of + {atom, _, true} -> + []; + _ -> + throw({error, {element(2, '$1'), yecc, "illegal test in guard **"}}) + end. +guard -> guard_tests : '$1'. + + + +%%atomic -> char : '$1'. +atomic -> integer : '$1'. +atomic -> float : '$1'. +atomic -> atom : '$1'. +atomic -> strings : '$1'. + +strings -> string : '$1'. +strings -> string strings : + {string,?line('$1'),element(3, '$1') ++ element(3, '$2')}. + + +prefix_op -> '+' : '$1'. +prefix_op -> '-' : '$1'. +prefix_op -> 'bnot' : '$1'. +prefix_op -> 'not' : '$1'. + +mult_op -> '/' : '$1'. +mult_op -> '*' : '$1'. +mult_op -> 'div' : '$1'. +mult_op -> 'rem' : '$1'. +mult_op -> 'band' : '$1'. +mult_op -> 'and' : '$1'. + +add_op -> '+' : '$1'. +add_op -> '-' : '$1'. +add_op -> 'bor' : '$1'. +add_op -> 'bxor' : '$1'. +add_op -> 'bsl' : '$1'. +add_op -> 'bsr' : '$1'. +add_op -> 'or' : '$1'. +add_op -> 'xor' : '$1'. + +list_op -> '++' : '$1'. +list_op -> '--' : '$1'. + +comp_op -> '==' : '$1'. +comp_op -> '/=' : '$1'. +comp_op -> '=<' : '$1'. +comp_op -> '<' : '$1'. +comp_op -> '>=' : '$1'. +comp_op -> '>' : '$1'. +comp_op -> '=:=' : '$1'. +comp_op -> '=/=' : '$1'. + + +Erlang code. + +%% mkop(Op, Arg) -> {op,Line,Op,Arg}. +%% mkop(Left, Op, Right) -> {op,Line,Op,Left,Right}. + +-define(mkop2(L, OpPos, R), + begin + {Op,Pos} = OpPos, + {op,Pos,Op,L,R} + end). + +-define(mkop1(OpPos, A), + begin + {Op,Pos} = OpPos, + {op,Pos,Op,A} + end). + +%% keep track of line info in tokens +-define(line(Tup), element(2, Tup)). + +%% build_fun(Line, [Clause]) -> {'fun',Line,{clauses,[Clause]}}. +build_fun([H|_] = Cs) -> + Arity = length(element(4, hd(Cs))), + {'fun',?line(H),{clauses,check_clauses(Cs, 'fun', Arity)}}. + +check_clauses(Cs, Name, Arity) -> + mapl(fun ({clause,L,N,As,G,B}) when N =:= Name, length(As) =:= Arity -> + {clause,L,As,G,B}; + ({clause,L,_N,_As,_G,_B}) -> + ret_err(L, "head mismatch") end, Cs). + +%% mapl(F,List) +%% an alternative map which always maps from left to right +%% and makes it possible to interrupt the mapping with throw on +%% the first occurence from left as expected. +%% can be removed when the jam machine (and all other machines) +%% uses the standardized (Erlang 5.0) evaluation order (from left to right) +mapl(F, [H|T]) -> + V = F(H), + [V | mapl(F,T)]; +mapl(_, []) -> + []. + +-spec ret_err(_, _) -> no_return(). +ret_err(L, S) -> + {location,Location} = get_attribute(L, location), + return_error(Location, S). + +%%% [Experimental]. The parser just copies the attributes of the +%%% scanner tokens to the abstract format. This design decision has +%%% been hidden to some extent: use set_line() and get_attribute() to +%%% access the second element of (almost all) of the abstract format +%%% tuples. A typical use is to negate line numbers to prevent the +%%% compiler from emitting warnings and errors. The second element can +%%% (of course) be set to any value, but then these functions no +%%% longer apply. To get all present attributes as a property list +%%% get_attributes() should be used. + +set_line(L, F) -> + erl_scan:set_attribute(line, L, F). + +get_attribute(L, Name) -> + erl_scan:attributes_info(L, Name). + +get_attributes(L) -> + erl_scan:attributes_info(L). diff --git a/src/rebar_eml_plugin.erl b/src/rebar_eml_plugin.erl new file mode 100644 index 0000000..69868ef --- /dev/null +++ b/src/rebar_eml_plugin.erl @@ -0,0 +1,72 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2012 Torbjorn Tornkvist (etnt@redhoterlang.com) +%% +%% This file is based upon rebar_lfe_compiler.erl from the +%% Rebar project, which had the following notice: +%% +%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com), +%% Tim Dysinger (tim@dysinger.net) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- + +-module(rebar_eml_plugin). + +-export([compile/2]). + + +%% =================================================================== +%% Public API +%% =================================================================== + +compile(Config, _AppFile) -> + FirstFiles = rebar_config:get_list(Config, lfe_first_files, []), + rebar_base_compiler:run(Config, FirstFiles, "src", ".eml", "ebin", ".beam", + fun compile_eml/3). + +%% =================================================================== +%% Internal functions +%% =================================================================== + +compile_eml(Source, _Target, Config) -> + case code:which(eml) of + non_existing -> + rebar_utils:abort( + "~n" + "*** MISSING EML COMPILER ***~n" + " You must do one of the following:~n" + " a) Install EML globally in your erl libs~n" + " b) Add EML as a dep for your project, eg:~n" + " {eml, \"0.*\",~n" + " {git, \"git://github.com/etnt/eml\",~n" + " \"HEAD\"}}~n" + "~n" + , []); + _ -> + Opts = [{i, "include"}, {outdir, "ebin"}, report] + ++ rebar_config:get_list(Config, erl_opts, []), + try {ok,_} = eml:compile_file(Source, Opts), ok + catch Class:Error -> + rebar_utils:abort("~p: EML compilation failed: ~p:~p~n~p~n", + [Source, Class, Error, erlang:get_stacktrace()]) + end + end.