diff --git a/.gitignore b/.gitignore index da6ae306b45..2aa0d3831c5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ /src/*_parser.erl /test/ebin /test/tmp -/ebin/*.beam +/ebin/* /exbin/*.beam /src/*rl.old /.eunit/* diff --git a/Makefile b/Makefile index 9a3d9e2fb80..88a9c10c054 100644 --- a/Makefile +++ b/Makefile @@ -42,5 +42,5 @@ test: deps all release: all test dialyzer --src src $(DIALYZER_WARNINGS) -compile_elixir: +exbin: lib/*.ex lib/*/*.ex @$(ERL) -s elixir_compiler core -s erlang halt diff --git a/ebin/elixir.app b/ebin/elixir.app deleted file mode 100644 index 68a322f6e24..00000000000 --- a/ebin/elixir.app +++ /dev/null @@ -1,17 +0,0 @@ -{application,elixir, - [{description,"elixir "}, - {vsn,"0.4.0"}, - {registered,[]}, - {applications,[kernel,stdlib]}, - {module,[elixir,eex_lexer,elixir_clauses,elixir_compiler, - elixir_def_method,elixir_errors,elixir_glob, - elixir_interpolation,elixir_macro,elixir_namespace, - elixir_parser,elixir_quote,elixir_sup,elixir_tokenizer, - elixir_translator,elixir_tree_helpers]}, - {env,[]}, - {modules,[eex_lexer,elixir,elixir_clauses,elixir_compiler, - elixir_def_method,elixir_errors,elixir_glob, - elixir_interpolation,elixir_macro,elixir_namespace, - elixir_parser,elixir_quote,elixir_sup, - elixir_tokenizer,elixir_translator, - elixir_tree_helpers]}]}. diff --git a/include/elixir.hrl b/include/elixir.hrl index 119f2792695..50513d05369 100644 --- a/include/elixir.hrl +++ b/include/elixir.hrl @@ -11,13 +11,14 @@ % 1) when true, new variables can be defined in that subtree % 2) when true, we are inside a guard % 3) when true, don't resolve sub references -% 4) the current method -% 5) the current namespace -% 6) a dict of defined variables and their alias -% 7) a list of all variables defined in a particular assign -% 8) a dict of all variables defined in a particular clause -% 9) a counter with the variables defined -% 10) the current scope filename +% 4) when true, don't add new names. used by try. +% 5) the current method +% 6) the current namespace +% 7) a dict of defined variables and their alias +% 8) a list of all variables defined in a particular assign +% 9) a dict of all variables defined in a particular clause +% 10) a counter with the variables defined +% 11) the current scope filename % --record(elixir_scope, {assign=false, guard=false, noref=false, method=[], namespace=[], vars=dict:new(), - temp_vars=[], clause_vars=dict:new(), counter=0, filename="nofile"}). \ No newline at end of file +-record(elixir_scope, {assign=false, guard=false, noref=false, noname=false, method=[], + namespace=[], vars=dict:new(), temp_vars=[], clause_vars=dict:new(), counter=0, filename="nofile"}). \ No newline at end of file diff --git a/lib/elixir/macros.ex b/lib/elixir/macros.ex index 046741edb43..10d8a315651 100644 --- a/lib/elixir/macros.ex +++ b/lib/elixir/macros.ex @@ -1,52 +1,56 @@ ns Elixir::Macros -# Provides an integer division macro according to Erlang semantics. -# Raises an error if one of the arguments is not an integer. -# Can be used in guard tests. -# -# == Examples -# -# 5 div 2 #=> 2 -# -defmacro div(left, right), do: - quote(erlang_op :div, unquote(left), unquote(right)) - -# Provides an 'private' macro for restrict visibility of functions +# Provides a 'private' macro for restrict visibility of functions # # == Examples # # ns Foo # definition of Foo namespace # -# private # make function an private +# private # mark following functions as private +# # def secret do # :secret # end # # endns # ends namespace here +# # Foo.secret #=> it will raise 'undef' error # defmacro private do quote(Erlang.elixir_def_method.set_visibility(__NAMESPACE__, :private)) end -# Provides an 'public' macro for restrict visibility of functions +# Provides a 'public' macro for restrict visibility of functions # # == Examples # # ns Foo # definition of Foo namespace # -# public # make function an public +# public # mark following functions as public (the default) +# # def secret do # :secret # end # # endns # ends namespace here +# # Foo.secret #=> :secret # defmacro public do quote(Erlang.elixir_def_method.set_visibility(__NAMESPACE__, :public)) end +# Provides an integer division macro according to Erlang semantics. +# Raises an error if one of the arguments is not an integer. +# Can be used in guard tests. +# +# == Examples +# +# 5 div 2 #=> 2 +# +defmacro div(left, right), do: + quote(erlang_op :div, unquote(left), unquote(right)) + # Provides an integer remainder macro according to Erlang semantics. # Raises an error if one of the arguments is not an integer. # Can be used in guard tests. @@ -212,7 +216,9 @@ defmacro !(expr) do ) end -# private +# Mark visibility from here on to private. We can't use the +# private macro because it is defined in this namespace. +Erlang.elixir_def_method.set_visibility(__NAMESPACE__, :private) # Build if clauses by nesting them recursively. # For instance, the following clause: diff --git a/rebar b/rebar index 4de54da2f59..4bfd453eeb6 100755 Binary files a/rebar and b/rebar differ diff --git a/rebar.config b/rebar.config index 6a763fc9341..1d6b2c930eb 100644 --- a/rebar.config +++ b/rebar.config @@ -21,9 +21,6 @@ debug_info ]}. -%% make executable landed in bin -{escript_name, "bin/elixir"}. - %% enable verbos for yecc {yrl_opts, [ {report, false}, @@ -49,4 +46,4 @@ {eunit, "cp exbin/* .eunit/"} ]}. -{post_hooks, [{compile, "erl -noshell -pa ebin -s elixir_compiler core -s erlang halt"}]}. +{post_hooks, [{compile, "make exbin"}]}. diff --git a/src/elixir_clauses.erl b/src/elixir_clauses.erl index 0e6fc792363..9752c01c39e 100644 --- a/src/elixir_clauses.erl +++ b/src/elixir_clauses.erl @@ -1,5 +1,6 @@ -module(elixir_clauses). --export([match/3, assigns/3, assigns_blocks/4, assigns_blocks/5, extract_guards/1]). +-export([match/3, assigns/3, assigns_blocks/4, + try_catch/3, assigns_blocks/5, extract_guards/1]). -include("elixir.hrl"). % Function for translating assigns. @@ -19,11 +20,7 @@ assigns_blocks(Fun, Args, Exprs, Guards, S) -> { TGuards, SG } = elixir_translator:translate(Guards, SA#elixir_scope{guard=true}), { TExprs, SE } = elixir_translator:translate(Exprs, SG#elixir_scope{guard=false}), - % Properly listify args - case is_list(TArgs) of - true -> FArgs = TArgs; - false -> FArgs = [TArgs] - end, + FArgs = listify(TArgs), % Properly listify guards FGuards = case TGuards of @@ -33,7 +30,7 @@ assigns_blocks(Fun, Args, Exprs, Guards, S) -> % Uncompact expressions from the block. case TExprs of - { block, _, FExprs } -> []; + [{ block, _, FExprs }] -> []; _ -> FExprs = TExprs end, @@ -44,13 +41,19 @@ assigns_blocks(Fun, Args, Exprs, Guards, S) -> extract_guards({ '|', _, [Left, Right] }) -> { Left, [Right] }; extract_guards(Else) -> { Else, [] }. -% Function for translating macros with match style -% clauses like case, try and receive. +% Function for translating macros for try's catch. -match(Line, Clauses, RawS) -> - S = RawS#elixir_scope{clause_vars=dict:new()}, +try_catch(Line, Clauses, S) -> DecoupledClauses = decouple_clauses(Clauses, []), + % Just pass the variable counter forward between each clause. + Transformer = fun(X, Acc) -> translate_each(X, umergec(S, Acc)) end, + lists:mapfoldl(Transformer, S, DecoupledClauses). + +% Function for translating macros with match style like case and receive. +match(Line, Clauses, RawS) -> + S = RawS#elixir_scope{clause_vars=dict:new()}, + DecoupledClauses = decouple_clauses(handle_else(match, Line, Clauses), []), case DecoupledClauses of [DecoupledClause] -> { TDecoupledClause, TS } = translate_each(DecoupledClause, S), @@ -113,6 +116,20 @@ match(Line, Clauses, RawS) -> end end. +% Handle else clauses by moving them under the given Kind. +handle_else(Kind, Line, Clauses) -> + case orddict:find(else, Clauses) of + { ok, Else } -> + ElseClause = prepend_to_block(Line, {'_', Line, false}, Else), + TClauses = orddict:erase(else, Clauses), + case orddict:find(Kind, TClauses) of + { ok, KindClauses } -> + orddict:store(Kind, listify(KindClauses) ++ [ElseClause], TClauses); + _ -> [{Kind,ElseClause}] + end; + _ -> Clauses + end. + % Decouple clauses. A clause is a key-value pair. If the value is an array, % it is broken into several other key-value pairs with the same key. This % process is only valid for :match and :catch keys (as they are the only @@ -145,7 +162,11 @@ translate_each({Key,{block,_,Exprs}}, S) -> translate_each({Key,Expr}, S) when not is_list(Expr) -> translate_each({Key,[Expr]}, S); -% Clauses must return at least two elements. +% Do clauses have no conditions. So we are done. +translate_each({Key,Expr}, S) when Key == do -> + elixir_translator:translate(Expr, S); + +% Condition clauses must return at least two elements. translate_each({Key,[Expr]}, S) -> translate_each({Key,[Expr, nil]}, S); @@ -198,3 +219,16 @@ normalize_clause_var(Var, OldValue, ClauseVars) -> umergec(S1, S2) -> S1#elixir_scope{counter=S2#elixir_scope.counter}. + +%% Listify + +listify(Expr) when not is_list(Expr) -> [Expr]; +listify(Expr) -> Expr. + +%% Prepend a given expression to a block. + +prepend_to_block(_Line, Expr, { block, Line, Args }) -> + { block, Line, [Expr|Args] }; + +prepend_to_block(Line, Expr, Args) -> + { block, Line, [Expr, Args] }. \ No newline at end of file diff --git a/src/elixir_parser.yrl b/src/elixir_parser.yrl index e3951a0ac5a..3fa3af45758 100644 --- a/src/elixir_parser.yrl +++ b/src/elixir_parser.yrl @@ -6,7 +6,7 @@ Nonterminals expr block_expr curly_expr call_expr max_expr base_expr matched_expr matched_op_expr unmatched_expr unmatched_op_expr comma_separator kv_eol - match_op add_op mult_op unary_op addadd_op multmult_op pipe_op + match_op add_op mult_op unary_op addadd_op multmult_op pipe_op arrow_op andand_op oror_op andalso_op orelse_op and_op or_op comp_expr_op open_paren close_paren open_bracket close_bracket @@ -27,7 +27,7 @@ Terminals number signed_number atom ref string call_op special_op dot_call_op comp_op 'not' 'and' 'or' 'xor' 'andalso' 'orelse' - '=' '+' '-' '*' '/' '++' '--' '**' '//' + '=' '+' '-' '*' '/' '++' '--' '**' '//' '<-' '(' ')' eol ',' '[' ']' '|' '{' '}' '.' '::' '&&' '||' '!' . @@ -38,13 +38,13 @@ Right 10 match_op. Left 20 do. Left 30 ','. % Solve nested call_args conflicts Left 40 pipe_op. -Left 50 oror_op. -Left 60 andand_op. -Left 70 orelse_op. -Left 80 andalso_op. -Left 90 or_op. -Left 100 and_op. -% Right 90 right_op. +Right 50 arrow_op. +Left 60 oror_op. +Left 70 andand_op. +Left 80 orelse_op. +Left 90 andalso_op. +Left 100 or_op. +Left 110 and_op. Left 150 comp_expr_op. Left 160 add_op. Left 170 mult_op. @@ -88,6 +88,7 @@ unmatched_op_expr -> expr orelse_op expr : build_op('$2', '$1', '$3'). unmatched_op_expr -> expr and_op expr : build_op('$2', '$1', '$3'). unmatched_op_expr -> expr or_op expr : build_op('$2', '$1', '$3'). unmatched_op_expr -> expr pipe_op expr : build_op('$2', '$1', '$3'). +unmatched_op_expr -> expr arrow_op expr : build_op('$2', '$1', '$3'). unmatched_op_expr -> expr comp_expr_op expr : build_expr_op('$2', '$1', '$3'). unmatched_op_expr -> unary_op expr : build_unary_op('$1', '$2'). unmatched_op_expr -> special_op expr : build_special_op('$1', '$2'). @@ -105,6 +106,7 @@ matched_op_expr -> matched_expr orelse_op matched_expr : build_op('$2', '$1', '$ matched_op_expr -> matched_expr and_op matched_expr : build_op('$2', '$1', '$3'). matched_op_expr -> matched_expr or_op matched_expr : build_op('$2', '$1', '$3'). matched_op_expr -> matched_expr pipe_op matched_expr : build_op('$2', '$1', '$3'). +matched_op_expr -> matched_expr arrow_op matched_expr : build_op('$2', '$1', '$3'). matched_op_expr -> matched_expr comp_expr_op matched_expr : build_expr_op('$2', '$1', '$3'). matched_op_expr -> unary_op matched_expr : build_unary_op('$1', '$2'). matched_op_expr -> special_op matched_expr : build_special_op('$1', '$2'). @@ -223,6 +225,9 @@ or_op -> 'xor' eol : '$1'. pipe_op -> '|' : '$1'. pipe_op -> '|' eol : '$1'. +arrow_op -> '<-' : '$1'. +arrow_op -> '<-' eol : '$1'. + comp_expr_op -> comp_op : '$1'. comp_expr_op -> comp_op eol : '$1'. diff --git a/src/elixir_tokenizer.erl b/src/elixir_tokenizer.erl index 1f3099a6da8..8b44ebcce3e 100644 --- a/src/elixir_tokenizer.erl +++ b/src/elixir_tokenizer.erl @@ -41,7 +41,8 @@ tokenize(Line, [$.,T1,T2|Rest], Tokens) when T1 == $& andalso T2 == $&; T1 == $= andalso T2 == $=; T1 == $! andalso T2 == $=; T1 == $< andalso T2 == $=; T1 == $> andalso T2 == $=; T1 == $+ andalso T2 == $+; T1 == $- andalso T2 == $-; - T1 == $* andalso T2 == $*; T1 == $/ andalso T2 == $/ -> + T1 == $* andalso T2 == $*; T1 == $/ andalso T2 == $/; + T1 == $< andalso T2 == $- -> tokenize(Line, Rest, [tokenize_call_identifier(identifier, Line, list_to_atom([T1,T2]), Rest),{'.',Line}|Tokens]); % ## Single Token Operators @@ -104,7 +105,8 @@ tokenize(Line, [$:,T1,T2|Rest], Tokens) when T1 == $& andalso T2 == $&; T1 == $= andalso T2 == $=; T1 == $! andalso T2 == $=; T1 == $< andalso T2 == $=; T1 == $> andalso T2 == $=; T1 == $+ andalso T2 == $+; T1 == $- andalso T2 == $-; - T1 == $* andalso T2 == $*; T1 == $/ andalso T2 == $/ -> + T1 == $* andalso T2 == $*; T1 == $/ andalso T2 == $/; + T1 == $< andalso T2 == $- -> tokenize(Line, Rest, [{atom,Line,[list_to_atom([T1,T2])]}|Tokens]); % ## Single Token Operators @@ -130,7 +132,8 @@ tokenize(Line, [T1,T2,$:|Rest], Tokens) when T1 == $& andalso T2 == $&; T1 == $= andalso T2 == $=; T1 == $! andalso T2 == $=; T1 == $< andalso T2 == $=; T1 == $> andalso T2 == $=; T1 == $+ andalso T2 == $+; T1 == $- andalso T2 == $-; - T1 == $* andalso T2 == $*; T1 == $/ andalso T2 == $/ -> + T1 == $* andalso T2 == $*; T1 == $/ andalso T2 == $/; + T1 == $< andalso T2 == $- -> tokenize(Line, Rest, [{kv_identifier,Line,list_to_atom([T1,T2])}|Tokens]); % ## Single Token Operators @@ -156,7 +159,8 @@ tokenize(Line, [T1,T2,$(|Rest], Tokens) when T1 == $& andalso T2 == $&; T1 == $= andalso T2 == $=; T1 == $! andalso T2 == $=; T1 == $< andalso T2 == $=; T1 == $> andalso T2 == $=; T1 == $+ andalso T2 == $+; T1 == $- andalso T2 == $-; - T1 == $* andalso T2 == $*; T1 == $/ andalso T2 == $/ -> + T1 == $* andalso T2 == $*; T1 == $/ andalso T2 == $/; + T1 == $< andalso T2 == $- -> tokenize(Line, [$(|Rest], [{call_op,Line,list_to_atom([T1,T2])}|Tokens]); % ## Single Token Operators @@ -191,7 +195,8 @@ tokenize(Line, [T1,T2,T3|Rest], Tokens) when % ## Comparison two token operators tokenize(Line, [T1,T2|Rest], Tokens) when T1 == $= andalso T2 == $=; T1 == $! andalso T2 == $=; - T1 == $< andalso T2 == $=; T1 == $> andalso T2 == $= -> + T1 == $< andalso T2 == $=; T1 == $> andalso T2 == $=; + T1 == $< andalso T2 == $- -> tokenize(Line, Rest, [{comp_op, Line, list_to_atom([T1,T2])}|Tokens]); % ## Two Token Operators diff --git a/src/elixir_translator.erl b/src/elixir_translator.erl index 614ec468659..3da97e92442 100644 --- a/src/elixir_translator.erl +++ b/src/elixir_translator.erl @@ -43,7 +43,7 @@ translate_each({ '-', Line, [Expr] }, S) when is_number(Expr) -> translate_each(-1 * Expr, S); translate_each({ Op, Line, Exprs }, S) when is_list(Exprs), - Op == '+'; Op == '-'; Op == '*'; Op == '/'; + Op == '+'; Op == '-'; Op == '*'; Op == '/'; Op == '<-'; Op == '++'; Op == '--'; Op == 'andalso'; Op == 'orelse'; Op == 'not'; Op == 'and'; Op == 'or'; Op == 'xor'; Op == '<'; Op == '>'; Op == '<='; Op == '>='; @@ -65,16 +65,8 @@ translate_each({ erlang_op, Line, [Op, Expr] }, S) when is_atom(Op) -> translate_each({'case', Line, [Expr, RawClauses]}, S) -> Clauses = orddict:erase(do, RawClauses), - - case Clauses of - [{else,Else},{match,Match}] -> - ElseClause = prepend_to_block(Line, {'_', Line, false}, Else), - MatchClauses = [{match,listify(Match) ++ [ElseClause]}]; - MatchClauses -> [] - end, - { TExpr, NS } = translate_each(Expr, S), - { TClauses, TS } = elixir_clauses:match(Line, MatchClauses, NS), + { TClauses, TS } = elixir_clauses:match(Line, Clauses, NS), { { 'case', Line, TExpr, TClauses }, TS }; % TODO: Handle tree errors properly @@ -206,6 +198,40 @@ translate_each({fn, Line, RawArgs}, S) when is_list(RawArgs) -> { TClause, NS } = elixir_clauses:assigns_blocks(fun translate/2, Args, [Expr], S), { { 'fun', Line, {clauses, [TClause]} }, NS }; +%% Try + +translate_each({'try', Line, [Clauses]}, RawS) -> + Do = proplists:get_value('do', Clauses, []), + Catch = proplists:get_value('catch', Clauses, []), + After = proplists:get_value('after', Clauses, []), + + S = RawS#elixir_scope{noname=true}, + + { TDo, SB } = translate([Do], S), + { TCatch, SC } = elixir_clauses:try_catch(Line, [{'catch',Catch}], umergec(S, SB)), + { TAfter, SA } = translate([After], umergec(S, SC)), + { { 'try', Line, unpack_try(do, TDo), [], TCatch, unpack_try('after', TAfter) }, umergec(RawS, SA) }; + +% TODO: Handle tree errors properly +translate_each({'try', _, Args} = Clause, S) when is_list(Args) -> + error({invalid_arguments_for_try, Clause}); + +%% Receive + +translate_each({'receive', Line, [RawClauses] }, S) -> + Clauses = orddict:erase(do, RawClauses), + case orddict:find('after', Clauses) of + { ok, After } -> + AClauses = orddict:erase('after', Clauses), + { TClauses, SC } = elixir_clauses:match(Line, AClauses ++ [{'after',After}], S), + { FClauses, [TAfter] } = lists:split(length(TClauses) - 1, TClauses), + { _, _, [FExpr], _, FAfter } = TAfter, + { { 'receive', Line, FClauses, FExpr, FAfter }, SC }; + error -> + { TClauses, SC } = elixir_clauses:match(Line, Clauses, S), + { { 'receive', Line, TClauses }, SC } + end; + %% Variables & Function calls translate_each({Name, Line, false}, S) when is_atom(Name) -> @@ -220,7 +246,7 @@ translate_each({Name, Line, false}, S) when is_atom(Name) -> case { Match, dict:is_key(Name, Vars), lists:member(Name, TempVars) } of { true, true, true } -> { {var, Line, dict:fetch(Name, Vars) }, S }; { true, Else, _ } -> - { NewVar, NS } = case Else of + { NewVar, NS } = case Else or S#elixir_scope.noname of true -> elixir_tree_helpers:build_var_name(Line, S); false -> { {var, Line, Name}, S } end, @@ -321,18 +347,11 @@ translate_each(Bitstring, S) when is_bitstring(Bitstring) -> %% Helpers -%% Listify - -listify(Expr) when not is_list(Expr) -> [Expr]; -listify(Expr) -> Expr. - -%% Prepend a given expression to a block. - -prepend_to_block(_Line, Expr, { block, Line, Args }) -> - { block, Line, [Expr|Args] }; - -prepend_to_block(Line, Expr, Args) -> - { block, Line, [Expr, Args] }. +% Unpack a list of expressions from a block. +% Return an empty list in case it is an empty expression on after. +unpack_try(_, [{ block, _, Exprs }]) -> Exprs; +unpack_try('after', [{ nil, _ }]) -> []; +unpack_try(_, Exprs) -> Exprs. % Receives two scopes and return a new scope based on the second % with their variables merged. @@ -365,5 +384,5 @@ convert_op('!==') -> '=/='; convert_op('===') -> '=:='; convert_op('!=') -> '/='; convert_op('<=') -> '=<'; -convert_op(Else) -> Else. - +convert_op('<-') -> '!'; +convert_op(Else) -> Else. \ No newline at end of file diff --git a/test/erlang/conditionals_test.erl b/test/erlang/conditionals_test.erl index ecd0a80e37f..e2e07dd93ab 100644 --- a/test/erlang/conditionals_test.erl +++ b/test/erlang/conditionals_test.erl @@ -9,6 +9,8 @@ booleans_test() -> {true, _} = elixir:eval("true"), {false, _} = elixir:eval("false"). +% If + if_else_kv_args_test() -> {1, _} = elixir:eval("if(true, do: 1)"), {nil, _} = elixir:eval("if(false, do: 1)"), @@ -24,15 +26,15 @@ if_elsif_else_test() -> {3, _} = elixir:eval("if false do\n 1\nelsif: true\n3\nelse:\n2\nend"), {nil, _} = elixir:eval("if false do\n 1\nelsif: [true, 3]\nelse:\n2\nend"). -% vars_if_test() -> -% F = fun() -> -% {1, [{foo,1}]} = elixir:eval("if foo = 1; true; else; false; end; foo"), -% elixir:eval("module Bar\ndef foo; 1; end\ndef bar(x); if x; foo = 2; else; foo = foo; end; foo; end\nend"), -% {1, _} = elixir:eval("Bar.bar(false)"), -% {2, _} = elixir:eval("Bar.bar(true)") -% end, -% test_helper:run_and_remove(F, ['::Bar']). -% +vars_if_test() -> + F = fun() -> + {1, [{foo,1}]} = elixir:eval("if foo = 1 do; true; else: false; end; foo"), + elixir:eval("ns Bar\ndef foo, do: 1\ndef bar(x) do\nif x do; foo = 2; else: foo = foo; end; foo; end\n"), + {1, _} = elixir:eval("Bar.bar(false)"), + {2, _} = elixir:eval("Bar.bar(true)") + end, + test_helper:run_and_remove(F, ['::Bar']). + % multi_assigned_if_test() -> % {3, _} = elixir:eval("x = 1\nif true\nx = 2\nx = 3\nelse true\nend\nx"), % {3, _} = elixir:eval("x = 1\nif true\n\~x = 1\nx = 2\nx = 3\nelse true\nend\nx"), @@ -40,13 +42,29 @@ if_elsif_else_test() -> % {nil, _} = elixir:eval("if false\nx = 1\nelse true\nend\nx"). % +% Try + +try_test() -> + {2, _} = elixir:eval("try do\nErlang.foo.bar\ncatch: { :error, :undef, _ }; 2\nend"). + +% Receive + +receive_test() -> + {10, _} = elixir:eval("self() <- :foo\nreceive do\nmatch: :foo\n10\nend"), + {20, _} = elixir:eval("self() <- :bar\nreceive do\nmatch: :foo\n10\nelse: 20\nend"), + {30, _} = elixir:eval("receive do\nafter: 1\n30\nend"). + +vars_receive_test() -> + {10, _} = elixir:eval("self() <- :foo\nreceive do\nmatch: :foo\na = 10\nmatch: :bar\nend\na"), + {nil, _} = elixir:eval("self() <- :bar\nreceive do\nmatch: :foo\nb = 10\nelse: 20\nend\nb"), + {30, _} = elixir:eval("receive do\nmatch: :foo\nafter: 1\nc = 30\nend\nc"). + +% Case + case_test() -> {true, _} = elixir:eval("case 1 do\nmatch: 2; false\nmatch: 1; true\nend"), - {true, [{x,1}]} = elixir:eval("case 1 do\nmatch: {x,y}; false\nmatch: x; true\nend"). -% {true, [{x,1},{y,2}]} = elixir:eval("case {1,2} match {x,y} then true match {1,x} then false end"), -% {true, [{x,1},{y,2}]} = elixir:eval("case {1,2} match {x,y}\ntrue\nmatch {1,x}\nfalse\nend"), -% {true, _} = elixir:eval("case {1,2} match {3,4}\nfalse\nelse true\nend"), -% {true, _} = elixir:eval("case {1,2} match {3,4}, {1,2}\ntrue\nend"). + {true, [{x,1}]} = elixir:eval("case 1 do\nmatch: {x,y}; false\nmatch: x; true\nend"), + {true, _} = elixir:eval("case {1,2} do;match: {3,4}\nfalse\nelse: true\nend"). case_with_do_ambiguity_test() -> {1,_} = elixir:eval("case quote(true) do\nmatch: true; 1\nmatch: _; false\nend"). @@ -56,18 +74,14 @@ case_with_do_ambiguity_test() -> % {3, _} = elixir:eval("x = 1\ncase 1 match \~x\nx = 2\nx = 3\nelse true\nend\nx"), % {1, _} = elixir:eval("case true match true\nx = 1\nelse true\nend\nx"), % {nil, _} = elixir:eval("case true match false\nx = 1\nelse true\nend\nx"). -% -% vars_case_test() -> -% F = fun() -> -% elixir:eval("module Bar\ndef foo; 1; end\ndef bar(x); case x match true then foo = 2 match false then foo = foo end; foo; end\nend"), -% {1, _} = elixir:eval("Bar.bar(false)"), -% {2, _} = elixir:eval("Bar.bar(true)"), -% elixir:eval("module Baz\ndef foo; 1; end\ndef bar(x); case x match {foo,2} then \~foo = 2 match false then foo = foo end; foo; end\nend"), -% {1, _} = elixir:eval("Baz.bar(false)"), -% {2, _} = elixir:eval("Baz.bar({2, 2})"), -% ?assertError({badmatch, 2}, elixir:eval("Baz.bar({1, 2})")) -% end, -% test_helper:run_and_remove(F, ['::Bar', '::Baz']). + +vars_case_test() -> + F = fun() -> + elixir:eval("ns Bar\ndef foo, do: 1\ndef bar(x) do\ncase x do\nmatch: true; foo = 2\nmatch: false; foo = foo\nend\nfoo\nend"), + {1, _} = elixir:eval("Bar.bar(false)"), + {2, _} = elixir:eval("Bar.bar(true)") + end, + test_helper:run_and_remove(F, ['::Bar']). % Comparison diff --git a/test/erlang/function_test.erl b/test/erlang/function_test.erl index 7eb76be924e..f6475b049fa 100644 --- a/test/erlang/function_test.erl +++ b/test/erlang/function_test.erl @@ -21,54 +21,11 @@ function_with_kv_args_test() -> {Fun, _} = elixir:eval("fn(a, [other: b, another: c], do: a + b + c)"), 6 = Fun(1,[{another,3},{other,2}]). -% function_assignment_multiline_test() -> -% {_, [{a, Res2}]} = elixir:eval("a = do \nx = 1\nx + 2\n end"), -% 3 = Res2(), -% {_, [{a, Res4}]} = elixir:eval("a = do (y) \nx = 1\nx + y\n end"), -% 3 = Res4(2). -% -% function_assignment_with_assignment_test() -> -% {_, [{a, Res1}]} = elixir:eval("a = -> b = 3"), -% 3 = Res1(). -% -% function_assignment_with_empty_args_test() -> -% {_, [{a, Res1}]} = elixir:eval("a = -> () 1 + 2"), -% 3 = Res1(), -% {_, [{a, Res3}]} = elixir:eval("a = -> (\n) (1 + 2)"), -% 3 = Res3(). -% -% function_assignment_with_args_test() -> -% {_, [{a, Res1}]} = elixir:eval("a = -> (x,y) x + y"), -% 3 = Res1(1,2), -% {_, [{a, Res2}]} = elixir:eval("a = do (x,y); x + y; end"), -% 3 = Res2(1,2), -% {_, [{a, Res3}]} = elixir:eval("a = -> (\nx,\ny\n) x + y"), -% 3 = Res3(1,2). -% -% function_nested_assignment_test() -> -% {_, [{a, Res1}]} = elixir:eval("a = -> -> 1 + 2"), -% ResF1 = Res1(), -% 3 = ResF1(). -% -% function_assignment_new_line_test() -> -% {_, [{a, Res2}]} = elixir:eval("a = do\n1 + 2\nend"), -% 3 = Res2(). -% -% function_nested_assignment_new_line_test() -> -% {_, [{a, Res1}]} = elixir:eval("a = -> do\n1 + 2\nend "), -% ResF1 = Res1(), -% 3 = ResF1(), -% {_, [{a, Res2}]} = elixir:eval("a = do\n-> 1 + 2\nend "), -% ResF2 = Res2(), -% 3 = ResF2(). -% -% function_as_clojure_test() -> -% {_, [{a, Res1}|_]} = elixir:eval("b = 1; a = -> b + 2"), -% 3 = Res1(), -% {_, [{a, Res2}|_]} = elixir:eval("b = 1; a = do\nb + 2\nend"), -% 3 = Res2(). -% -% %% Function calls +function_as_clojure_test() -> + {_, [{a, Res1}|_]} = elixir:eval("b = 1; a = fn { b + 2 }"), + 3 = Res1(). + +%% Function calls function_call_test() -> {3, _} = elixir:eval("x = fn(a,b, do: a + b)\nx.(1,2)"). @@ -79,23 +36,14 @@ function_call_without_arg_test() -> function_call_do_end_test() -> {[1,[{do,2},{else,3}]], _} = elixir:eval("x = fn(a, b){ [a, b] }\nx.(1) do\n2\nelse: 3\nend"). -% function_calls_with_arg_test() -> -% {3, _} = elixir:eval("b = 1; a = -> (a) a + b; a.(2)"). -% -% function_call_with_assignment_test() -> -% {3, [{a,_},{c, 3}]} = elixir:eval("a = -> (x) x + 2; c = a.(1)"). -% -% function_call_inside_another_function_test() -> -% {1, _} = elixir:eval("a = -> (x) x + 2; b = -> a.(1) - 2; b.()"). -% -% function_calls_with_multiple_args_test() -> -% {5, _} = elixir:eval("a = -> (a, b) a + b; a.(3, 2)"). -% -% function_calls_with_multiple_expressions_test() -> -% {26, _} = elixir:eval("a = -> (a, b) a + b; a.((3 + 4 - 1), (2 * 10))"). -% -% function_calls_with_multiple_args_with_line_breaks_test() -> -% {5, _} = elixir:eval("a = -> (a, b) a + b; a.(\n3,\n2\n)"). -% -% function_calls_with_parenthesis_test() -> -% {3, [{a,_},{b,1}]} = elixir:eval("(a = -> (x) x + 2).(b = 1)"). \ No newline at end of file +function_call_with_assignment_test() -> + {3, [{a,_},{c, 3}]} = elixir:eval("a = fn(x){ x + 2 }; c = a.(1)"). + +function_calls_with_multiple_expressions_test() -> + {26, _} = elixir:eval("a = fn(a, b){ a + b }; a.((3 + 4 - 1), (2 * 10))"). + +function_calls_with_multiple_args_with_line_breaks_test() -> + {5, _} = elixir:eval("a = fn(a, b){ a + b }; a.(\n3,\n2\n)"). + +function_calls_with_parenthesis_test() -> + {3, [{a,_},{b,1}]} = elixir:eval("a = fn(x){ x + 2 }.(b = 1)"). \ No newline at end of file