├── .cirrus.yml ├── .edts ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── cover.spec ├── include ├── astranaut.hrl ├── astranaut_do.hrl ├── astranaut_struct_name.hrl ├── compile_meta.hrl ├── compile_opts.hrl ├── disable_tco.hrl ├── do.hrl ├── import_module.hrl ├── macro.hrl ├── quote.hrl ├── rebinding.hrl ├── stacktrace.hrl └── struct.hrl ├── rebar.config ├── rebar.lock ├── src ├── astranaut.app.src ├── astranaut.erl ├── astranaut_compile_opts.erl ├── astranaut_disable_tco.erl ├── astranaut_do.erl ├── astranaut_error.erl ├── astranaut_lib.erl ├── astranaut_macro.erl ├── astranaut_monad.erl ├── astranaut_quote.erl ├── astranaut_rebinding.erl ├── astranaut_return.erl ├── astranaut_syntax.erl ├── astranaut_traverse.erl └── astranaut_uniplate.erl └── test ├── astranaut_SUITE.erl ├── astranaut_SUITE_data ├── sample_1.erl ├── sample_2.erl ├── sample_expressions.erl └── sample_transformer_1.erl ├── astranaut_error_SUITE.erl ├── astranaut_macro_SUITE.erl ├── astranaut_macro_SUITE_data ├── macro_example.erl ├── macro_exports.erl ├── macro_test.erl ├── macro_with_error.erl └── macro_with_warnings.erl ├── astranaut_quote_SUITE.erl ├── astranaut_quote_SUITE_data └── quote_example.erl ├── astranaut_rebinding_SUITE.erl ├── astranaut_rebinding_SUITE_data ├── rebinding_example.erl └── rebinding_test.erl ├── astranaut_test_lib.erl ├── astranaut_traverse_SUITE.erl ├── astranaut_uniplate_SUITE.erl ├── astranaut_uniplate_SUITE_data └── sample_plus.erl ├── disable_tco_SUITE.erl └── disable_tco_example.erl /.cirrus.yml: -------------------------------------------------------------------------------- 1 | test_task: 2 | container: 3 | matrix: 4 | - image: erlang:24 5 | - image: erlang:23 6 | - image: erlang:22 7 | - image: erlang:21 8 | - image: erlang:20 9 | - image: erlang:19 10 | rebar3_cache: 11 | folder: _build 12 | fingerprint_script: cat rebar.lock 13 | populate_script: rebar3 compile --deps_only 14 | compile_script: rebar3 compile 15 | test_script: rebar3 ct 16 | -------------------------------------------------------------------------------- /.edts: -------------------------------------------------------------------------------- 1 | :lib-dirs '("_build/default/lib" "_build/test/lib") 2 | :app-include-dirs '("include") 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | logs 15 | _build 16 | .idea 17 | *.iml 18 | rebar3.crashdump 19 | *~ 20 | doc 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 23.0 4 | - 22.1 5 | - 21.3 6 | - 20.3 7 | - 19.3 8 | script: rebar3 ct 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 slepher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cover.spec: -------------------------------------------------------------------------------- 1 | {incl_app, astranaut, details}. 2 | -------------------------------------------------------------------------------- /include/astranaut.hrl: -------------------------------------------------------------------------------- 1 | -include("quote.hrl"). 2 | -include("macro.hrl"). 3 | -------------------------------------------------------------------------------- /include/astranaut_do.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(ASTRANAUT_DO). 2 | -define(ASTRANAUT_DO, true). 3 | -compile({parse_transform, astranaut_do_transformer}). 4 | -endif. 5 | -------------------------------------------------------------------------------- /include/astranaut_struct_name.hrl: -------------------------------------------------------------------------------- 1 | %% just used in astranaut project, should not use outside. 2 | -ifndef(ASTRANANUT_STRUCT_NAME). 3 | -define(ASTRANANUT_STRUCT_NAME, true). 4 | %% for '__struct__' in map 5 | -define(STRUCT_KEY, '__struct__'). 6 | %% for astranaut_walk_return. 7 | -define(WALK_RETURN, astranaut_walk_return). 8 | %% for astranaut_base. 9 | -define(BASE_M, astranaut_base). 10 | %% for astranaut_return_m. 11 | -define(RETURN_OK, astranaut_return_ok). 12 | -define(RETURN_FAIL, astranaut_return_fail). 13 | %% for astranaut_traverse_m. 14 | -define(TRAVERSE_M, astranaut_traverse). 15 | %% for astranaut_error_state. 16 | -define(ERROR_STATE, astranaut_error). 17 | -endif. 18 | -------------------------------------------------------------------------------- /include/compile_meta.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(ASTRANAUT_COMPILE_META). 2 | -define(ASTRANAUT_COMPILE_META, true). 3 | -compile({parse_transform, astranaut_compile_meta_transformer}). 4 | -endif. 5 | -------------------------------------------------------------------------------- /include/compile_opts.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(ASTRANAUT_COMPILE_OPTS). 2 | -define(ASTRANAUT_COMPILE_OPTS, true). 3 | -compile({parse_transform, astranaut_compile_opts}). 4 | -endif. 5 | -------------------------------------------------------------------------------- /include/disable_tco.hrl: -------------------------------------------------------------------------------- 1 | -compile({parse_transform, astranaut_disable_tco}). 2 | -------------------------------------------------------------------------------- /include/do.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(ERL_AF_DO). 2 | -define(ERL_AF_DO, true). 3 | -compile({parse_transform, astranaut_do}). 4 | -endif. 5 | -------------------------------------------------------------------------------- /include/import_module.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(ASTRANAUT_IMPORT_MODULE). 2 | -define(ASTRANAUT_IMPORT_MODULE, true). 3 | -compile({parse_transform, astranaut_import_module}). 4 | -endif. 5 | -------------------------------------------------------------------------------- /include/macro.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(ASTRANAUT_MACRO). 2 | -define(ASTRANAUT_MACRO, true). 3 | -compile({parse_transform, astranaut_macro}). 4 | -endif. 5 | -------------------------------------------------------------------------------- /include/quote.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(ASTRANAUT_QUOTE). 2 | -define(ASTRANAUT_QUOTE, true). 3 | -compile({parse_transform, astranaut_quote}). 4 | -endif. 5 | -------------------------------------------------------------------------------- /include/rebinding.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(ASTRANAUT_REBINDING). 2 | -define(ASTRANAUT_REBINDING, true). 3 | -compile({parse_transform, astranaut_rebinding}). 4 | -endif. 5 | -------------------------------------------------------------------------------- /include/stacktrace.hrl: -------------------------------------------------------------------------------- 1 | -ifdef('OTP_RELEASE'). 2 | %% The 'OTP_RELEASE' macro introduced at OTP-21, 3 | %% so we can use it for detecting whether the Erlang compiler supports new try/catch syntax or not. 4 | -define(CAPTURE_STACKTRACE, :__StackTrace). 5 | -define(GET_STACKTRACE, __StackTrace). 6 | -else. 7 | -define(CAPTURE_STACKTRACE, ). 8 | -define(GET_STACKTRACE, erlang:get_stacktrace()). 9 | -endif. 10 | -------------------------------------------------------------------------------- /include/struct.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(ASTRANAUT_STRUCT). 2 | -define(ASTRANAUT_STRUCT, true). 3 | -compile({parse_transform, astranaut_struct_transformer}). 4 | -endif. 5 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, []}. 3 | {cover_enabled, true}. 4 | {provider_hooks, [{post, [{ct, cover}]}]}. 5 | {erlc_compiler,[{recursive,false}]}. 6 | {erl_first_files, [ 7 | "src/astranaut_monad.erl", 8 | "src/astranaut_error.erl", 9 | "src/astranaut_traverse.erl", 10 | "src/astranaut_return.erl", 11 | "src/astranaut_uniplate.erl", 12 | "src/astranaut.erl", 13 | "src/astranaut_lib.erl", 14 | "src/astranaut_syntax.erl", 15 | "src/astranaut_quote.erl", 16 | "src/astranaut_do.erl", 17 | "src/astranaut_macro.erl", 18 | "src/astranaut_rebinding.erl", 19 | "test/astranaut_test_lib.erl" 20 | ]}. 21 | 22 | 23 | {edoc_opts, [ {preprocess, true} ]}. 24 | 25 | {xref_checks,[ 26 | undefined_function_calls, 27 | undefined_functions, 28 | locals_not_used, 29 | deprecated_function_calls, 30 | deprecated_functions 31 | ]}. 32 | 33 | {plugins, [ 34 | ]}. 35 | 36 | {profiles, [ 37 | {test, [ 38 | {erl_opts, [nowarn_export_all]} 39 | ]} 40 | ]}. 41 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /src/astranaut.app.src: -------------------------------------------------------------------------------- 1 | {application, astranaut, 2 | [{description, "Erlang Ast traverse and macro."}, 3 | {vsn, "0.10.1"}, 4 | {registered, []}, 5 | {applications, [kernel, stdlib, compiler]}, 6 | {env,[]}, 7 | {modules, []}, 8 | {licenses, ["MIT"]}, 9 | {links, [{"Github", "https://www.github.com/slepher/astranaut"}]} 10 | ]}. 11 | -------------------------------------------------------------------------------- /src/astranaut_compile_opts.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2021, Chen Slepher 4 | %%% @doc 5 | %%% add compile_opt() which returns the compile options when compile that file 6 | %%% helps to compile file in test directory data_dir *_SUITE_data 7 | %%% @end 8 | %%% Created : 19 Feb 2021 by Chen Slepher 9 | %%%------------------------------------------------------------------- 10 | -module(astranaut_compile_opts). 11 | 12 | %% API 13 | -export([parse_transform/2, format_error/1]). 14 | 15 | %%%=================================================================== 16 | %%% API 17 | %%%=================================================================== 18 | parse_transform(BaseForms, Opts) -> 19 | OptsAst = astranaut_lib:abstract_form(Opts), 20 | CompileOptsForms = astranaut_lib:gen_exported_function(compile_opts, OptsAst), 21 | astranaut_syntax:insert_forms(CompileOptsForms, BaseForms). 22 | 23 | format_error(Error) -> 24 | astranaut:format_error(Error). 25 | -------------------------------------------------------------------------------- /src/astranaut_disable_tco.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2017, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 17 Oct 2017 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_disable_tco). 10 | 11 | -include("quote.hrl"). 12 | %% API 13 | -export([parse_transform/2, format_error/1]). 14 | %%%=================================================================== 15 | %%% API 16 | %%%=================================================================== 17 | parse_transform(Forms, _Opt) -> 18 | Opts = #{traverse => all}, 19 | astranaut:smap_with_state(fun walk/3, sets:new(), Forms, Opts). 20 | 21 | format_error(Message) -> 22 | case io_lib:deep_char_list(Message) of 23 | true -> Message; 24 | _ -> io_lib:write(Message) 25 | end. 26 | %%-------------------------------------------------------------------- 27 | %% @doc 28 | %% @spec 29 | %% @end 30 | %%-------------------------------------------------------------------- 31 | 32 | %%%=================================================================== 33 | %%% Internal functions 34 | %%%=================================================================== 35 | walk({function, _Pos, _Name, _Arity, _Clauses} = Function, _Variables, #{step := pre}) -> 36 | Opts = #{traverse => pre}, 37 | Variables = astranaut:sreduce(fun walk_variables/3, sets:new(), Function, Opts), 38 | {Function, Variables}; 39 | walk({function, Pos, Name, Arity, Clauses}, Variables, #{step := post}) -> 40 | {NClauses, NVariables} = walk_clauses(Clauses, {atom, Name}, Variables), 41 | {{function, Pos, Name, Arity, NClauses}, NVariables}; 42 | walk({'fun', Pos, {clauses, Clauses}}, Variables, #{step := post}) -> 43 | {NClauses, NVariables} = walk_clauses(Clauses, undefined, Variables), 44 | {{'fun', Pos, {clauses, NClauses}}, NVariables}; 45 | walk({named_fun, Pos, Name, Clauses}, Variables, #{step := post}) -> 46 | {NClauses, NVariables} = walk_clauses(Clauses, {atom, Name}, Variables), 47 | {{named_fun, Pos, Name, NClauses}, NVariables}; 48 | walk(Node, Variables, _Attr) -> 49 | {Node, Variables}. 50 | 51 | walk_variables({var, _Pos, Name}, Variables, _Attr) -> 52 | sets:add_element(Name, Variables); 53 | walk_variables(_Node, Variables, _Attr) -> 54 | Variables. 55 | 56 | walk_clauses(Clauses, Name, Variables) -> 57 | {NClauses, NVaraibles} = 58 | lists:foldl( 59 | fun(Clause, {CAcc, VAcc}) -> 60 | {NClause, NVAcc} = walk_clause(Clause, Name, VAcc), 61 | {[NClause|CAcc], NVAcc} 62 | end, {[], Variables}, Clauses), 63 | {lists:reverse(NClauses), NVaraibles}. 64 | 65 | walk_clause({clause, Pos, Patterns, Guards, Body}, Name, Variables) -> 66 | {NBody, NVariables} = walk_body(Body, Name, Variables), 67 | {{clause, Pos, Patterns, Guards, NBody}, NVariables}. 68 | 69 | walk_body([{call, _Pos, {Type, _Pos1, FName}, _Args} = Rep], Name, Variables) -> 70 | if 71 | {Type, FName} =:= Name -> 72 | {[Rep], Variables}; 73 | true -> 74 | {NRep, NVariables} = add_try_catch(Rep, Variables), 75 | {[NRep], NVariables} 76 | end; 77 | walk_body([{call, _Pos, {remote, _Pos1, _Module, _Function}, _Args} = Rep], _Name, Variables) -> 78 | {NRep, NVariables} = add_try_catch(Rep, Variables), 79 | {[NRep], NVariables}; 80 | walk_body([H], _Name, Variables) -> 81 | {[H], Variables}; 82 | walk_body([H|T], Name, Variables) -> 83 | {NT, NVariables} = walk_body(T, Name, Variables), 84 | {[H|NT], NVariables}; 85 | walk_body([], _Name, Variables) -> 86 | {[], Variables}. 87 | 88 | add_try_catch({call, Pos, _Fun, _Args} = Expr, Variables) -> 89 | Class = 90 | erl_syntax_lib:new_variable_name( 91 | fun(N) -> list_to_atom("Class" ++ integer_to_list(N)) end, Variables), 92 | Exception = 93 | erl_syntax_lib:new_variable_name( 94 | fun(N) -> list_to_atom("Exception" ++ integer_to_list(N)) end, Variables), 95 | StackTrace = 96 | erl_syntax_lib:new_variable_name( 97 | fun(N) -> list_to_atom("StackTrace" ++ integer_to_list(N)) end, Variables), 98 | NVariables = sets:union(sets:from_list([Class, Exception, StackTrace]), Variables), 99 | ClassVar = {var, Pos, Class}, 100 | ExceptionVar = {var, Pos, Exception}, 101 | StackTraceVar = {var, Pos, StackTrace}, 102 | Node = try_catch_node(Expr, Pos, ClassVar, ExceptionVar, StackTraceVar), 103 | {Node, NVariables}. 104 | 105 | -ifdef('OTP_RELEASE'). 106 | try_catch_node(Expr, Pos, ClassVar, ExceptionVar, StackTraceVar) -> 107 | quote( 108 | try 109 | unquote(Expr) 110 | catch 111 | _@ClassVar:_@ExceptionVar:_@StackTraceVar -> 112 | erlang:raise(_@ClassVar, _@ExceptionVar, _@StackTraceVar) 113 | end, Pos). 114 | -else. 115 | try_catch_node(Expr, Pos, ClassVar, ExceptionVar, _StackTraceVar) -> 116 | quote( 117 | try 118 | unquote(Expr) 119 | catch 120 | _@ClassVar:_@ExceptionVar -> 121 | erlang:raise(_@ClassVar, _@ExceptionVar, erlang:get_stacktrace()) 122 | end, Pos). 123 | -endif. 124 | -------------------------------------------------------------------------------- /src/astranaut_do.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2020, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 2 Jul 2020 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_do). 10 | 11 | -include("quote.hrl"). 12 | 13 | %% API 14 | -export([do/2]). 15 | -export([parse_transform/2, format_error/1]). 16 | 17 | %%%=================================================================== 18 | %%% API 19 | %%%=================================================================== 20 | -spec do(erl_syntax:abstract(), #{monad := module(), monad_fail := module()}) -> 21 | astranaut_traverse:return(erl_syntax:abstract()). 22 | do({lc, Pos, Monad, Comprehensions}, Opts) -> 23 | astranaut_return:lift_m( 24 | fun(Expressions) -> 25 | quote((fun() -> 26 | unquote_splicing(Expressions) 27 | end)(), Pos) 28 | end, do_comprehensions(Comprehensions, Monad, Opts)); 29 | do(_Ast, #{}) -> 30 | {error, expected_list_comprehension}. 31 | 32 | parse_transform(Forms, _Opts) -> 33 | TransformOpts = #{formatter => ?MODULE, traverse => post}, 34 | astranaut_return:to_compiler(astranaut:map(fun walk_node/2, Forms, TransformOpts)). 35 | 36 | format_error(non_empty_do) -> 37 | "A 'do' construct cannot be empty"; 38 | format_error(last_generate_expression) -> 39 | "The last expression in a 'do' construct count not be a generate expression: A <- B"; 40 | format_error(Reason) -> 41 | astranaut:format_error(Reason). 42 | %%%=================================================================== 43 | %%% Internal functions 44 | %%%=================================================================== 45 | walk_node({call, _Pos, {atom, __Pos1, do}, [{lc, _Pos2, _Monad, _Comprehensions} = LCNode]}, #{}) -> 46 | do(LCNode, #{monad => astranaut_monad, monad_fail => astranaut_monad}); 47 | walk_node(Node, _Attrs) -> 48 | Node. 49 | 50 | %% 'do' syntax transformation: 51 | do_comprehensions([], _Monad, #{}) -> 52 | astranaut_return:error_fail(non_empty_do); 53 | do_comprehensions([{GenerateOrMatch, _Pos, _Pattern, _Expr}], _Monad, #{}) 54 | when GenerateOrMatch =:= generate orelse GenerateOrMatch =:= match -> 55 | astranaut_return:error_fail(last_generate_expression); 56 | do_comprehensions([Expr], Monad, #{} = Opts) -> 57 | %% Don't do '>>' chaining on the last elem 58 | astranaut_return:return([update_expression(Expr, Monad, Opts)]); 59 | do_comprehensions([Expr | Exprs], Monad, Opts) -> 60 | Expr1 = update_expression(Expr, Monad, Opts), 61 | astranaut_return:lift_m( 62 | fun(Exprs1) -> 63 | bind_expression(Expr1, Exprs1, Monad, Opts) 64 | end, do_comprehensions(Exprs, Monad, Opts)). 65 | 66 | bind_expression({generate, Pos, {var, _Pos, _Var} = Pattern, Expr}, Exprs, Monad, #{monad := MonadModule}) -> 67 | %% "Pattern <- Expr, Tail" where Pattern is a simple variable 68 | %% is transformed to 69 | %% "Monad:Bind(Expr, fun (Pattern) -> Tail end)" 70 | %% without a fail to match clause 71 | [quote(_A@MonadModule:bind( 72 | unquote(Expr), 73 | fun(unquote = Pattern) -> 74 | unquote_splicing(Exprs) 75 | end, unquote(Monad)), Pos)]; 76 | bind_expression({generate, Pos, Pattern, Expr}, Exprs, Monad, #{monad := MonadModule}) -> 77 | PosExpr = astranaut_lib:abstract_form(Pos, Pos), 78 | String = astranaut_lib:abstract_form(astranaut_lib:ast_to_string(Pattern), Pos), 79 | %% "Pattern <- Expr, Tail" where Pattern is not a simple variable 80 | %% is transformed to 81 | %% "Monad:Bind(Expr, fun (Pattern) -> Tail; (Var) -> exit({monad_badmatch, Var, Pos, PatternString}) end)" 82 | %% without a fail to match clause 83 | [quote(_A@MonadModule:bind( 84 | unquote(Expr), 85 | fun(unquote = Pattern) -> 86 | unquote_splicing(Exprs); 87 | (Var) -> 88 | exit({monad_badmatch, Var, unquote(PosExpr), unquote(String)}) 89 | end, unquote(Monad)), Pos)]; 90 | bind_expression({match, _Pos, _Pattern, _Expr} = Expr, Exprs, _Monad, #{}) -> 91 | %% Handles 'let binding' in do expression a-la Haskell 92 | %% Value = Expr, Tail is not transformed. 93 | [Expr|Exprs]; 94 | bind_expression(Expr, Exprs, Monad, #{monad := MonadModule}) -> 95 | %% Expr, Tail 96 | %% is transformed to 97 | %% "Monad:bind(Expr, fun(_) -> Tail end) 98 | Pos = astranaut_syntax:get_pos(Expr), 99 | [quote(_A@MonadModule:bind(unquote(Expr), fun(_) -> unquote_splicing(Exprs) end, unquote(Monad)), Pos)]. 100 | 101 | update_expression(Expression, Monad, #{monad := MonadModule, monad_fail := MonadFailModule}) -> 102 | astranaut:smap( 103 | fun({call, Pos, {atom, _Pos1, fail}, [Arg]}) -> 104 | %% 'return' calls of a particular form: 105 | %% return(Argument), and 106 | %% Transformed to: 107 | %% "Monad:return(Argument)" in monadic context 108 | quote(_A@MonadFailModule:fail(unquote(Arg), unquote(Monad)), Pos); 109 | ({call, Pos, {atom, _Pos1, return}, [Arg]}) -> 110 | %% 'fail' calls of a particular form: 111 | %% fail(Argument) 112 | %% Transformed to: 113 | %% "Monad:fail(Argument)" in monadic context 114 | quote(_A@MonadModule:return(unquote(Arg), unquote(Monad)), Pos); 115 | (Node) -> 116 | Node 117 | end, Expression, #{traverse => pre}). 118 | -------------------------------------------------------------------------------- /src/astranaut_monad.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2021, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 2 Apr 2021 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_monad). 10 | 11 | %% API 12 | -export([bind/3, return/2]). 13 | -export([lift_m/3, sequence_m/2, map_m/3]). 14 | -export([lift_m/4, sequence_m/3, map_m/4, map_m_flatten/4]). 15 | -export([identity_bind/0, identity_return/0]). 16 | -export([maybe_bind/0, maybe_return/0]). 17 | -export([either_bind/0, either_return/0]). 18 | -export([reader_bind/1, reader_return/1, reader_lift/0, reader_ask/1, reader_local/0, reader_state/1]). 19 | -export([state_bind/1, state_return/1, state_lift/2, state_ask/2, state_local/1, state_state/1]). 20 | -export([writer_bind/3, writer_return/2, writer_lift/3]). 21 | -export([writer_ask/2, writer_local/1, writer_state/2]). 22 | -export([writer_writer/1, writer_listen/2]). 23 | -export([monad_bind/1, monad_return/1, monad_lift/1]). 24 | -export([monad_state/1, monad_ask/1, monad_local/1]). 25 | -export([monad_writer_updated/1, monad_listen_updated/1]). 26 | -export([monad_catch_on_error/1, monad_fail_on_error/1]). 27 | -export([mappend/1, mempty/1]). 28 | 29 | -export_type([maybe/1, either/2, state/2]). 30 | -export_type([monad/2, monad_bind/1, monad_return/1]). 31 | -export_type([monad_ask/1, monad_local/1, monad_state/1, monad_writer/1, monad_listen/1, monad_lift/1]). 32 | 33 | -type maybe(A) :: {just, A} | nothing. 34 | -type either(E, A) :: {left, E} | {right, A}. 35 | -type state(S, A) :: fun((S) -> {A, S}). 36 | 37 | -type monad(_M, _A) :: any(). 38 | -type monad_bind(M) :: fun((monad(M, A), fun((A) -> monad(M, B))) -> monad(M, B)). 39 | -type monad_return(M) :: fun((A) -> monad(M, A)). 40 | -type monad_ask(M) :: fun(() -> monad(M, _A)). 41 | -type monad_local(M) :: fun((fun((R) -> R), monad(M, A)) -> monad(M, A)). 42 | -type monad_state(M) :: fun((fun((S) -> {A, S})) -> monad(M, A)). 43 | -type monad_writer(M) :: fun(({A, _W}) -> monad(M, A)). 44 | -type monad_listen(M) :: fun((monad(M, A)) -> monad(M, {A, _W})). 45 | -type monad_lift(T) :: fun((monad(M, A)) -> monad({T, M}, A)). 46 | %%%=================================================================== 47 | %%% API 48 | %%%=================================================================== 49 | bind(ok, KMB, _Monad) -> 50 | KMB(ok); 51 | bind(MA, KMB, Monad) -> 52 | Bind = monad_bind(Monad), 53 | Bind(MA, KMB). 54 | 55 | return(A, Monad) -> 56 | Return = monad_return(Monad), 57 | Return(A). 58 | 59 | -spec lift_m(fun((A) -> B), monad(M, A), M) -> monad(M, B). 60 | lift_m(F, MA, Monad) -> 61 | lift_m(F, MA, monad_bind(Monad), monad_return(Monad)). 62 | 63 | -spec lift_m(fun((A) -> B), monad(M, A), monad_bind(M), monad_return(M)) -> monad(M, B). 64 | lift_m(F, MA, Bind, Return) -> 65 | Bind(MA, fun(A) -> Return(F(A)) end). 66 | 67 | -spec map_m(fun((A) -> monad(M, B)), [A], M) -> monad(M, [B]). 68 | map_m(F, As, Monad) -> 69 | map_m(F, As, monad_bind(Monad), monad_return(Monad)). 70 | 71 | map_m(F, As, Bind, Return) -> 72 | map_m_fold(F, As, fun(A1, As1) -> [A1|As1] end, Bind, Return). 73 | 74 | -spec map_m_flatten(fun((A) -> monad(M, B)), [A], monad_bind(M), monad_return(M)) -> monad(M, [B]). 75 | map_m_flatten(F, As, Bind, Return) -> 76 | Fold = fun(AHs1, As1) when is_list(AHs1) -> 77 | AHs1 ++ As1; 78 | (A1, As1) -> 79 | [A1|As1] 80 | end, 81 | map_m_fold(F, As, Fold, Bind, Return). 82 | 83 | map_m_fold(F, [A|As], Fold, Bind, Return) -> 84 | Bind( 85 | F(A), 86 | fun(A1) -> 87 | Bind( 88 | map_m_fold(F, As, Fold, Bind, Return), 89 | fun(As1) -> 90 | Return(Fold(A1, As1)) 91 | end) 92 | end); 93 | map_m_fold(_F, [], __Fold, _Bind, Return) -> 94 | Return([]). 95 | 96 | -spec sequence_m([monad(M, A)], M) -> monad(M, [A]). 97 | sequence_m(MAs, Monad) -> 98 | sequence_m(MAs, monad_bind(Monad), monad_return(Monad)). 99 | 100 | -spec sequence_m([monad(M, A)], monad_bind(M), monad_return(M)) -> monad(M, [A]). 101 | sequence_m(MAs, Bind, Return) -> 102 | map_m(fun(A) -> A end, MAs, Bind, Return). 103 | 104 | -spec identity_bind() -> fun((A, fun((A) -> B)) -> B). 105 | identity_bind() -> 106 | fun(A, KIB) -> 107 | KIB(A) 108 | end. 109 | 110 | -spec identity_return() -> fun((A) -> A). 111 | identity_return() -> 112 | fun(A) -> A end. 113 | 114 | -spec maybe_bind() -> fun((maybe(A), fun((A) -> maybe(B))) -> maybe(B)). 115 | maybe_bind() -> 116 | fun(MA, AFB) -> 117 | case MA of 118 | {just, A} -> 119 | AFB(A); 120 | nothing -> 121 | nothing 122 | end 123 | end. 124 | 125 | -spec maybe_return() -> fun((A) -> maybe(A)). 126 | maybe_return() -> 127 | fun(A) -> 128 | {just, A} 129 | end. 130 | 131 | -spec either_bind() -> fun((either(E, A), fun((A) -> either(E, B))) -> either(E, B)). 132 | either_bind() -> 133 | fun({left, E}, _AFB) -> 134 | {left, E}; 135 | ({right, A}, AFB) -> 136 | AFB(A) 137 | end. 138 | 139 | -spec either_return() -> fun((A) -> either(_E, A)). 140 | either_return() -> 141 | fun(A) -> 142 | {right, A} 143 | end. 144 | 145 | reader_bind(Bind) -> 146 | fun(RA, KRB) -> 147 | fun(R) -> 148 | Bind( 149 | RA(R), 150 | fun(A) -> 151 | (KRB(A))(R) 152 | end) 153 | end 154 | end. 155 | 156 | reader_return(Return) -> 157 | fun(A) -> 158 | fun(_R) -> 159 | Return(A) 160 | end 161 | end. 162 | 163 | reader_lift() -> 164 | fun(MA) -> 165 | fun(_R) -> 166 | MA 167 | end 168 | end. 169 | 170 | reader_ask(Return) -> 171 | fun() -> 172 | fun(R) -> 173 | Return(R) 174 | end 175 | end. 176 | 177 | reader_local() -> 178 | fun(F, RA) -> 179 | fun(R) -> 180 | RA(F(R)) 181 | end 182 | end. 183 | 184 | reader_state(State) -> 185 | Lift = reader_lift(), 186 | fun(F) -> 187 | Lift(State(F)) 188 | end. 189 | 190 | state_bind(Bind) -> 191 | fun(SA, KSB) -> 192 | fun(S) -> 193 | Bind( 194 | SA(S), 195 | fun({A, S1}) -> 196 | SB = KSB(A), 197 | SB(S1) 198 | end) 199 | end 200 | end. 201 | 202 | state_return(Return) -> 203 | fun(A) -> 204 | fun(S) -> Return({A, S}) end 205 | end. 206 | 207 | state_lift(Bind, Return) -> 208 | fun(MA) -> 209 | fun(S) -> lift_m(fun(A) -> {A, S} end, MA, Bind, Return) end 210 | end. 211 | 212 | state_state(Return) -> 213 | fun(F) -> 214 | fun(S) -> 215 | Return(F(S)) 216 | end 217 | end. 218 | 219 | state_ask(Lift, Ask) -> 220 | Lift(Ask()). 221 | 222 | state_local(Local) -> 223 | fun(F, SA) -> 224 | fun(S) -> 225 | Local(F, SA(S)) 226 | end 227 | end. 228 | 229 | writer_bind(Bind, Return, Mappend) -> 230 | fun(WA, KWB) -> 231 | Bind( 232 | WA, 233 | fun({A, W1}) -> 234 | Bind( 235 | KWB(A), 236 | fun({B, W2}) -> 237 | W3 = Mappend(W1, W2), 238 | Return({B, W3}) 239 | end) 240 | end) 241 | end. 242 | 243 | writer_return(Return, Mempty) -> 244 | fun(A) -> 245 | Return({A, Mempty()}) 246 | end. 247 | 248 | writer_lift(Bind, Return, Mempty) -> 249 | fun(MA) -> 250 | lift_m(fun(A) -> {A, Mempty()} end, MA, Bind, Return) 251 | end. 252 | 253 | writer_ask(Lift, Ask) -> 254 | fun() -> 255 | Lift(Ask()) 256 | end. 257 | 258 | writer_local(Local) -> 259 | fun(F, WA) -> 260 | Local(F, WA) 261 | end. 262 | 263 | writer_state(Lift, State) -> 264 | fun(F) -> 265 | Lift(State(F)) 266 | end. 267 | 268 | writer_writer(Return) -> 269 | fun({A, W}) -> 270 | Return({A, W}) 271 | end. 272 | 273 | writer_listen(Bind, Return) -> 274 | fun(WA) -> 275 | lift_m(fun({A, Ws}) -> {{A, Ws}, Ws} end, WA, Bind, Return) 276 | end. 277 | 278 | -spec monad_bind(M) -> monad_bind(M). 279 | monad_bind(reader) -> 280 | reader_bind(identity_bind()); 281 | monad_bind({reader, M}) -> 282 | Bind = monad_bind(M), 283 | reader_bind(Bind); 284 | monad_bind(state) -> 285 | state_bind(identity_bind()); 286 | monad_bind({state, M}) -> 287 | Bind = monad_bind(M), 288 | state_bind(Bind); 289 | monad_bind(either) -> 290 | either_bind(); 291 | monad_bind(maybe) -> 292 | maybe_bind(); 293 | monad_bind(identity) -> 294 | identity_bind(); 295 | monad_bind(traverse) -> 296 | fun astranaut_traverse:bind/2; 297 | monad_bind(return) -> 298 | fun astranaut_return:bind/2. 299 | 300 | -spec monad_return(M) -> monad_return(M). 301 | monad_return(reader) -> 302 | reader_return(identity_return()); 303 | monad_return({reader, M}) -> 304 | Return = monad_return(M), 305 | reader_return(Return); 306 | monad_return(state) -> 307 | state_return(identity_return()); 308 | monad_return({state, M}) -> 309 | Return = monad_return(M), 310 | state_return(Return); 311 | monad_return(either) -> 312 | either_return(); 313 | monad_return(maybe) -> 314 | maybe_return(); 315 | monad_return(identity) -> 316 | identity_return(); 317 | monad_return(traverse) -> 318 | fun astranaut_traverse:return/1; 319 | monad_return(return) -> 320 | fun astranaut_return:return/1. 321 | 322 | monad_lift({reader, _M}) -> 323 | reader_lift(); 324 | monad_lift({state, M}) -> 325 | Bind = monad_bind(M), 326 | Return = monad_return(M), 327 | state_lift(Bind, Return). 328 | 329 | monad_state(reader) -> 330 | undefined; 331 | monad_state({reader, M}) -> 332 | case monad_state(M) of 333 | undefined -> 334 | undefined; 335 | State -> 336 | reader_state(State) 337 | end; 338 | monad_state(state) -> 339 | state_state(identity_return()); 340 | monad_state({state, M}) -> 341 | Return = monad_return(M), 342 | state_state(Return); 343 | monad_state(identity) -> 344 | undefined; 345 | monad_state(maybe) -> 346 | undefined; 347 | monad_state(either) -> 348 | undefined; 349 | monad_state(traverse) -> 350 | fun astranaut_traverse:state/1. 351 | 352 | monad_ask(reader) -> 353 | reader_ask(identity_return()); 354 | monad_ask({reader, M}) -> 355 | Return = monad_return(M), 356 | reader_ask(Return); 357 | monad_ask(state) -> 358 | undefined; 359 | monad_ask({state, M}) -> 360 | case monad_ask(M) of 361 | undefined -> 362 | undefined; 363 | Ask -> 364 | Lift = monad_lift({state, M}), 365 | state_ask(Lift, Ask) 366 | end; 367 | monad_ask(identity) -> 368 | undefined; 369 | monad_ask(maybe) -> 370 | undefined; 371 | monad_ask(either) -> 372 | undefined; 373 | monad_ask(traverse) -> 374 | fun astranaut_traverse:ask/0; 375 | monad_ask(return) -> 376 | undefined. 377 | 378 | monad_local(reader) -> 379 | reader_local(); 380 | monad_local({reader, _M}) -> 381 | reader_local(); 382 | monad_local({state, M}) -> 383 | case monad_local(M) of 384 | undefined -> 385 | undefined; 386 | Local -> 387 | state_local(Local) 388 | end; 389 | monad_local(state) -> 390 | undefined; 391 | monad_local(identity) -> 392 | undefined; 393 | monad_local(maybe) -> 394 | undefined; 395 | monad_local(either) -> 396 | undefined; 397 | monad_local(traverse) -> 398 | fun astranaut_traverse:local/2; 399 | monad_local(return) -> 400 | undefined. 401 | 402 | monad_writer_updated(traverse) -> 403 | fun astranaut_traverse:writer_updated/1; 404 | monad_writer_updated(_) -> 405 | undefined. 406 | 407 | monad_listen_updated(traverse) -> 408 | fun astranaut_traverse:listen_updated/1; 409 | monad_listen_updated(_) -> 410 | undefined. 411 | 412 | monad_catch_on_error(traverse) -> 413 | fun astranaut_traverse:catch_on_error/2; 414 | monad_catch_on_error(_) -> 415 | undefined. 416 | 417 | monad_fail_on_error(traverse) -> 418 | fun astranaut_traverse:fail_on_error/1; 419 | monad_fail_on_error(_) -> 420 | undefined. 421 | 422 | mappend('or') -> 423 | fun(A, B) -> A or B end. 424 | 425 | mempty('or') -> 426 | fun() -> false end. 427 | -------------------------------------------------------------------------------- /src/astranaut_return.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2020, Chen Slepher 4 | %%% @doc 5 | %%% return type of {@link astranaut:map/3}, {@link astranaut:reduce/4}, {@link astranaut:mapfold/4}. 6 | %%% @end 7 | %%% Created : 8 Jul 2020 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_return). 10 | 11 | -include("astranaut_struct_name.hrl"). 12 | 13 | -export_type([struct/1]). 14 | 15 | -type struct(A) :: astranaut_return_ok(A) | astranaut_return_fail(). 16 | 17 | -type astranaut_return_ok(A) :: #{?STRUCT_KEY := ?RETURN_OK, 18 | return := A, 19 | error := astranaut_error:struct()}. 20 | 21 | -type astranaut_return_fail() :: #{?STRUCT_KEY := ?RETURN_FAIL, 22 | error := astranaut_error:struct()}. 23 | 24 | -compile({no_auto_import, [error/1]}). 25 | 26 | -export([ok/1, ok/2, fail/0, fail/1]). 27 | -export([warning_ok/2, warnings_ok/2, error_ok/2, errors_ok/2, error_fail/1, errors_fail/1]). 28 | -export([run/1, run_error/1]). 29 | -export([from_compiler/1]). 30 | -export([lift_m/2, map_m/2, sequence_m/1, foldl_m/3]). 31 | -export([to_monad/1]). 32 | -export([to_compiler/1]). 33 | -export([simplify/1]). 34 | -export([bind/2, return/1]). 35 | -export([then/2]). 36 | -export([with_error/2]). 37 | -export([error/1, errors/1, warning/1, warnings/1]). 38 | -export([formatted_error/1, formatted_error/3, formatted_errors/1]). 39 | -export([formatted_warning/1, formatted_warning/3, formatted_warnings/1]). 40 | -export([has_error/1]). 41 | 42 | %%%=================================================================== 43 | %%% API 44 | %%%=================================================================== 45 | -spec ok(A) -> struct(A). 46 | ok(Return) -> 47 | ok(Return, astranaut_error:new()). 48 | 49 | -spec ok(A, astranaut_error:struct()) -> struct(A). 50 | ok(Return, #{?STRUCT_KEY := ?ERROR_STATE} = Error) -> 51 | #{?STRUCT_KEY => ?RETURN_OK, return => Return, error => Error}. 52 | 53 | -spec fail() -> struct(ok). 54 | fail() -> 55 | fail(astranaut_error:new()). 56 | 57 | -spec fail(astranaut_error:struct()) -> struct(ok). 58 | fail(#{?STRUCT_KEY := ?ERROR_STATE} = Error) -> 59 | #{?STRUCT_KEY => ?RETURN_FAIL, error => Error}. 60 | 61 | warning_ok(Warning, Return) -> 62 | then(warning(Warning), ok(Return)). 63 | 64 | warnings_ok(Warnings, Return) when is_list(Warnings) -> 65 | then(warnings(Warnings), ok(Return)). 66 | 67 | error_ok(Error, Return) -> 68 | then(error(Error), ok(Return)). 69 | 70 | errors_ok(Errors, Return) when is_list(Errors) -> 71 | then(errors(Errors), ok(Return)). 72 | 73 | error_fail(Error) -> 74 | then(error(Error), fail()). 75 | 76 | errors_fail(Errors) when is_list(Errors) -> 77 | then(errors(Errors), fail()). 78 | 79 | -spec run(struct(A)) -> {just, A} | nothing. 80 | run(#{?STRUCT_KEY := ?RETURN_OK, return := Return}) -> 81 | {just, Return}; 82 | run(#{?STRUCT_KEY := ?RETURN_FAIL}) -> 83 | nothing. 84 | 85 | -spec run_error(struct(_A)) -> astranaut_error:struct(). 86 | run_error(#{?STRUCT_KEY := StructKey, error := Error}) 87 | when (StructKey =:= ?RETURN_OK); (StructKey =:= ?RETURN_FAIL) -> 88 | Error. 89 | 90 | -spec lift_m(fun((A) -> B), struct(A)) -> struct(B). 91 | lift_m(F, X) -> 92 | bind(X, fun(A) -> return(F(A)) end). 93 | 94 | -spec map_m(fun((A) -> struct(B)), [struct(A)]) -> struct(B). 95 | map_m(F, [X|Xs]) -> 96 | bind(F(X), 97 | fun(A) -> 98 | bind(map_m(F, Xs), 99 | fun(As) -> 100 | return([A|As]) 101 | end) 102 | end); 103 | map_m(_F, []) -> 104 | return([]). 105 | 106 | -spec sequence_m([struct(A)]) -> struct([A]). 107 | sequence_m(Xs) -> 108 | map_m(fun(A) -> A end, Xs). 109 | 110 | -spec foldl_m(fun((A, S) -> struct(S)), S, [A]) -> struct(S). 111 | foldl_m(F, Acc, [X|Xs]) -> 112 | bind( 113 | F(X, Acc), 114 | fun(Acc1) -> 115 | foldl_m(F, Acc1, Xs) 116 | end); 117 | foldl_m(_F, Acc, []) -> 118 | return(Acc). 119 | 120 | from_compiler(CompilerReturn) -> 121 | astranaut_lib:concerete(CompilerReturn, [fun from_compiler_1/1]). 122 | 123 | from_compiler_1(Forms) when is_list(Forms) -> 124 | {ok, ok(Forms)}; 125 | from_compiler_1(Module) when is_atom(Module) -> 126 | {ok, ok(Module)}; 127 | from_compiler_1({warning, Forms, Warnings}) when is_list(Warnings) -> 128 | ErrorStruct = astranaut_error:new(), 129 | ErrorStruct1 = astranaut_error:append_file_warnings(Warnings, ErrorStruct), 130 | {ok, ok(Forms, ErrorStruct1)}; 131 | from_compiler_1({error, Errors, Warnings}) when is_list(Warnings), is_list(Errors) -> 132 | ErrorStruct = astranaut_error:new(), 133 | ErrorStruct1 = astranaut_error:append_file_warnings(Warnings, ErrorStruct), 134 | ErrorStruct2 = astranaut_error:append_file_errors(Errors, ErrorStruct1), 135 | {ok, fail(ErrorStruct2)}; 136 | from_compiler_1(_Other) -> 137 | error. 138 | 139 | from_return(#{?STRUCT_KEY := ?RETURN_OK} = MA) -> 140 | {ok, MA}; 141 | from_return(#{?STRUCT_KEY := ?RETURN_FAIL} = MA) -> 142 | {ok, MA}; 143 | from_return(_) -> 144 | error. 145 | 146 | from_walk_return(#{?STRUCT_KEY := ?WALK_RETURN, return := Return, errors := Errors, warnings := Warnings}) -> 147 | {ok, 148 | astranaut_return:then( 149 | astranaut_return:then( 150 | astranaut_return:warnings(Warnings), 151 | astranaut_return:errors(Errors)), 152 | astranaut_return:return(Return))}; 153 | from_walk_return(_) -> 154 | error. 155 | 156 | to_monad(A) -> 157 | astranaut_lib:concerete(A, [fun from_return/1, fun from_walk_return/1, fun from_compiler_1/1]). 158 | 159 | -spec to_compiler(struct([erl_syntax:syntaxTree()])) -> 160 | [erl_syntax:syntaxTree()] | {warning, [erl_syntax:syntaxTree()], astranaut_error:compiler_error()} | 161 | {error, astranaut_error:compiler_error(), astranaut_error:compiler_error()}. 162 | to_compiler(#{?STRUCT_KEY := ?RETURN_OK, return := Forms, error := Error}) -> 163 | case astranaut_error:realize(Error) of 164 | {[], []} -> 165 | Forms; 166 | {[], Warnings} -> 167 | {warning, Forms, Warnings}; 168 | {Errors, Warnings} -> 169 | {error, Errors, Warnings} 170 | end; 171 | to_compiler(#{?STRUCT_KEY := ?RETURN_FAIL, error := Error}) -> 172 | {Errors, Warnings} = astranaut_error:realize(Error), 173 | {error, Errors, Warnings}. 174 | 175 | -spec simplify(struct(A)) -> A | no_return(). 176 | simplify(#{?STRUCT_KEY := ?RETURN_OK, return := Return, error := Error}) -> 177 | case astranaut_error:is_empty(Error) of 178 | true -> 179 | Return; 180 | false -> 181 | exit(astranaut_error:printable(Error)) 182 | end; 183 | simplify(#{?STRUCT_KEY := ?RETURN_FAIL, error := Error}) -> 184 | exit(astranaut_error:printable(Error)). 185 | 186 | -spec bind(struct(A) | ok, fun((A) -> struct(B))) -> struct(B). 187 | bind(ok, KMB) -> 188 | KMB(ok); 189 | bind(MA, KMB) -> 190 | MA1 = to_monad(MA), 191 | bind_1(MA1, KMB). 192 | 193 | -spec return(A) -> struct(A). 194 | return(A) -> 195 | ok(A). 196 | 197 | -spec then(struct(_A), struct(B)) -> struct(B). 198 | then(MA, MB) -> 199 | bind(MA, fun(_) -> MB end). 200 | 201 | with_error(F, #{?STRUCT_KEY := ?RETURN_OK, error := Error} = MA) -> 202 | Error1 = F(Error), 203 | MA#{error => Error1}; 204 | with_error(F, #{?STRUCT_KEY := ?RETURN_FAIL, error := Error} = MA) -> 205 | Error1 = F(Error), 206 | MA#{error => Error1}. 207 | 208 | error(Error) -> 209 | errors([Error]). 210 | 211 | errors(Errors) -> 212 | ErrorStruct = astranaut_error:new(), 213 | ErrorStruct1 = astranaut_error:append_errors(Errors, ErrorStruct), 214 | ok(ok, ErrorStruct1). 215 | 216 | warning(Warning) -> 217 | warnings([Warning]). 218 | 219 | warnings(Warnings) -> 220 | ErrorStruct = astranaut_error:new(), 221 | ErrorStruct1 = astranaut_error:append_warnings(Warnings, ErrorStruct), 222 | ok(ok, ErrorStruct1). 223 | 224 | formatted_error({Pos, Formatter, Error}) -> 225 | formatted_error(Pos, Formatter, Error). 226 | 227 | formatted_error(Pos, Formatter, Error) -> 228 | formatted_errors([{Pos, Formatter, Error}]). 229 | 230 | formatted_errors(Errors) -> 231 | ErrorStruct = astranaut_error:new(), 232 | ErrorStruct1 = astranaut_error:append_formatted_errors(Errors, ErrorStruct), 233 | ok(ok, ErrorStruct1). 234 | 235 | formatted_warning({Pos, Formatter, Warning}) -> 236 | formatted_warning(Pos, Formatter, Warning). 237 | 238 | formatted_warning(Pos, Formatter, Warning) -> 239 | formatted_warnings([{Pos, Formatter, Warning}]). 240 | 241 | formatted_warnings(Warnings) -> 242 | ErrorStruct = astranaut_error:new(), 243 | ErrorStruct1 = astranaut_error:append_formatted_warnings(Warnings, ErrorStruct), 244 | ok(ok, ErrorStruct1). 245 | 246 | has_error(#{error := ErrorStruct}) -> 247 | not astranaut_error:is_empty_error(ErrorStruct). 248 | %%%=================================================================== 249 | %%% Internal functions 250 | %%%=================================================================== 251 | bind_1(#{?STRUCT_KEY := ?RETURN_OK, return := A, error := Error0}, KMB) -> 252 | #{error := Error1} = MB = to_monad(KMB(A)), 253 | Error2 = astranaut_error:merge(Error0, Error1), 254 | MB#{error => Error2}; 255 | bind_1(#{?STRUCT_KEY := ?RETURN_FAIL} = MA, _KMB) -> 256 | MA. 257 | -------------------------------------------------------------------------------- /src/astranaut_syntax.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2021, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 18 Mar 2021 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_syntax). 10 | 11 | -type form() :: erl_parse:abstract_form(). 12 | 13 | %% API 14 | -export([type/1, get_pos/1, set_pos/2, is_pos/1, is_leaf/1]). 15 | -export([subtrees/1, update_tree/2, revert/1]). 16 | -export([subtrees_pge/3, attribute_subtrees_type/3]). 17 | -export([pattern_node/1, guard_node/1, expression_node/1, update_node/2]). 18 | -export([reorder_updated_forms/1, sort_forms/1, insert_forms/2]). 19 | 20 | type(Node) -> 21 | erl_syntax:type(Node). 22 | 23 | get_pos(Node) -> 24 | erl_syntax:get_pos(Node). 25 | 26 | set_pos(Node, Pos) -> 27 | case erl_syntax:is_tree(Node) of 28 | true -> 29 | erl_syntax:set_pos(Node, Pos); 30 | false -> 31 | %% unwrap node if node is a wrapper. 32 | Node1 = erl_syntax:revert(Node), 33 | set_pos_1(Node1, Pos) 34 | end. 35 | 36 | set_pos_1({error, {_, Formatter, Error}}, Pos) -> 37 | {error, {Pos, Formatter, Error}}; 38 | set_pos_1({warning, {_, Formatter, Warning}}, Pos) -> 39 | {warnings, {Pos, Formatter, Warning}}; 40 | set_pos_1(Node, Pos) -> 41 | setelement(2, Node, Pos). 42 | 43 | is_pos(Pos) -> 44 | case Pos of 45 | Pos when is_integer(Pos) -> 46 | true; 47 | {Line, Column} when is_integer(Line), is_integer(Column) -> 48 | true; 49 | Pos -> 50 | false 51 | end. 52 | 53 | is_leaf(Node) -> 54 | erl_syntax:is_leaf(Node). 55 | 56 | %% as this issue mentioned, it's a bug, but will cause compatibility issue 57 | %% https://github.com/erlang/otp/issues/4529 58 | %% the goal of revert/1 is to fix this without cause compatibility issues. 59 | %% just use astranaut_syntax:subtrees/1 replace of erl_syntax:subtress/1, 60 | %% astranaut_syntax:revert/1 replace of erl_syntax:revert/1. 61 | -spec subtrees(erl_syntax:syntaxTree()) -> [[erl_syntax:syntaxTree()]]. 62 | subtrees({attribute, Pos, Name, {TypeName, TypeBody, TypeParams}}) when Name =:= type; Name =:= opaque -> 63 | NameTree = name_arity_tree(Name, Pos), 64 | TypeNameTree = name_arity_tree(TypeName, Pos), 65 | [[NameTree], [TypeNameTree, TypeBody|TypeParams]]; 66 | subtrees({attribute, Pos, Name, {MFA, Specs}}) when Name =:= spec; Name =:= callback -> 67 | NameTree = name_arity_tree(Name, Pos), 68 | MFATree = mfa_tree(MFA, Pos), 69 | [[NameTree], [MFATree|Specs]]; 70 | subtrees(Node) -> 71 | erl_syntax:subtrees(Node). 72 | 73 | mfa_tree(MFA, Pos) -> 74 | erl_syntax:set_pos(erl_syntax:tuple(lists:map(fun(Element) -> name_arity_tree(Element, Pos) end, tuple_to_list(MFA))), Pos). 75 | 76 | name_arity_tree(Name, Pos) when is_atom(Name) -> 77 | erl_syntax:set_pos(erl_syntax:atom(Name), Pos); 78 | name_arity_tree(Arity, Pos) when is_integer(Arity) -> 79 | erl_syntax:set_pos(erl_syntax:integer(Arity), Pos). 80 | 81 | -spec update_tree(erl_syntax:syntaxTree(), [[erl_syntax:syntaxTree()]]) -> erl_syntax:syntaxTree(). 82 | update_tree(Node, Subtrees) -> 83 | erl_syntax:update_tree(Node, Subtrees). 84 | 85 | revert(Node) -> 86 | case erl_syntax:is_tree(Node) of 87 | false -> 88 | erl_syntax:revert(Node); 89 | true -> 90 | case erl_syntax:type(Node) of 91 | attribute -> 92 | Name = erl_syntax:concrete(erl_syntax:attribute_name(Node)), 93 | Args = erl_syntax:attribute_arguments(Node), 94 | Pos = erl_syntax:get_pos(Node), 95 | revert_attribute(Name, Args, Pos, Node); 96 | _ -> 97 | erl_syntax:revert(Node) 98 | end 99 | end. 100 | 101 | revert_attribute(Name, [TypeNameTree, TypeTree|TypeParamTrees], Pos, _Node) when Name =:= type; Name =:= opaque -> 102 | TypeName = erl_syntax:atom_value(TypeNameTree), 103 | {attribute, Pos, Name, {TypeName, TypeTree, TypeParamTrees}}; 104 | revert_attribute(Name, [MFATree|SpecTrees], Pos, _Node) when Name =:= spec; Name =:= callback -> 105 | MFA = mfa_value(MFATree), 106 | {attribute, Pos, Name, {MFA, SpecTrees}}; 107 | revert_attribute(_Name, _Subtrees, _Pos, Node) -> 108 | erl_syntax:revert(Node). 109 | 110 | mfa_value(MFATree) -> 111 | tuple = erl_syntax:type(MFATree), 112 | list_to_tuple( 113 | lists:map( 114 | fun(MFA) -> 115 | Type = erl_syntax:type(MFA), 116 | name_arity_value(Type, MFA) 117 | end, erl_syntax:tuple_elements(MFATree))). 118 | 119 | name_arity_value(atom, NameTree) -> 120 | erl_syntax:atom_value(NameTree); 121 | name_arity_value(integer, ArityTree) -> 122 | erl_syntax:integer_value(ArityTree). 123 | 124 | subtrees_pge(_Type, Subtrees, #{node := pattern}) -> 125 | Subtrees; 126 | subtrees_pge(named_fun_expr, [Names, Clauses], #{}) -> 127 | [pattern_node(Names), Clauses]; 128 | subtrees_pge(Type, [Patterns, Expressions], #{}) when Type =:= match_expr; Type =:= clause -> 129 | [pattern_node(Patterns), expression_node(Expressions)]; 130 | subtrees_pge(clause, [Patterns, Guards, Expressions], #{}) -> 131 | [pattern_node(Patterns), guard_node(Guards), expression_node(Expressions)]; 132 | subtrees_pge(Type, [Patterns, Expressions], #{}) when Type =:= generator; Type =:= binary_generator -> 133 | [pattern_node(Patterns), expression_node(Expressions)]; 134 | subtrees_pge(_Type, Subtrees, #{}) -> 135 | Subtrees. 136 | 137 | attribute_subtrees_type(attribute, [[NameTree], BodyTrees], #{}) -> 138 | Name = erl_syntax:atom_value(NameTree), 139 | [[NameTree], update_attribute_body_trees(Name, BodyTrees)]; 140 | attribute_subtrees_type(_Type, Subtrees, #{}) -> 141 | Subtrees. 142 | 143 | update_attribute_body_trees(record = Name, [RecordNameTree|RecordBodyTrees]) -> 144 | attribute(Name, [name_node(RecordNameTree)|RecordBodyTrees]); 145 | update_attribute_body_trees(Name, [TypeNameTree, TypeTree|TypeParamTrees]) when Name =:= type; Name =:= opaque -> 146 | attribute(Name, [name_node(TypeNameTree), type_node(TypeTree)|type_param_node(TypeParamTrees)]); 147 | update_attribute_body_trees(Name, [SpecMFATree|SpecTrees]) when Name =:= spec; Name =:= callback -> 148 | attribute(Name, [name_node(SpecMFATree)|type_node(SpecTrees)]); 149 | update_attribute_body_trees(Name, BodyTrees) -> 150 | attribute(Name, BodyTrees). 151 | 152 | pattern_node(Subtree) -> 153 | update_node(pattern, Subtree). 154 | 155 | guard_node(Subtree) -> 156 | update_node(guard, Subtree). 157 | 158 | expression_node(Subtree) -> 159 | update_node(expression, Subtree). 160 | 161 | name_node(Subtree) -> 162 | update_node(name, Subtree). 163 | 164 | type_node(Subtree) -> 165 | update_node(type, Subtree). 166 | 167 | type_param_node(Subtree) -> 168 | update_node(type_param, Subtree). 169 | 170 | attribute(Attribute, Subtree) -> 171 | astranaut_uniplate:up_attr(#{attribute => Attribute}, Subtree). 172 | 173 | update_node(Node, Subtree) -> 174 | astranaut_uniplate:up_attr(#{node => Node}, Subtree). 175 | 176 | %%=================================================================== 177 | %% update forms related functions 178 | %%=================================================================== 179 | -spec reorder_updated_forms([form()]) -> [form()]. 180 | reorder_updated_forms(Forms) -> 181 | Functions = forms_functions(Forms), 182 | reorder_updated_forms(Forms, Functions, grforms_new()). 183 | 184 | reorder_updated_forms([{updated, Form, NewForms}|Tails], Functions0, GRForms) -> 185 | %% get new functions after transformed. 186 | FormFunctions = forms_functions([Form]), 187 | NewFormsFunctions = forms_functions(NewForms), 188 | NewFormsFunctions1 = ordsets:subtract(NewFormsFunctions, FormFunctions), 189 | {Functions1, GRForms1, Tails2} = 190 | insert_forms(NewForms, NewFormsFunctions1, Functions0, GRForms, Tails), 191 | reorder_updated_forms(Tails2, Functions1, GRForms1); 192 | reorder_updated_forms([Form|Tails], Functions, GRForms) -> 193 | reorder_updated_forms(Tails, Functions, grforms_append(Form, GRForms)); 194 | reorder_updated_forms([], _Functions, GRForms) -> 195 | grforms_to_forms(GRForms). 196 | 197 | forms_functions(Forms) -> 198 | forms_functions(Forms, ordsets:new()). 199 | 200 | forms_functions(Forms, Functions0) -> 201 | lists:foldl( 202 | fun({function, _Pos, Name, Arity, _Clauses}, Acc) -> 203 | ordsets:add_element({Name, Arity}, Acc); 204 | (_Node, Acc) -> 205 | Acc 206 | end, Functions0, Forms). 207 | 208 | grforms_new() -> 209 | %% [Eof], [Function...], [Attribute...], [Module...]}. 210 | %% ERForms, FRForms, ARForms, MForms. 211 | {[], [], [], []}. 212 | 213 | grforms_to_forms({ERForms, FRForms, ARForms, MRForms}) -> 214 | lists:reverse(MRForms) ++ lists:reverse(ARForms) ++ lists:reverse(FRForms) ++ ERForms. 215 | 216 | grforms_append({attribute, _Pos, module, _ModuleName} = Module, {[], [], [], MForms}) -> 217 | {[], [], [], [Module|MForms]}; 218 | grforms_append({attribute, _Pos, file, _FileName} = File, {[], [], [], MForms}) -> 219 | {[], [], [], [File|MForms]}; 220 | grforms_append({attribute, _Pos, file, _FileName} = File, {[], [], ARForms, MForms}) -> 221 | {[], [], [File|ARForms], MForms}; 222 | grforms_append({attribute, _Pos, file, _FileName} = File, {[], FRForms, ARForms, MForms}) -> 223 | {[], [File|FRForms], ARForms, MForms}; 224 | grforms_append({attribute, _Pos, export, _Exports} = Export, {[], FRForms, ARForms, MForms}) -> 225 | {[], FRForms, [Export|ARForms], MForms}; 226 | grforms_append({attribute, _Pos, export_type, _Exports} = ExportType, {[], FRForms, ARForms, MForms}) -> 227 | {[], FRForms, [ExportType|ARForms], MForms}; 228 | grforms_append({attribute, _Pos, spec, _SpecValue} = Spec, {ERForms, FRForms, ARForms, MRForms}) -> 229 | {ERForms, [Spec|FRForms], ARForms, MRForms}; 230 | grforms_append({function, _Pos, _Name, _Arity, _Clauses} = Function, {ERForms, FRForms, ARForms, MRForms}) -> 231 | {ERForms, [Function|FRForms], ARForms, MRForms}; 232 | grforms_append({eof, _Pos} = Eof, {[], FRForms, ARForms, MRForms}) -> 233 | {[Eof], FRForms, ARForms, MRForms}; 234 | grforms_append(Form, {[], [], ARForms, MRForms}) -> 235 | {[], [], [Form|ARForms], MRForms}; 236 | grforms_append(Form, {[], FRForms, ARForms, MRForms}) -> 237 | {[], [Form|FRForms], ARForms, MRForms}; 238 | grforms_append(Form, GRForms) -> 239 | erlang:exit({insert_form_failed, Form, GRForms}). 240 | 241 | grforms_insert(Form, GRForms) -> 242 | case erl_syntax:type(Form) of 243 | attribute -> 244 | Name = erl_syntax:concrete(erl_syntax:attribute_name(Form)), 245 | grforms_insert_attribute(Name, Form, GRForms); 246 | comment -> 247 | grforms_insert_comment(Form, GRForms); 248 | function -> 249 | grforms_insert_function(Form, GRForms); 250 | eof_marker -> 251 | grforms_insert_eof(Form, GRForms); 252 | error_marker -> 253 | grforms_insert_error_marker(Form, GRForms); 254 | form_list -> 255 | grforms_insert_form_list(Form, GRForms); 256 | warning_marker -> 257 | grforms_insert_warning_marker(Form, GRForms); 258 | text -> 259 | grforms_insert_text(Form, GRForms); 260 | _ -> 261 | grforms_insert_default(Form, GRForms) 262 | end. 263 | 264 | grforms_insert_attribute(file, File, {ERForms, FRForms, ARForms, []}) -> 265 | {ERForms, FRForms, ARForms, [File]}; 266 | grforms_insert_attribute(file, File, GRForms) -> 267 | grforms_append(File, GRForms); 268 | grforms_insert_attribute(module, Module, {ERForms, FRForms, ARForms, [{attribute, _Pos2, module, _ModuleName1}|MForms]}) -> 269 | {ERForms, FRForms, ARForms, [Module|MForms]}; 270 | grforms_insert_attribute(module, Module, {ERForms, FRForms, ARForms, MForms}) -> 271 | {ERForms, FRForms, ARForms, [Module|MForms]}; 272 | grforms_insert_attribute(export, {attribute, Pos, export, Exports}, {ERForms, FRForms, ARForms, MRForms}) -> 273 | Exports1 = remove_duplicated_exports(Exports, FRForms), 274 | Exports2 = remove_duplicated_exports(Exports1, ARForms), 275 | case Exports2 of 276 | [] -> 277 | {ERForms, FRForms, ARForms, MRForms}; 278 | _ -> 279 | Export = {attribute, Pos, export, Exports2}, 280 | {ERForms, FRForms, [Export|ARForms], MRForms} 281 | end; 282 | grforms_insert_attribute(spec, Spec, {ERForms, FRForms, ARForms, MRForms}) -> 283 | {ERForms, [Spec|FRForms], ARForms, MRForms}; 284 | grforms_insert_attribute(_Name, Attribute, {ERForms, FRForms, ARForms, MRForms}) -> 285 | {ERForms, FRForms, [Attribute|ARForms], MRForms}. 286 | 287 | grforms_insert_comment(Comment, GRForms) -> 288 | grforms_append(Comment, GRForms). 289 | 290 | grforms_insert_function(Function, {ERForms, FRForms, ARForms, MRForms}) -> 291 | {ERForms, [Function|FRForms], ARForms, MRForms}. 292 | 293 | grforms_insert_eof(_Eof1, {[Eof], FRForms, ARForms, MRForms}) -> 294 | {[Eof], FRForms, ARForms, MRForms}; 295 | grforms_insert_eof(Eof, {[], FRForms, ARForms, MRForms}) -> 296 | {[Eof], FRForms, ARForms, MRForms}. 297 | 298 | grforms_insert_error_marker( Error, GRForms) -> 299 | grforms_append(Error, GRForms). 300 | 301 | grforms_insert_warning_marker(Warning, GRForms) -> 302 | grforms_append(Warning, GRForms). 303 | 304 | grforms_insert_form_list(Form, GRForms) -> 305 | lists:foldl(fun grforms_insert/2, GRForms, erl_syntax:form_list_elements(erl_syntax:flatten_form_list(Form))). 306 | 307 | grforms_insert_text(Text, GRForms) -> 308 | grforms_append(Text, GRForms). 309 | 310 | grforms_insert_default(Form, GRForms) -> 311 | grforms_append(Form, GRForms). 312 | 313 | remove_duplicated_exports(Exports1, [{attribute, _Pos, export, Exports}|T]) -> 314 | Exports2 = Exports1 -- Exports, 315 | remove_duplicated_exports(Exports2, T); 316 | remove_duplicated_exports(Exports, [_Form|T]) -> 317 | remove_duplicated_exports(Exports, T); 318 | remove_duplicated_exports([], _Forms) -> 319 | []; 320 | remove_duplicated_exports(Exports, []) -> 321 | Exports. 322 | 323 | grforms_with_functions(Fun, {ERForms, FRForms, ARForms, MRForms}) -> 324 | FRForms1 = Fun(FRForms), 325 | {ERForms, FRForms1, ARForms, MRForms}. 326 | 327 | -spec sort_forms([erl_parse:abstract_form()]) -> [erl_parse:abstract_form()]. 328 | %% @doc sort forms to valid order, same as insert_forms(Forms, []). 329 | %% @see insert_forms/2. 330 | sort_forms(Forms) -> 331 | insert_forms(Forms, []). 332 | 333 | -spec insert_forms([erl_parse:abstract_form()], [erl_parse:abstract_form()]) -> [erl_parse:abstract_form()]. 334 | %% @doc insert new forms to froms with order fillow these rules 335 | %%
    336 | %%
  • rename functions in forms which function has '__original__' call in it with same name and arity in new forms.
  • 337 | %%
  • '__original__'(Args1, Args2, ...) will be transformed to RenamedFunction(Args1, Args2, ...).
  • 338 | %%
  • after rename, it dose not matter function or spec with duplicated name and arity, lint will get these errors.
  • 339 | %%
  • attribute in new forms will insert before first spec or function or eof.
  • 340 | %%
  • spec in new forms will insert before function with same name and arity or eof.
  • 341 | %%
  • function in new forms will insert after spec with same name and arity or insert before eof.
  • 342 | %%
  • eof_marker in new forms will be dropped if there is an eof_marker already exists in forms.
  • 343 | %%
  • eof_marker in new forms will insert at the end of forms if there is no eof_market in forms.
  • 344 | %%
  • if form is marked from other file (between -file(file1) and -file(file2)), do not change this mark.(not implemented)
  • 345 | %%
  • insert_forms does not changes the original forms order, but with some rules check.
  • 346 | %%
  • eof_marker should be the last element of original forms
  • 347 | %%
  • except file attribute, module should be the first element of original forms
  • 348 | %%
  • an error {insert_form_failed, Form, GRForms} is throwed when original forms check failed
  • 349 | %%
350 | %% @end 351 | insert_forms(NewForms, Forms) -> 352 | Functions = forms_functions(Forms), 353 | NewFormsFunctions = forms_functions(NewForms), 354 | GRForms = lists:foldl(fun grforms_append/2, grforms_new(), Forms), 355 | {_Functions, GRForms1, []} = insert_forms(NewForms, NewFormsFunctions, Functions, GRForms, []), 356 | grforms_to_forms(GRForms1). 357 | 358 | %% merge forms rule 359 | %% 1. rename functions which generated functions has '__original__' call in it with same name and arity. 360 | %% 2. rename spec if new spec is generated 361 | %% 3. for map_forms/2, code does not know how forms will change in tails 362 | %$ 4. if it's need to adjust new generated form order, only forms in heads should be affeted 363 | %% 5. new generated attribute should insert before first -spec in heads 364 | %% 6. new generated spec should insert before function with same name and arity in heads 365 | %% 7. new generated function should insert after spec with same name and arity in heads. 366 | %% 8. if file of new generated form is different from oldone, file attribute should be created to mark file. 367 | %% new_forms, heads, tails is original order. 368 | %% after merge forms, Heads is reversed order, tails is original order. 369 | insert_forms(NewForms, NewFormsFucntions, Functions, GRForms, Tails) -> 370 | {Functions1, NewForms1, GRForms1, Tails1} = merge_functions(NewForms, NewFormsFucntions, Functions, GRForms, Tails), 371 | GRForms2 = lists:foldl(fun grforms_insert/2, GRForms1, NewForms1), 372 | {Functions1, GRForms2, Tails1}. 373 | 374 | %% ===================================================================== 375 | %% merge functions 376 | %% ===================================================================== 377 | merge_functions(NewForms, NewFormsFucntions, Functions, GRForms, Tails) -> 378 | ExistsNewFunctions = 379 | ordsets:from_list( 380 | lists:filter( 381 | fun(NameArity) -> 382 | ordsets:is_element(NameArity, Functions) 383 | end, ordsets:to_list(NewFormsFucntions))), 384 | Functions1 = ordsets:union(Functions, NewFormsFucntions), 385 | {Functions2, NewFormsR2, GRForms1, Tails1} = 386 | lists:foldl( 387 | fun({function, _Pos, Name, Arity, _Clauses} = Form, 388 | {FunctionsAcc, NewFormsAcc, GRFormsAcc, TailsAcc}) -> 389 | case ordsets:is_element({Name, Arity}, ExistsNewFunctions) andalso is_renamed(Arity, Form) of 390 | true -> 391 | NewName = new_function_name(Name, Arity, FunctionsAcc), 392 | Form1 = update_call_name('__original__', NewName, Arity, Form), 393 | GRFormsAcc1 = 394 | grforms_with_functions( 395 | fun(FRForms) -> 396 | update_function_name(Name, Arity, NewName, FRForms) 397 | end, GRFormsAcc), 398 | TailsAcc1 = update_function_name(Name, Arity, NewName, TailsAcc), 399 | {ordsets:add_element({NewName, Arity}, FunctionsAcc), [Form1|NewFormsAcc], GRFormsAcc1, TailsAcc1}; 400 | false -> 401 | {FunctionsAcc, [Form|NewFormsAcc], GRFormsAcc, TailsAcc} 402 | end; 403 | (Form, {FunctionsAcc, NewFormsAcc, HeadsAcc, TailsAcc}) -> 404 | {FunctionsAcc, [Form|NewFormsAcc], HeadsAcc, TailsAcc} 405 | end, {Functions1, [], GRForms, Tails}, NewForms), 406 | {Functions2, lists:reverse(NewFormsR2), GRForms1, Tails1}. 407 | 408 | -spec is_renamed(integer(), erl_parse:abstract_form()) -> boolean(). 409 | is_renamed(Arity, Form) -> 410 | astranaut:search( 411 | fun({call, _Pos1, {atom, _Pos2, '__original__'}, Arguments}) -> 412 | length(Arguments) =:= Arity; 413 | (_Node) -> 414 | false 415 | end, Form, #{traverse => pre}). 416 | 417 | new_function_name(FName, Arity, Functions) -> 418 | new_function_name(FName, Arity, Functions, 1). 419 | 420 | new_function_name(FName, Arity, Functions, Counter) -> 421 | FName1 = list_to_atom(atom_to_list(FName) ++ "_" ++ integer_to_list(Counter)), 422 | case ordsets:is_element({FName1, Arity}, Functions) of 423 | true -> 424 | new_function_name(FName, Arity, Functions, Counter + 1); 425 | false -> 426 | FName1 427 | end. 428 | 429 | update_function_name(Name, Arity, NewName, Forms) -> 430 | lists:map( 431 | fun({function, Pos, FName, FArity, Clauses}) 432 | when FName =:= Name, FArity =:= Arity -> 433 | Clauses1 = update_call_name(Name, NewName, Arity, Clauses), 434 | {function, Pos, NewName, Arity, Clauses1}; 435 | (Form) -> 436 | Form 437 | end, Forms). 438 | 439 | update_call_name(OrignalName, NewName, Arity, Function) -> 440 | astranaut:smap( 441 | fun({call, Pos, {atom, Pos2, Name}, Arguments}) 442 | when Name =:= OrignalName, length(Arguments) =:= Arity -> 443 | {call, Pos, {atom, Pos2, NewName}, Arguments}; 444 | (Node) -> 445 | Node 446 | end, Function, #{traverse => pre}). 447 | -------------------------------------------------------------------------------- /src/astranaut_traverse.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2020, Chen Slepher 4 | %%% @doc 5 | %%% traverse monad for {@link astranaut:map_m/3} 6 | %%% @end 7 | %%% Created : 6 Jul 2020 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_traverse). 10 | 11 | %%%=================================================================== 12 | %%% macros 13 | %%%=================================================================== 14 | -include("astranaut_struct_name.hrl"). 15 | 16 | -define(STATE_OK, astranaut_traverse_state_ok). 17 | -define(STATE_FAIL, astranaut_traverse_state_fail). 18 | 19 | -compile({no_auto_import, [error/1, get/0, put/1]}). 20 | %%%=================================================================== 21 | %%% types 22 | %%%=================================================================== 23 | -export_type([struct/2]). 24 | 25 | -type struct(S, A) :: #{?STRUCT_KEY => ?TRAVERSE_M, inner => inner_type(S, A)}. 26 | 27 | -type inner_type(S, A) :: fun((module(), S, #{}, astranaut_error:struct()) -> state_struct(S, A)). 28 | 29 | -type state_struct(S, A) :: 30 | #{?STRUCT_KEY => ?STATE_OK, 31 | return => A, 32 | updated => boolean(), 33 | state => S, 34 | error => astranaut_error:struct()} | 35 | #{?STRUCT_KEY => ?STATE_FAIL, 36 | state => S, 37 | error => astranaut_error:struct()}. 38 | 39 | -type formatter() :: module(). 40 | -type convertable(S, A) :: astranaut:walk_return(S, A) | astranaut_return:struct(A) | state_struct(S, A). 41 | %%%=================================================================== 42 | %%% API 43 | %%%=================================================================== 44 | -export([astranaut_traverse/1]). 45 | -export([convertable_struct/1]). 46 | -export([run/4, eval/4, exec/4]). 47 | -export([lift_m/2, map_m/2, sequence_m/1]). 48 | -export([bind/2, then/2, return/1]). 49 | -export([fail/1, fail/2, fails/1]). 50 | -export([fail_on_error/1, catch_on_error/2]). 51 | -export([with_error/2, catch_fail/2, set_fail/1]). 52 | -export([ask/0, local/2]). 53 | -export([state/1, get/0, put/1, modify/1]). 54 | -export([listen_error/1, writer_updated/1, listen_updated/1]). 55 | -export([warning/1, warnings/1, formatted_warnings/1, error/1, errors/1, formatted_errors/1]). 56 | -export([update_file/1, eof/0, update_pos/2, update_pos/3, with_formatter/2]). 57 | 58 | -spec astranaut_traverse(convertable(S, A)) -> struct(S, A). 59 | astranaut_traverse(#{?STRUCT_KEY := ?WALK_RETURN} = Map) -> 60 | Inner = 61 | fun(_Formatter, File, _Attr, State0) -> 62 | State1 = maps:get(state, Map, State0), 63 | Errors = maps:get(errors, Map, []), 64 | Warnings = maps:get(warnings, Map, []), 65 | Error1 = astranaut_error:append_ews(Errors, Warnings, astranaut_error:new(File)), 66 | case maps:find(return, Map) of 67 | {ok, Return} -> 68 | state_ok(#{return => Return, state => State1, error => Error1}); 69 | error -> 70 | case Errors of 71 | [] -> 72 | exit(no_return_with_empty_error); 73 | Errors -> 74 | state_fail(#{state => State1, error => Error1}) 75 | end 76 | end 77 | end, 78 | new(Inner); 79 | astranaut_traverse(#{?STRUCT_KEY := ?RETURN_OK, return := Return, error := ErrorStruct}) -> 80 | Inner = 81 | fun(_Formatter, File, _Attr, State) -> 82 | state_ok(#{return => Return, state => State, error => astranaut_error:update_file(File, ErrorStruct)}) 83 | end, 84 | new(Inner); 85 | astranaut_traverse(#{?STRUCT_KEY := ?RETURN_FAIL, error := ErrorStruct}) -> 86 | Inner = 87 | fun(_Formatter, File, _Attr, State) -> 88 | state_fail(#{state => State, error => astranaut_error:update_file(File, ErrorStruct)}) 89 | end, 90 | new(Inner); 91 | astranaut_traverse(#{?STRUCT_KEY := ?TRAVERSE_M} = MA) -> 92 | MA. 93 | 94 | convertable_struct(#{?STRUCT_KEY := Key}) -> 95 | convertable_struct_key(Key); 96 | convertable_struct(_Other) -> 97 | false. 98 | 99 | convertable_struct_key(?RETURN_OK) -> 100 | true; 101 | convertable_struct_key(?RETURN_FAIL) -> 102 | true; 103 | convertable_struct_key(?WALK_RETURN) -> 104 | true; 105 | convertable_struct_key(?TRAVERSE_M) -> 106 | true; 107 | convertable_struct_key(_) -> 108 | false. 109 | 110 | -spec new(inner_type(S, A)) -> struct(S, A). 111 | new(Inner) when is_function(Inner, 4) -> 112 | #{?STRUCT_KEY => ?TRAVERSE_M, inner => Inner}. 113 | 114 | state_ok(#{return := _Return, state := _State, error := _Error} = Map) -> 115 | maps:merge(#{?STRUCT_KEY => ?STATE_OK, updated => false}, Map). 116 | 117 | state_fail(#{state := _State, error := _Error} = Map) -> 118 | Map#{?STRUCT_KEY => ?STATE_FAIL}. 119 | 120 | -spec run(struct(S, A), formatter(), #{}, S) -> astranaut_return:struct({A, S}). 121 | run(#{?STRUCT_KEY := ?TRAVERSE_M} = MA, Formatter, Attr, State) -> 122 | case run_0(MA, Formatter, undefined, Attr, State) of 123 | #{?STRUCT_KEY := ?STATE_OK, return := Return, state := State1, error := Error} -> 124 | astranaut_return:ok({Return, State1}, Error); 125 | #{?STRUCT_KEY := ?STATE_FAIL, error := Error} -> 126 | astranaut_return:fail(Error) 127 | end. 128 | 129 | -spec eval(struct(S, A), formatter(), #{}, S) -> astranaut_return:struct(A). 130 | eval(#{?STRUCT_KEY := ?TRAVERSE_M} = MA, Formatter, Attr, State) -> 131 | astranaut_return:lift_m(fun({A, _State}) -> A end, run(MA, Formatter, Attr, State)). 132 | 133 | -spec exec(struct(S, _A), formatter(), #{}, S) -> astranaut_return:struct(S). 134 | exec(#{?STRUCT_KEY := ?TRAVERSE_M} = MA, Formatter, Attr, State) -> 135 | astranaut_return:lift_m(fun({_A, State1}) -> State1 end, run(MA, Formatter, Attr, State)). 136 | 137 | -spec lift_m(fun((A) -> B), struct(S, A)) -> struct(S, B). 138 | lift_m(F, X) -> 139 | bind(X, fun(A) -> return(F(A)) end). 140 | 141 | -spec map_m(fun((A) -> struct(S, B)), [struct(S, A)]) -> struct(S, B). 142 | map_m(F, [X|Xs]) -> 143 | bind(F(X), 144 | fun(A) -> 145 | bind(map_m(F, Xs), 146 | fun(As) -> 147 | return([A|As]) 148 | end) 149 | end); 150 | map_m(_F, []) -> 151 | return([]). 152 | 153 | -spec sequence_m([struct(S, A)]) -> struct(S, [A]). 154 | sequence_m(Xs) -> 155 | map_m(fun(A) -> A end, Xs). 156 | 157 | -spec bind(struct(S, A) | ok, fun((A) -> struct(S, B))) -> struct(S, B). 158 | bind(ok, KMB) -> 159 | KMB(ok); 160 | bind(MA, KMB) when is_function(KMB, 1) -> 161 | map_m_state_ok( 162 | fun(Formatter, Attr, #{return := A, state := State1, 163 | updated := Updated1, error := Error1}) -> 164 | File = astranaut_error:file(Error1), 165 | MB = run_0(KMB(A), Formatter, File, Attr, State1), 166 | case MB of 167 | #{?STRUCT_KEY := ?STATE_OK, updated := Updated2, error := Error2} -> 168 | Updated3 = Updated1 or Updated2, 169 | Error3 = astranaut_error:merge(Error1, Error2), 170 | update_m_state(MB, #{updated => Updated3, error => Error3}); 171 | #{?STRUCT_KEY := ?STATE_FAIL, error := Error2} -> 172 | Error3 = astranaut_error:merge(Error1, Error2), 173 | update_m_state(MB, #{error => Error3}) 174 | end 175 | end, MA). 176 | 177 | -spec then(struct(S, _A), struct(S, B)) -> struct(S, B). 178 | then(MA, MB) -> 179 | bind(MA, fun(_) -> MB end). 180 | 181 | -spec return(A) -> struct(_S, A). 182 | return(A) -> 183 | Inner = 184 | fun(_Formatter, File, _Attr, State) -> 185 | state_ok(#{return => A, state => State, error => astranaut_error:new(File)}) 186 | end, 187 | new(Inner). 188 | 189 | -spec fail_on_error(struct(S, A)) -> struct(S, A). 190 | fail_on_error(MA) -> 191 | map_m_state_ok( 192 | fun(_Formatter, _Attr, #{state := State1, error := Error} = StateOk) -> 193 | case astranaut_error:is_empty_error(Error) of 194 | true -> 195 | StateOk; 196 | false -> 197 | state_fail(#{state => State1, error => Error}) 198 | end 199 | end, MA). 200 | 201 | -spec catch_on_error(struct(S, A), fun(() -> struct(S, A))) -> struct(S, A). 202 | catch_on_error(MA, FMA) -> 203 | map_m_state( 204 | fun(_Formatter, _Attr, #{?STRUCT_KEY := ?STATE_OK} = StateOk) -> 205 | StateOk; 206 | (Formatter, Attr, #{?STRUCT_KEY := ?STATE_FAIL, state := State, error := Error1}) -> 207 | File = astranaut_error:file(Error1), 208 | #{error := Error2} = MB = run_0(FMA(), Formatter, File, Attr, State), 209 | Error3 = astranaut_error:merge(Error1, Error2), 210 | update_m_state(MB, #{error => Error3}) 211 | end, fail_on_error(MA)). 212 | 213 | -spec fail(_E) -> struct(_S, _A). 214 | fail(E) -> 215 | fails([E]). 216 | 217 | -spec fail(_E, ?TRAVERSE_M) -> struct(_S, _A). 218 | fail(E, ?TRAVERSE_M) -> 219 | fails([E]). 220 | 221 | -spec fails([_E]) -> struct(_S, _A). 222 | fails(Es) -> 223 | Inner = 224 | fun(_Formatter, File, _Attr, State) -> 225 | Error1 = astranaut_error:append_errors(Es, astranaut_error:new(File)), 226 | state_fail(#{state => State, error => Error1}) 227 | end, 228 | new(Inner). 229 | %%%=================================================================== 230 | %%% state related functions. 231 | %%%=================================================================== 232 | ask() -> 233 | Inner = 234 | fun(_Formatter, File, Attr, State) -> 235 | state_ok(#{return => Attr, state => State, error => astranaut_error:new(File)}) 236 | end, 237 | new(Inner). 238 | 239 | local(F, MA) -> 240 | Inner = 241 | fun(Formatter, File, Attr, State) -> 242 | Attr1 = F(Attr), 243 | run_0(MA, Formatter, File, Attr1, State) 244 | end, 245 | new(Inner). 246 | 247 | -spec state(fun((S) -> {A, S})) -> struct(S, A). 248 | state(F) -> 249 | Inner = 250 | fun(_Formatter, File, _Attr, State0) -> 251 | {A, State1} = F(State0), 252 | state_ok(#{return => A, state => State1, error => astranaut_error:new(File)}) 253 | end, 254 | new(Inner). 255 | 256 | -spec modify(fun((S) -> S)) -> struct(S, ok). 257 | modify(F) -> 258 | state(fun(State) -> State1 = F(State), {ok, State1} end). 259 | 260 | -spec get() -> struct(S, S). 261 | get() -> 262 | state(fun(State) -> {State, State} end). 263 | 264 | -spec put(S) -> struct(S, ok). 265 | put(State) -> 266 | state(fun(_State) -> {ok, State} end). 267 | 268 | -spec listen_error(struct(S, A)) -> struct(S, {A, astranaut_error:struct()}). 269 | listen_error(MA) -> 270 | map_m_state_ok( 271 | fun(#{return := Return, error := Error} = MState) -> 272 | MState#{return => {Return, Error}} 273 | end, MA). 274 | 275 | writer_updated({A, Updated}) -> 276 | Inner = 277 | fun(_Formatter, File, _Attr, State) -> 278 | state_ok(#{return => A, state => State, updated => Updated, error => astranaut_error:new(File)}) 279 | end, 280 | new(Inner). 281 | 282 | listen_updated(MA) -> 283 | map_m_state_ok( 284 | fun(#{return := Return, updated := Updated} = MState) -> 285 | update_m_state(MState, #{return => {Return, Updated}}) 286 | end, MA). 287 | %%%=================================================================== 288 | %%% error_state related functions 289 | %%%=================================================================== 290 | -spec with_error(fun((astranaut_error:struct()) 291 | -> astranaut_error:struct()), 292 | struct(S, A)) 293 | -> struct(S, A). 294 | with_error(F, MA) -> 295 | map_m_state( 296 | fun(#{error := Error1} = MState) -> 297 | Error2 = F(Error1), 298 | update_m_state(MState, #{error => Error2}) 299 | end, MA). 300 | 301 | catch_fail(F, MA) -> 302 | map_m_state( 303 | fun(_Formatter, _Attr, #{?STRUCT_KEY := ?STATE_OK} = StateM) -> 304 | StateM; 305 | (Formatter, Attr, #{?STRUCT_KEY := ?STATE_FAIL, state := State1, error := Error1}) -> 306 | File = astranaut_error:file(Error1), 307 | #{error := Error2} = MB = run_0(F(), Formatter, File, Attr, State1), 308 | Error3 = astranaut_error:merge(Error1, Error2), 309 | update_m_state(MB, #{error => Error3}) 310 | end, MA). 311 | 312 | set_fail(MA) -> 313 | map_m_state( 314 | fun(_Formatter, #{?STRUCT_KEY := ?STATE_OK, state := State, error := Error}) -> 315 | state_fail(#{state => State, error => Error}) 316 | end, MA). 317 | 318 | -spec generate_error(astranaut_error:struct()) -> struct(_S, _A). 319 | generate_error(Error) -> 320 | Inner = 321 | fun(_Formatter, File, _Attr, State) -> 322 | state_ok(#{return => ok, state => State, error => astranaut_error:update_file(File, Error)}) 323 | end, 324 | new(Inner). 325 | 326 | -spec warning(term()) -> struct(_S, _A). 327 | warning(Warning) -> 328 | warnings([Warning]). 329 | 330 | -spec warnings([term()]) -> struct(_S, _A). 331 | warnings(Warnings) -> 332 | generate_error(astranaut_error:new_warnings(Warnings)). 333 | 334 | -spec formatted_warnings([{erl_anno:location(), formatter(), term()}]) -> struct(_S, _A). 335 | formatted_warnings(Warnings) -> 336 | generate_error(astranaut_error:new_formatted_warnings(Warnings)). 337 | 338 | -spec error(term()) -> struct(_S, _A). 339 | error(Error) -> 340 | errors([Error]). 341 | 342 | -spec errors([term()]) -> struct(_S, _A). 343 | errors(Errors) -> 344 | generate_error(astranaut_error:new_errors(Errors)). 345 | 346 | -spec formatted_errors([{erl_anno:location(), formatter(), term()}]) -> struct(_S, _A). 347 | formatted_errors(Errors) -> 348 | generate_error(astranaut_error:new_formatted_errors(Errors)). 349 | 350 | -spec update_file(astranaut_error:compile_file()) -> struct(_S, _A). 351 | update_file(File) -> 352 | Inner = 353 | fun(_Formatter, _File0, _Attr, State) -> 354 | state_ok(#{return => ok, state => State, error => astranaut_error:new(File)}) 355 | end, 356 | new(Inner). 357 | 358 | -spec eof() -> struct(_S, _A). 359 | eof() -> 360 | update_file(eof). 361 | 362 | -spec update_pos(erl_anno:location(), struct(S, A)) -> struct(S, A). 363 | update_pos(Pos, MA) -> 364 | map_m_state( 365 | fun(Formatter, #{error := Error0} = MState) -> 366 | case astranaut_error:no_pending(Error0) of 367 | true -> 368 | MState; 369 | false -> 370 | Error1 = astranaut_error:update_pos(Pos, Formatter, Error0), 371 | update_m_state(MState, #{error => Error1}) 372 | end 373 | end, MA). 374 | 375 | -spec update_pos(erl_anno:location(), module(), struct(S, A)) -> struct(S, A). 376 | update_pos(Pos, Formatter, MA) -> 377 | with_formatter(Formatter, update_pos(Pos, MA)). 378 | 379 | -spec with_formatter(module(), struct(S, A)) -> struct(S, A). 380 | with_formatter(Formatter, MA) -> 381 | Inner = 382 | fun(_Formatter0, File, Attr, State) -> 383 | run_0(MA, Formatter, File, Attr, State) 384 | end, 385 | new(Inner). 386 | %%%=================================================================== 387 | %%% Internal functions 388 | %%%=================================================================== 389 | run_0(#{?STRUCT_KEY := ?TRAVERSE_M, inner := Inner}, Formatter, File, Attr, State) -> 390 | Inner(Formatter, File, Attr, State). 391 | 392 | map_m_state(F, MA) -> 393 | Inner = 394 | fun(Formatter, File, Attr, State) -> 395 | MState1 = run_0(MA, Formatter, File, Attr, State), 396 | MState2 = apply_map_state_m_f(F, Formatter, Attr, MState1), 397 | case MState2 of 398 | #{?STRUCT_KEY := ?STATE_OK} -> 399 | MState2; 400 | #{?STRUCT_KEY := ?STATE_FAIL} -> 401 | MState2; 402 | _ -> 403 | exit({invalid_m_state_after_map, MState1, MState2}) 404 | end 405 | end, 406 | new(Inner). 407 | 408 | map_m_state_ok(F, MA) -> 409 | map_m_state( 410 | fun(Formatter, Attr, #{?STRUCT_KEY := ?STATE_OK} = StateM) -> 411 | apply_map_state_m_f(F, Formatter, Attr, StateM); 412 | (_Formatter, _Attr, #{?STRUCT_KEY := ?STATE_FAIL} = StateM) -> 413 | StateM 414 | end, MA). 415 | 416 | apply_map_state_m_f(F, _Formatter, _Attr, MState) when is_function(F, 1) -> 417 | F(MState); 418 | apply_map_state_m_f(F, Formatter, _Attr, MState) when is_function(F, 2) -> 419 | F(Formatter, MState); 420 | apply_map_state_m_f(F, Formatter, Attr, MState) when is_function(F, 3) -> 421 | F(Formatter, Attr, MState). 422 | 423 | update_m_state(#{} = State, #{} = Map) -> 424 | merge_struct(State, Map, #{?STATE_OK => [return, state, error, updated], 425 | ?STATE_FAIL => {[state, error], [return, updated]}}). 426 | 427 | merge_struct(#{?STRUCT_KEY := StructName} = Struct, Map, KeyMap) when is_map(KeyMap)-> 428 | case maps:find(StructName, KeyMap) of 429 | {ok, {Keys, OptionalKeys}} -> 430 | merge_struct(Struct, Map, Keys, OptionalKeys); 431 | {ok, Keys} -> 432 | merge_struct(Struct, Map, Keys, []); 433 | error -> 434 | erlang:error({invalid_struct, StructName}) 435 | end. 436 | merge_struct(#{?STRUCT_KEY := StructName} = Struct, Map, Keys, OptionalKeys) when is_list(Keys), is_list(OptionalKeys) -> 437 | RestKeys = maps:keys(Map) -- Keys -- OptionalKeys -- [?STRUCT_KEY], 438 | case RestKeys of 439 | [] -> 440 | maps:fold( 441 | fun(Key, Value, Acc) -> 442 | case validate_struct_value(Key, Value) of 443 | true -> 444 | maps:put(Key, Value, Acc); 445 | false -> 446 | erlang:error({invalid_struct_value, StructName, Key, Value}) 447 | end 448 | end, Struct, Map); 449 | _ -> 450 | erlang:error({invalid_merge_keys, StructName, RestKeys, Map}) 451 | end. 452 | 453 | validate_struct_value(return, _Return) -> 454 | true; 455 | validate_struct_value(state, _State) -> 456 | true; 457 | validate_struct_value(error, #{?STRUCT_KEY := ?ERROR_STATE}) -> 458 | true; 459 | validate_struct_value(updated, Updated) when is_boolean(Updated) -> 460 | true; 461 | validate_struct_value(_Key, _Value) -> 462 | false. 463 | -------------------------------------------------------------------------------- /test/astranaut_SUITE_data/sample_1.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2020, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 9 Jul 2020 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(sample_1). 10 | 11 | -compile({parse_transform, sample_transformer_1}). 12 | 13 | %% API 14 | -export([warning_0/0, error_0/0]). 15 | 16 | -baseline(mark_base). 17 | -mark(mark_error_0). 18 | -mark(mark_0). 19 | %%%=================================================================== 20 | %%% API 21 | %%%=================================================================== 22 | -baseline(function_base). 23 | error_0() -> 24 | mark_error_1. 25 | 26 | warning_0() -> 27 | mark_1. 28 | 29 | %%-------------------------------------------------------------------- 30 | %% @doc 31 | %% @spec 32 | %% @end 33 | %%-------------------------------------------------------------------- 34 | 35 | %%%=================================================================== 36 | %%% Internal functions 37 | %%%=================================================================== 38 | -------------------------------------------------------------------------------- /test/astranaut_SUITE_data/sample_2.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2020, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 9 Jul 2020 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(sample_2). 10 | 11 | %% API 12 | -export([test/1]). 13 | 14 | -mark(mark_01). 15 | %%%=================================================================== 16 | %%% API 17 | %%%=================================================================== 18 | 19 | test(ok_2)-> 20 | test(ok_1); 21 | test(ok_3)-> 22 | test_1(ok_3); 23 | test(ok_4)-> 24 | test(ok_4, ok_4); 25 | test(_A) -> 26 | ok_2. 27 | 28 | test_1(ok_3) -> 29 | ok_3. 30 | 31 | test(A, _B) -> 32 | A. 33 | %%-------------------------------------------------------------------- 34 | %% @doc 35 | %% @spec 36 | %% @end 37 | %%-------------------------------------------------------------------- 38 | 39 | %%%=================================================================== 40 | %%% Internal functions 41 | %%%=================================================================== 42 | -------------------------------------------------------------------------------- /test/astranaut_SUITE_data/sample_expressions.erl: -------------------------------------------------------------------------------- 1 | -module(sample_expressions). 2 | 3 | -compile(export_all). 4 | -compile(nowarn_export_all). 5 | 6 | -include("stacktrace.hrl"). 7 | 8 | -record(test, {a, b, c}). 9 | 10 | list() -> 11 | [1, 2, 3]. 12 | 13 | tuple() -> 14 | {1, 2, 3}. 15 | 16 | map() -> 17 | A = #{a => 1, b => 2}, 18 | B = A#{a => 2, b => 3}, 19 | #{a := C, b := D} = B, 20 | {B, C, D}. 21 | 22 | record() -> 23 | A = #test{a = 1, b = 2}, 24 | B = A#test{a = 2, b = 3}, 25 | C = #test.a, 26 | D = A#test.a, 27 | #test{a = E} = A, 28 | {A, B, C, D, E}. 29 | 30 | if_expr(A) -> 31 | if 32 | A =:= 2 -> a; 33 | A =:= 1 -> b; 34 | true -> c 35 | end. 36 | 37 | case_expr(A) -> 38 | case A of 39 | 10 -> a; 40 | 20 -> b; 41 | C when (C =/= 10), (C =/= 20), (C =:= 30); (C =:= 40) ; (C =:= 50)-> 42 | c; 43 | A -> 44 | d 45 | end. 46 | 47 | try_catch_expr(A) -> 48 | try A of 49 | 10 -> 50 | 10; 51 | A -> 52 | A 53 | catch 54 | Error:badarg?CAPTURE_STACKTRACE -> 55 | erlang:raise(Error, badarg, ?GET_STACKTRACE); 56 | Error:Exception?CAPTURE_STACKTRACE -> 57 | erlang:raise(Error, Exception, ?GET_STACKTRACE) 58 | after 59 | ok 60 | end. 61 | -------------------------------------------------------------------------------- /test/astranaut_SUITE_data/sample_transformer_1.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2021, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 19 Feb 2021 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(sample_transformer_1). 10 | 11 | %% API 12 | -export([parse_transform/2, format_error/1]). 13 | 14 | %%%=================================================================== 15 | %%% API 16 | %%%=================================================================== 17 | parse_transform(Forms, _Opts) -> 18 | Return = 19 | astranaut_return:lift_m( 20 | fun(_) -> Forms end, 21 | astranaut_lib:with_attribute( 22 | fun(mark_error_0, _Acc) -> 23 | {warning, error_0}; 24 | (mark_0, _Acc) -> 25 | {warning, warning_0}; 26 | (_Attr, Acc) -> 27 | Acc 28 | end, ok, Forms, mark, #{formatter => ?MODULE})), 29 | astranaut_return:to_compiler(Return). 30 | 31 | format_error(error_0) -> 32 | io_lib:format("get error 0", []); 33 | format_error(warning_0) -> 34 | io_lib:format("get warning 0", []); 35 | format_error(Error) -> 36 | astranaut:format_error(Error). 37 | %%-------------------------------------------------------------------- 38 | %% @doc 39 | %% @spec 40 | %% @end 41 | %%-------------------------------------------------------------------- 42 | 43 | %%%=================================================================== 44 | %%% Internal functions 45 | %%%=================================================================== 46 | -------------------------------------------------------------------------------- /test/astranaut_error_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2020, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 6 Jul 2020 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_error_SUITE). 10 | 11 | -compile(export_all). 12 | -compile(nowarn_export_all). 13 | 14 | -include("rebinding.hrl"). 15 | -include_lib("stdlib/include/assert.hrl"). 16 | -include_lib("common_test/include/ct.hrl"). 17 | 18 | -rebinding_all([{clause_pinned, true}]). 19 | 20 | %%-------------------------------------------------------------------- 21 | %% @spec suite() -> Info 22 | %% Info = [tuple()] 23 | %% @end 24 | %%-------------------------------------------------------------------- 25 | suite() -> 26 | [{timetrap,{seconds,30}}]. 27 | 28 | %%-------------------------------------------------------------------- 29 | %% @spec init_per_suite(Config0) -> 30 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 31 | %% Config0 = Config1 = [tuple()] 32 | %% Reason = term() 33 | %% @end 34 | %%-------------------------------------------------------------------- 35 | init_per_suite(Config) -> 36 | Config. 37 | 38 | %%-------------------------------------------------------------------- 39 | %% @spec end_per_suite(Config0) -> term() | {save_config,Config1} 40 | %% Config0 = Config1 = [tuple()] 41 | %% @end 42 | %%-------------------------------------------------------------------- 43 | end_per_suite(_Config) -> 44 | ok. 45 | 46 | %%-------------------------------------------------------------------- 47 | %% @spec init_per_group(GroupName, Config0) -> 48 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 49 | %% GroupName = atom() 50 | %% Config0 = Config1 = [tuple()] 51 | %% Reason = term() 52 | %% @end 53 | %%-------------------------------------------------------------------- 54 | init_per_group(_GroupName, Config) -> 55 | Config. 56 | 57 | %%-------------------------------------------------------------------- 58 | %% @spec end_per_group(GroupName, Config0) -> 59 | %% term() | {save_config,Config1} 60 | %% GroupName = atom() 61 | %% Config0 = Config1 = [tuple()] 62 | %% @end 63 | %%-------------------------------------------------------------------- 64 | end_per_group(_GroupName, _Config) -> 65 | ok. 66 | 67 | %%-------------------------------------------------------------------- 68 | %% @spec init_per_testcase(TestCase, Config0) -> 69 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 70 | %% TestCase = atom() 71 | %% Config0 = Config1 = [tuple()] 72 | %% Reason = term() 73 | %% @end 74 | %%-------------------------------------------------------------------- 75 | init_per_testcase(_TestCase, Config) -> 76 | Config. 77 | 78 | %%-------------------------------------------------------------------- 79 | %% @spec end_per_testcase(TestCase, Config0) -> 80 | %% term() | {save_config,Config1} | {fail,Reason} 81 | %% TestCase = atom() 82 | %% Config0 = Config1 = [tuple()] 83 | %% Reason = term() 84 | %% @end 85 | %%-------------------------------------------------------------------- 86 | end_per_testcase(_TestCase, _Config) -> 87 | ok. 88 | 89 | %%-------------------------------------------------------------------- 90 | %% @spec groups() -> [Group] 91 | %% Group = {GroupName,Properties,GroupsAndTestCases} 92 | %% GroupName = atom() 93 | %% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] 94 | %% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] 95 | %% TestCase = atom() 96 | %% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} 97 | %% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | 98 | %% repeat_until_any_ok | repeat_until_any_fail 99 | %% N = integer() | forever 100 | %% @end 101 | %%-------------------------------------------------------------------- 102 | groups() -> 103 | []. 104 | 105 | %%-------------------------------------------------------------------- 106 | %% @spec all() -> GroupsAndTestCases | {skip,Reason} 107 | %% GroupsAndTestCases = [{group,GroupName} | TestCase] 108 | %% GroupName = atom() 109 | %% TestCase = atom() 110 | %% Reason = term() 111 | %% @end 112 | %%-------------------------------------------------------------------- 113 | all() -> 114 | [test_state_1, test_state_2, test_state_3, test_state_4, test_state_5, test_state_6]. 115 | 116 | %%-------------------------------------------------------------------- 117 | %% @spec TestCase() -> Info 118 | %% Info = [tuple()] 119 | %% @end 120 | %%-------------------------------------------------------------------- 121 | test_merge_1() -> 122 | []. 123 | 124 | %%-------------------------------------------------------------------- 125 | %% @spec TestCase(Config0) -> 126 | %% ok | exit() | {skip,Reason} | {comment,Comment} | 127 | %% {save_config,Config1} | {skip_and_save,Reason,Config1} 128 | %% Config0 = Config1 = [tuple()] 129 | %% Reason = term() 130 | %% Comment = term() 131 | %% @end 132 | %%-------------------------------------------------------------------- 133 | test_state_1(_Config) -> 134 | Init = astranaut_error:new(), 135 | Errors = [error_0], 136 | Warnings = [], 137 | State = astranaut_error:append_error(error_0, Init), 138 | ?assertEqual({Errors, Warnings}, {astranaut_error:errors(State), astranaut_error:warnings(State)}), 139 | ok. 140 | 141 | test_state_2(_Config) -> 142 | Init = astranaut_error:new(), 143 | Errors = [error_0, error_1], 144 | State = astranaut_error:append_error(error_0, Init), 145 | State = astranaut_error:append_error(error_1, State), 146 | ?assertEqual(Errors, astranaut_error:errors(State)), 147 | ok. 148 | 149 | test_state_3(_Config) -> 150 | Init = astranaut_error:new(), 151 | Errors = [error_0, error_1], 152 | Warnings = [warning_0, warning_1, warning_2], 153 | State = astranaut_error:append_warnings([warning_0, warning_1], Init), 154 | State = astranaut_error:append_error(error_0, State), 155 | State = astranaut_error:append_error(error_1, State), 156 | State = astranaut_error:append_warning(warning_2, State), 157 | ?assertEqual({Errors, Warnings}, {astranaut_error:errors(State), astranaut_error:warnings(State)}), 158 | ok. 159 | 160 | test_state_4(_Config) -> 161 | State = astranaut_error:new(), 162 | Errors = [{10, ?MODULE, error_0}, {20, ?MODULE, error_1}], 163 | Warnings = [{5, ?MODULE, warning_0}, {15, ?MODULE, warning_1}, {25, ?MODULE, warning_2}], 164 | State = astranaut_error:append_warning(warning_0, State), 165 | State = astranaut_error:update_pos(5, ?MODULE, State), 166 | State = astranaut_error:append_error(error_0, State), 167 | State = astranaut_error:update_pos(10, ?MODULE, State), 168 | State = astranaut_error:append_warning(warning_1, State), 169 | State = astranaut_error:update_pos(15, ?MODULE, State), 170 | State = astranaut_error:append_error(error_1, State), 171 | State = astranaut_error:update_pos(20, ?MODULE, State), 172 | State = astranaut_error:append_warning(warning_2, State), 173 | State = astranaut_error:update_pos(25, ?MODULE, State), 174 | ?assertEqual({Errors, Warnings}, {astranaut_error:formatted_errors(State), 175 | astranaut_error:formatted_warnings(State)}), 176 | ok. 177 | 178 | test_state_5(_Config) -> 179 | State = astranaut_error:new(), 180 | Errors = [{?FILE, [{10, ?MODULE, error_0}, {20, ?MODULE, error_1}]}], 181 | Warnings = [{?FILE, [{5, ?MODULE, warning_0}, {15, ?MODULE, warning_1}, {25, ?MODULE, warning_2}]}], 182 | State = astranaut_error:append_warnings([warning_0], State), 183 | State = astranaut_error:update_pos(5, ?MODULE, State), 184 | State = astranaut_error:append_errors([error_0], State), 185 | State = astranaut_error:update_pos(10, ?MODULE, State), 186 | State = astranaut_error:append_warnings([warning_1], State), 187 | State = astranaut_error:update_pos(15, ?MODULE, State), 188 | State = astranaut_error:update_file(?FILE, State), 189 | State = astranaut_error:append_errors([error_1], State), 190 | State = astranaut_error:update_pos(20, ?MODULE, State), 191 | State = astranaut_error:append_warnings([warning_2], State), 192 | State = astranaut_error:update_pos(25, ?MODULE, State), 193 | State = astranaut_error:eof(State), 194 | ?assertEqual({Errors, Warnings}, astranaut_error:realize(State)), 195 | ok. 196 | 197 | test_state_6(_Config) -> 198 | State = astranaut_error:new(), 199 | File2 = ?FILE ++ "_2", 200 | Errors = maps:to_list(#{?FILE =>[{5, ?MODULE, error_0}, {10, ?MODULE, error_1}], File2 => [{20, ?MODULE, error_2}]}), 201 | Warnings = [{?FILE, [{5, ?MODULE, warning_0}, {15, ?MODULE, warning_1}, 202 | {25, ?MODULE, warning_2}, {30, ?MODULE, warning_3}]}], 203 | State = astranaut_error:append_warning(warning_0, State), 204 | State = astranaut_error:append_error(error_0, State), 205 | State = astranaut_error:update_pos(5, ?MODULE, State), 206 | State = astranaut_error:append_error(error_1, State), 207 | State = astranaut_error:update_pos(10, ?MODULE, State), 208 | State = astranaut_error:update_file(?FILE, State), 209 | State = astranaut_error:append_warning(warning_1, State), 210 | State = astranaut_error:update_pos(15, ?MODULE, State), 211 | State = astranaut_error:update_file(File2, State), 212 | State = astranaut_error:append_error(error_2, State), 213 | State = astranaut_error:update_pos(20, ?MODULE, State), 214 | State = astranaut_error:update_file(?FILE, State), 215 | State = astranaut_error:append_warning(warning_2, State), 216 | State = astranaut_error:update_pos(25, ?MODULE, State), 217 | State = astranaut_error:append_warning(warning_3, State), 218 | State = astranaut_error:update_pos(30, ?MODULE, State), 219 | State = astranaut_error:eof(State), 220 | ?assertEqual({Errors, Warnings}, astranaut_error:realize(State)), 221 | ok. 222 | -------------------------------------------------------------------------------- /test/astranaut_macro_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2018, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 8 Dec 2018 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_macro_SUITE). 10 | 11 | -compile(export_all). 12 | 13 | -include_lib("eunit/include/eunit.hrl"). 14 | -include_lib("common_test/include/ct.hrl"). 15 | 16 | %%-------------------------------------------------------------------- 17 | %% @spec suite() -> Info 18 | %% Info = [tuple()] 19 | %% @end 20 | %%-------------------------------------------------------------------- 21 | suite() -> 22 | [{timetrap,{seconds,60}}]. 23 | 24 | %%-------------------------------------------------------------------- 25 | %% @spec init_per_suite(Config0) -> 26 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 27 | %% Config0 = Config1 = [tuple()] 28 | %% Reason = term() 29 | %% @end 30 | %%-------------------------------------------------------------------- 31 | init_per_suite(Config) -> 32 | TestModules = [macro_exports, macro_example, macro_test], 33 | astranaut_test_lib:load_data_modules(Config, TestModules). 34 | %%-------------------------------------------------------------------- 35 | %% @spec end_per_suite(Config0) -> term() | {save_config,Config1} 36 | %% Config0 = Config1 = [tuple()] 37 | %% @end 38 | %%-------------------------------------------------------------------- 39 | end_per_suite(_Config) -> 40 | ok. 41 | 42 | %%-------------------------------------------------------------------- 43 | %% @spec init_per_group(GroupName, Config0) -> 44 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 45 | %% GroupName = atom() 46 | %% Config0 = Config1 = [tuple()] 47 | %% Reason = term() 48 | %% @end 49 | %%-------------------------------------------------------------------- 50 | init_per_group(_GroupName, Config) -> 51 | Config. 52 | 53 | %%-------------------------------------------------------------------- 54 | %% @spec end_per_group(GroupName, Config0) -> 55 | %% term() | {save_config,Config1} 56 | %% GroupName = atom() 57 | %% Config0 = Config1 = [tuple()] 58 | %% @end 59 | %%-------------------------------------------------------------------- 60 | end_per_group(_GroupName, _Config) -> 61 | ok. 62 | 63 | %%-------------------------------------------------------------------- 64 | %% @spec init_per_testcase(TestCase, Config0) -> 65 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 66 | %% TestCase = atom() 67 | %% Config0 = Config1 = [tuple()] 68 | %% Reason = term() 69 | %% @end 70 | %%-------------------------------------------------------------------- 71 | init_per_testcase(_TestCase, Config) -> 72 | Config. 73 | 74 | %%-------------------------------------------------------------------- 75 | %% @spec end_per_testcase(TestCase, Config0) -> 76 | %% term() | {save_config,Config1} | {fail,Reason} 77 | %% TestCase = atom() 78 | %% Config0 = Config1 = [tuple()] 79 | %% Reason = term() 80 | %% @end 81 | %%-------------------------------------------------------------------- 82 | end_per_testcase(_TestCase, _Config) -> 83 | ok. 84 | 85 | %%-------------------------------------------------------------------- 86 | %% @spec groups() -> [Group] 87 | %% Group = {GroupName,Properties,GroupsAndTestCases} 88 | %% GroupName = atom() 89 | %% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] 90 | %% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] 91 | %% TestCase = atom() 92 | %% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} 93 | %% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | 94 | %% repeat_until_any_ok | repeat_until_any_fail 95 | %% N = integer() | forever 96 | %% @end 97 | %%-------------------------------------------------------------------- 98 | groups() -> 99 | []. 100 | 101 | %%-------------------------------------------------------------------- 102 | %% @spec all() -> GroupsAndTestCases | {skip,Reason} 103 | %% GroupsAndTestCases = [{group,GroupName} | TestCase] 104 | %% GroupName = atom() 105 | %% TestCase = atom() 106 | %% Reason = term() 107 | %% @end 108 | %%-------------------------------------------------------------------- 109 | all() -> 110 | [test_ok_case, test_function_case, test_quote_case, 111 | test_unquote_splicing_case, test_pattern_case, test_other_case, 112 | test_macro_with_warnings, test_macro_with_error, 113 | test_macro_with_vars, test_macro_order, test_merge_rename_function]. 114 | 115 | %%-------------------------------------------------------------------- 116 | %% @spec TestCase() -> Info 117 | %% Info = [tuple()] 118 | %% @end 119 | %%-------------------------------------------------------------------- 120 | 121 | %%-------------------------------------------------------------------- 122 | %% @spec TestCase(Config0) -> 123 | %% ok | exit() | {skip,Reason} | {comment,Comment} | 124 | %% {save_config,Config1} | {skip_and_save,Reason,Config1} 125 | %% Config0 = Config1 = [tuple()] 126 | %% Reason = term() 127 | %% Comment = term() 128 | %% @end 129 | %%-------------------------------------------------------------------- 130 | test_ok_case(_Config) -> 131 | ?assertEqual(ok, macro_test:test_ok()). 132 | 133 | test_function_case(_Config) -> 134 | ?assertEqual(ok, macro_test:test_function(world)), 135 | ?assertEqual({error, foo}, macro_test:test_function(foo)), 136 | ok. 137 | 138 | test_quote_case(_Config) -> 139 | ?assertEqual({ok, ok}, macro_test:test_unquote()), 140 | ?assertEqual({ok, ok}, macro_test:test_binding()), 141 | ok. 142 | 143 | test_unquote_splicing_case(_Config) -> 144 | ?assertEqual({ok, {hello, foo, bar, world}}, macro_test:test_unquote_splicing()), 145 | {Value1, Value2} = macro_test:test_unquote_splicing_mix(), 146 | ?assertEqual({ok, [hello, foo, bar, world], {hello, foo, bar, world}}, Value1), 147 | ?assertEqual({error, foo, zaa}, Value2), 148 | ok. 149 | 150 | test_pattern_case(_Config) -> 151 | ?assertEqual({hello, world, foo, bar}, macro_test:test_match_pattern()), 152 | ?assertEqual({ok, {hello2, world, world, {hello, world}}}, macro_test:test_function_pattern_1()), 153 | ?assertEqual({error, {foo, bar}}, macro_test:test_function_pattern_2()), 154 | ?assertEqual({ok, 11}, macro_test:test_case_pattern_1()), 155 | ?assertEqual({ok, {hello, world, foo, bar}}, macro_test:test_case_pattern_2()), 156 | ?assertEqual({error, task}, macro_test:test_case_pattern_3()), 157 | ok. 158 | 159 | test_quote_code_case(_Config) -> 160 | ?assertEqual(ok, macro_test:test_quote_code()), 161 | ?assertEqual({hello, ok}, macro_test:test_quote_pos_1()), 162 | Ast = {tuple, 20, [{atom, 20, a}, {atom, 20, b}]}, 163 | NewAst = {tuple, 22, [{atom, 22, ok}, {tuple, 23, [{atom, 23, hello}, Ast]}]}, 164 | ?assertEqual(NewAst,macro_example:quote_pos_2(Ast)), 165 | ok. 166 | 167 | test_other_case(_Config) -> 168 | ?assertEqual(true, macro_test:test_case()), 169 | ?assertException(exit, throw, macro_test:test_try_catch()), 170 | ?assertEqual({hello, ok, world}, macro_test:test_function()), 171 | ?assertMatch({ok, {_, _, macro_test}}, macro_test:test_attributes()), 172 | ?assertEqual({ok, {hello, world}}, macro_test:test_group_args()), 173 | ok. 174 | 175 | test_macro_order(_Config) -> 176 | ?assertEqual({fail, ok}, macro_test:test_macro_order()), 177 | ok. 178 | 179 | test_macro_with_warnings(Config) -> 180 | Forms = astranaut_test_lib:test_module_forms(macro_with_warnings, Config), 181 | Basepos = astranaut_test_lib:get_baseline(yep, Forms), 182 | ErrorStruct = astranaut_return:run_error(astranaut_test_lib:compile_test_forms(Forms)), 183 | {FileErrors, [{File, Warnings}]} = astranaut_test_lib:realize_with_baseline(Basepos, ErrorStruct), 184 | ?assertEqual([], FileErrors), 185 | Local = macro_with_warnings__local_macro, 186 | ?assertEqual("macro_with_warnings.erl", filename:basename(File)), 187 | ?assertMatch( 188 | [{2, astranaut_macro, invalid_macro_attribute}, 189 | {3, astranaut_macro, invalid_macro_attribute}, 190 | {5, Local, noop_function}, 191 | {12, Local, noop}, 192 | {18, Local, noop}, 193 | {20, Local, noop}, 194 | {25, astranaut_quote,{unquote_splicing_pattern_non_empty_tail,[{atom, _, tail}]}} 195 | ], 196 | Warnings), 197 | ?assertEqual(ok, macro_with_warnings:test_attributes()), 198 | ok. 199 | 200 | test_macro_with_error(Config) -> 201 | Forms = astranaut_test_lib:test_module_forms(macro_with_error, Config), 202 | Baseline = astranaut_test_lib:get_baseline(yep, Forms), 203 | Return = astranaut_test_lib:compile_test_forms(Forms), 204 | ErrorStruct = astranaut_return:run_error(Return), 205 | {[{_File, Errors}], []} = astranaut_test_lib:realize_with_baseline(Baseline, ErrorStruct), 206 | 207 | Local = macro_with_error__local_macro, 208 | ?assertMatch( 209 | [{2, astranaut_macro, {invalid_import_macro_attr, {invalid_macro_tuple}}}, 210 | {3, astranaut_macro, {import_macro_failed, non_exists_module}}, 211 | {4, astranaut_macro, {unimported_macro_module, unimported_macro_module}}, 212 | {6, astranaut_macro, {undefined_macro, undefined_macro_0, 0}}, 213 | {7, astranaut_macro, {undefined_macro, undefined_macro_1, 0}}, 214 | {8, astranaut_macro, {undefined_macro, undefined_macro_2, 0}}, 215 | {9, astranaut_macro, {undefined_macro, undefined_macro_3, 0}}, 216 | {12, Local, {macro_exception, _MFA, [], _StackTrace}}, 217 | {15, Local, bar} 218 | ], Errors), 219 | %% %% TODO: astranaut:map_m does not just return error, but inject error_maker to forms. 220 | %% %% while fixing this, uncomment testcase below. 221 | %% Forms1 = astranaut_return:run(Return), 222 | %% ClauseNums = 223 | %% lists:foldl( 224 | %% fun({function, _Pos, Name, Arity, Clauses}, Acc) -> 225 | %% maps:put({Name, Arity}, length(Clauses), Acc); 226 | %% (_Form, Acc) -> 227 | %% Acc 228 | %% end, #{}, Forms1), 229 | %% ErrorMacro1 = maps:get({error_macro_1, 0}, ClauseNums, 0), 230 | %% ErrorMacro2 = maps:get({error_macro_2, 1}, ClauseNums, 1), 231 | %% ?assertEqual({0, 1}, {ErrorMacro1, ErrorMacro2}), 232 | ok. 233 | 234 | test_macro_with_vars(_Config) -> 235 | Value = macro_test:test_macro_with_vars(13), 236 | ?assertEqual(112, Value). 237 | 238 | test_merge_rename_function(_Config) -> 239 | Value1 = macro_test:test_merged_function(ok_1), 240 | Value2 = macro_test:test_merged_function(ok_2), 241 | Value3 = macro_test:test_merged_function(ok_3), 242 | Value4 = macro_test:test_merged_function(ok_4), 243 | ?assertEqual({ok_1, ok_2, ok_3, ok_4}, {Value1, Value2, Value3, Value4}), 244 | ok. 245 | -------------------------------------------------------------------------------- /test/astranaut_macro_SUITE_data/macro_example.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2019, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 29 Jan 2019 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(macro_example). 10 | 11 | -macro_options(debug_module). 12 | 13 | -include("quote.hrl"). 14 | -include("macro.hrl"). 15 | %% API 16 | 17 | -export_macro({[macro_group_args/1], [group_args]}). 18 | -export_macro({[macro_with_attributes/1], [inject_attrs]}). 19 | -export_macro([macro_exported_function/2]). 20 | -export_macro({[macro_order_outer/1], [{order, outer}]}). 21 | -export_macro({[macro_order_inner/1], [{order, inner}]}). 22 | -export_macro({[macro_merge_function/2], [{as_attr, merge_function}]}). 23 | 24 | -export_macro([quote_ok/0]). 25 | -export_macro([quote_unquote/1, quote_binding/1]). 26 | -export_macro([quote_unquote_splicing/2, quote_unquote_splicing_mix/2]). 27 | -export_macro([quote_match_pattern/1, quote_function_pattern/1, quote_case_pattern/1]). 28 | -export_macro([quote_code/0]). 29 | -export_macro([quote_pos_1/1, quote_pos_2/1]). 30 | -export_macro([macro_function/2]). 31 | -export_macro([macro_try_catch/0, macro_case/3]). 32 | -export_macro([macro_with_vars_1/1, macro_with_vars_2/1]). 33 | 34 | -exec_macro({macro_exported_function, [hello, world]}). 35 | 36 | %%%=================================================================== 37 | %%% API 38 | %%%=================================================================== 39 | quote_ok() -> 40 | quote(ok). 41 | 42 | quote_unquote(Ast) -> 43 | quote({ok, unquote(Ast)}). 44 | 45 | quote_binding(Ast) -> 46 | quote({ok, _@Ast}). 47 | 48 | quote_unquote_splicing(Ast1, Ast2) -> 49 | quote({ok, {hello, unquote_splicing([Ast1, Ast2]), world}}). 50 | 51 | quote_unquote_splicing_mix(Ast1, Ast2) -> 52 | AstList = [Ast1, Ast2], 53 | Fun1 = 54 | quote( 55 | fun(unquote_splicing = AstList) -> 56 | List = [hello, unquote_splicing(AstList), world], 57 | Tuple = {hello, unquote_splicing(AstList), world}, 58 | {ok, List, Tuple} 59 | end), 60 | Fun2 = 61 | quote( 62 | fun(unquote = Ast1, Two) -> 63 | {error, unquote(Ast1), Two} 64 | end), 65 | astranaut_lib:merge_clauses([Fun1, Fun2]). 66 | 67 | quote_match_pattern(Ast) -> 68 | quote(_A@Hello(_@Foo, _L@World)) = Ast, 69 | quote({_A@Hello, _@Foo, _L@World}). 70 | 71 | quote_function_pattern(quote = {hello, _A@World = World2} = C) -> 72 | quote({ok, {hello2, _A@World, _@World2, _@C}}); 73 | quote_function_pattern(Ast) -> 74 | quote({error, unquote(Ast)}). 75 | 76 | quote_case_pattern(Ast) -> 77 | case Ast of 78 | quote(_V@Function(_@Argument)) -> 79 | quote({ok, _V@Function(unquote(Argument) + 1)}); 80 | quote(_@Module:_@Function(_L@Arguments)) -> 81 | quote({ok, {_@Module, _@Function, _L@Arguments}}); 82 | _Other -> 83 | quote({error, unquote(Ast)}) 84 | end. 85 | 86 | quote_code() -> 87 | quote_code("test_fun()"). 88 | 89 | quote_pos_1(Ast) -> 90 | Pos = erl_syntax:get_pos(Ast), 91 | quote({hello, unquote(Ast)}, Pos). 92 | 93 | quote_pos_2(Ast) -> 94 | Pos = erl_syntax:get_pos(Ast), 95 | Line = erl_anno:line(Pos), 96 | Ast1 = quote({hello, unquote(Ast)}, Line + 3), 97 | quote({ok, unquote(Ast1)}, #{pos => Line + 2}). 98 | 99 | macro_case(Body, TrueClause, FalseClause) -> 100 | quote( 101 | case unquote(Body) of 102 | unquote(TrueClause) -> 103 | true; 104 | unquote(FalseClause) -> 105 | false 106 | end). 107 | 108 | -ifdef(OTP_RELEASE). 109 | macro_try_catch() -> 110 | Class = {var, 0, 'Class0'}, 111 | Exception = {var, 0, 'Exception0'}, 112 | Stack = {var, 0, 'Stack0'}, 113 | Expr = [Class, Exception, Stack], 114 | quote( 115 | try 116 | exit(throw) 117 | catch 118 | _@Class:_@Exception:_@Stack -> 119 | erlang:raise(_L@Expr) 120 | end). 121 | -else. 122 | macro_try_catch() -> 123 | Class = {var, 0, 'Class0'}, 124 | Exception = {var, 0, 'Exception0'}, 125 | quote( 126 | try 127 | exit(throw) 128 | catch 129 | _@Class:_@Exception -> 130 | erlang:raise(_@Class, _@Exception, erlang:get_stacktrace()) 131 | end). 132 | -endif. 133 | 134 | macro_order_outer(quote = ok) -> 135 | quote(ok); 136 | macro_order_outer(_Ast) -> 137 | quote(fail). 138 | 139 | macro_order_inner(quote = ok) -> 140 | quote(ok); 141 | macro_order_inner(_Ast) -> 142 | quote(fail). 143 | 144 | macro_function(Pattern, Middle) -> 145 | quote(fun(Head, _L@Pattern, Body) -> 146 | {Head, _@Middle, Body} 147 | end). 148 | 149 | macro_exported_function(Name, Pattern1) -> 150 | astranaut_lib:gen_exported_function( 151 | Name, 152 | quote( 153 | fun(_A@Pattern1) -> 154 | ok; 155 | (_Other) -> 156 | {error, _Other} 157 | end)). 158 | 159 | macro_merge_function(Name, Pattern) -> 160 | astranaut_lib:gen_exported_function( 161 | Name, 162 | quote( 163 | fun(_A@Pattern) -> 164 | ok_1; 165 | (Other) -> 166 | '__original__'(Other) 167 | end)). 168 | 169 | macro_with_attributes(#{file := File, pos := {Line, Col}, module := Module}) -> 170 | quote({ok, {_S@File, {_I@Line, _I@Col}, _A@Module}}); 171 | macro_with_attributes(#{file := File, pos := Line, module := Module}) -> 172 | quote({ok, {_S@File, _I@Line, _A@Module}}). 173 | 174 | macro_group_args(Asts) -> 175 | quote({ok, {unquote_splicing(Asts)}}). 176 | 177 | macro_with_vars_1(Ast) -> 178 | quote( 179 | begin 180 | A = 10, 181 | _ = A + 10, 182 | _ = A + 20, 183 | B = unquote(Ast), 184 | A + B 185 | end 186 | ). 187 | 188 | macro_with_vars_2(Ast) -> 189 | quote( 190 | begin 191 | A = 10, 192 | B = unquote(Ast), 193 | A + B 194 | end 195 | ). 196 | %%-------------------------------------------------------------------- 197 | %% @doc 198 | %% @spec 199 | %% @end 200 | %%-------------------------------------------------------------------- 201 | 202 | %%%=================================================================== 203 | %%% Internal functions 204 | %%%=================================================================== 205 | -------------------------------------------------------------------------------- /test/astranaut_macro_SUITE_data/macro_exports.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2019, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 24 Jan 2019 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(macro_exports). 10 | 11 | -macro_options(debug_module). 12 | 13 | %% API 14 | -export_macro({[macro_x/1], [as_attr]}). 15 | 16 | -include("quote.hrl"). 17 | -include("macro.hrl"). 18 | 19 | -export([hello/1]). 20 | 21 | %%%=================================================================== 22 | %%% API 23 | %%%=================================================================== 24 | 25 | macro_x(Name) -> 26 | macro_x_1(Name). 27 | 28 | macro_x_1(Name) -> 29 | Fun = 30 | case Name of 31 | hello -> 32 | quote( 33 | fun(this) -> 34 | {this, _A@Name}; 35 | (Default) -> 36 | '__original__'(Default) 37 | end); 38 | _ -> 39 | quote( 40 | fun(this) -> 41 | {this, _A@Name} 42 | end) 43 | end, 44 | astranaut_lib:gen_exported_function(Name, Fun). 45 | 46 | -macro_x([hello]). 47 | 48 | -macro_x([world]). 49 | 50 | hello(that) -> 51 | that. 52 | 53 | %%-------------------------------------------------------------------- 54 | %% @doc 55 | %% @spec 56 | %% @end 57 | %%-------------------------------------------------------------------- 58 | 59 | %%%=================================================================== 60 | %%% Internal functions 61 | %%%=================================================================== 62 | -------------------------------------------------------------------------------- /test/astranaut_macro_SUITE_data/macro_test.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2018, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 8 Dec 2018 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(macro_test). 10 | 11 | -quote_options(debug). 12 | -macro_options(debug_module). 13 | 14 | -include("quote.hrl"). 15 | -include("macro.hrl"). 16 | 17 | -define(MACRO_MODULE, macro_example). 18 | 19 | %% API 20 | -export([test_ok/0, test_function/1]). 21 | -export([test_unquote/0, test_binding/0]). 22 | -export([test_unquote_splicing/0, test_unquote_splicing_mix/0]). 23 | -export([test_match_pattern/0]). 24 | -export([test_function_pattern_1/0, test_function_pattern_2/0]). 25 | -export([test_case_pattern_1/0, test_case_pattern_2/0, test_case_pattern_3/0]). 26 | -export([test_quote_code/0, test_quote_pos_1/0, test_quote_pos_2/0]). 27 | -export([test_try_catch/0, test_case/0, test_function/0]). 28 | -export([test_attributes/0]). 29 | -export([test_group_args/0]). 30 | -export([test_macro_with_vars/1]). 31 | -export([test_macro_order/0]). 32 | 33 | -import_macro(?MACRO_MODULE). 34 | 35 | -use_macro({?MACRO_MODULE, macro_exported_function/2, [alias]}). 36 | -use_macro({?MACRO_MODULE, quote_code/0, #{alias => macro_quote_code}}). 37 | -use_macro({?MACRO_MODULE, macro_with_vars_1/1, [alias]}). 38 | -use_macro({?MACRO_MODULE, macro_with_vars_2/1, [alias]}). 39 | 40 | -local_macro([quote_ok/0]). 41 | 42 | -exec_macro({macro_exported_function, [hello, world]}). 43 | 44 | %%%=================================================================== 45 | %%% API 46 | %%%=================================================================== 47 | -spec test_ok() -> ok. 48 | test_ok() -> 49 | quote_ok(). 50 | 51 | test_function(World) -> 52 | hello(World). 53 | 54 | test_unquote() -> 55 | macro_example:quote_unquote(ok). 56 | 57 | test_binding() -> 58 | macro_example:quote_binding(ok). 59 | 60 | test_unquote_splicing() -> 61 | macro_example:quote_unquote_splicing(foo, bar). 62 | 63 | test_unquote_splicing_mix() -> 64 | F = macro_example:quote_unquote_splicing_mix(Foo, bar), 65 | A1 = F(foo, bar), 66 | A2 = F(foo, zaa), 67 | {A1, A2}. 68 | 69 | test_match_pattern() -> 70 | macro_example:quote_match_pattern(hello(world, foo, bar)). 71 | 72 | test_function_pattern_1() -> 73 | macro_example:quote_function_pattern({hello, world}). 74 | 75 | test_function_pattern_2() -> 76 | macro_example:quote_function_pattern({foo, bar}). 77 | 78 | test_case_pattern_1() -> 79 | F = fun(X) -> X end, 80 | macro_example:quote_case_pattern(F(10)). 81 | 82 | test_case_pattern_2() -> 83 | macro_example:quote_case_pattern(hello:world(foo, bar)). 84 | 85 | test_case_pattern_3() -> 86 | macro_example:quote_case_pattern(task). 87 | 88 | test_quote_code() -> 89 | macro_quote_code(). 90 | 91 | test_fun() -> 92 | ok. 93 | 94 | test_quote_pos_1() -> 95 | macro_example:quote_pos_1(ok). 96 | 97 | test_quote_pos_2() -> 98 | macro_example:quote_pos_2(ok). 99 | 100 | test_case() -> 101 | macro_example:macro_case(one_plus(), 2, 3). 102 | 103 | test_try_catch() -> 104 | macro_example:macro_try_catch(). 105 | 106 | test_function() -> 107 | F = macro_example:macro_function([_, _], ok), 108 | F(hello, foo, bar, world). 109 | 110 | test_attributes() -> 111 | macro_example:macro_with_attributes(). 112 | 113 | test_group_args() -> 114 | macro_example:macro_group_args(hello, world). 115 | 116 | test_macro_with_vars(N) -> 117 | A1 = macro_with_vars_1(N), 118 | A2 = macro_with_vars_2(A1), 119 | A3 = macro_with_vars_2(N), 120 | A4 = macro_with_vars_1(A1), 121 | A1 + A2 + A3 + A4. 122 | 123 | test_macro_order() -> 124 | Value1 = macro_example:macro_order_outer(macro_example:macro_order_outer(ok)), 125 | Value2 = macro_example:macro_order_inner(macro_example:macro_order_inner(ok)), 126 | {Value1, Value2}. 127 | 128 | -merge_function([test_merged_function, ok_1]). 129 | 130 | test_merged_function(ok_2)-> 131 | test_merged_function(ok_1); 132 | test_merged_function(ok_3)-> 133 | test_merged_function_1(ok_3); 134 | test_merged_function(ok_4)-> 135 | test_merged_function(ok_4, ok_4); 136 | test_merged_function(_A) -> 137 | ok_2. 138 | 139 | test_merged_function_1(ok_3) -> 140 | ok_3. 141 | 142 | test_merged_function(A, _B) -> 143 | A. 144 | 145 | quote_ok() -> 146 | quote(ok). 147 | 148 | one_plus() -> 149 | 1 + 1. 150 | %%-------------------------------------------------------------------- 151 | %% @doc 152 | %% @spec 153 | %% @end 154 | %%-------------------------------------------------------------------- 155 | 156 | %%%=================================================================== 157 | %%% Internal functions 158 | %%%=================================================================== 159 | -------------------------------------------------------------------------------- /test/astranaut_macro_SUITE_data/macro_with_error.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2018, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 8 Dec 2018 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(macro_with_error). 10 | 11 | -include("quote.hrl"). 12 | -include("macro.hrl"). 13 | 14 | -macro_options([debug_module_ast]). 15 | 16 | -export([format_error/1]). 17 | 18 | -baseline(yep). 19 | 20 | -import_macro({invalid_macro_tuple}). 21 | -import_macro(non_exists_module). 22 | -use_macro({unimported_macro_module, [macro_1/1]}). 23 | -local_macro([exception_error/0]). 24 | -local_macro([return_error/0, undefined_macro_0/0]). 25 | -local_macro([undefined_macro_1/0]). 26 | -export_macro([return_error/0, undefined_macro_2/0]). 27 | -export_macro([undefined_macro_3/0]). 28 | 29 | error_macro_1() -> 30 | exception_error(). 31 | 32 | error_macro_2(hello) -> 33 | return_error(); 34 | error_macro_2(_Other) -> 35 | at_least_one_clause. 36 | 37 | exception_error() -> 38 | erlang:error(foo). 39 | 40 | return_error() -> 41 | {error, bar}. 42 | 43 | format_error(bar) -> 44 | "oops, bar"; 45 | format_error(Error) -> 46 | astranaut_macro:format_error(Error). 47 | -------------------------------------------------------------------------------- /test/astranaut_macro_SUITE_data/macro_with_warnings.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2018, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 8 Dec 2018 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(macro_with_warnings). 10 | 11 | -include("quote.hrl"). 12 | -include("macro.hrl"). 13 | 14 | -macro_options([debug_module_ast]). 15 | 16 | %% API 17 | -export([test_cons/2]). 18 | 19 | -export([test_attributes/0]). 20 | -export([unquote_splicing_warnings/1]). 21 | -export([format_error/1]). 22 | 23 | -local_macro([function_macro/1]). 24 | -local_macro([noop_warnings/0]). 25 | -local_macro({[cons_macro/1], [as_attr]}). 26 | 27 | -baseline(yep). 28 | 29 | -exec_macro({function_macro, [c, d]}). 30 | -cons_macro([e, f, g]). 31 | -exec_macro({function_macro, [a]}). 32 | -exec_macro({function_macro, [b]}). 33 | 34 | %%%=================================================================== 35 | %%% API 36 | %%%=================================================================== 37 | 38 | test_attributes() -> 39 | noop_warnings(). 40 | 41 | test_cons(A, C) -> 42 | cons_macro([ 43 | A, 44 | cons_macro([ 45 | noop_warnings() 46 | ]), 47 | noop_warnings(), 48 | C 49 | ]). 50 | 51 | unquote_splicing_warnings(Ast) -> 52 | quote({unquote_splicing(Head), tail}) = Ast, 53 | Head. 54 | 55 | function_macro(a) -> 56 | astranaut_lib:gen_exported_function( 57 | test_local, 58 | quote( 59 | fun() -> 60 | ok 61 | end)); 62 | function_macro(b) -> 63 | Ast = astranaut_lib:gen_exported_function( 64 | test_local_b, 65 | quote( 66 | fun() -> 67 | ok 68 | end)), 69 | {warning, Ast, noop_function}. 70 | 71 | noop_warnings() -> 72 | {warning, quote(ok), noop}. 73 | 74 | 75 | cons_macro(Ast) -> 76 | Ast. 77 | 78 | format_error(noop) -> 79 | "oops, noop"; 80 | format_error(Error) -> 81 | astranaut_macro:format_error(Error). 82 | -------------------------------------------------------------------------------- /test/astranaut_quote_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2021, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 16 Jun 2021 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_quote_SUITE). 10 | 11 | -compile(export_all). 12 | -compile(nowarn_export_all). 13 | 14 | -include_lib("common_test/include/ct.hrl"). 15 | -include_lib("eunit/include/eunit.hrl"). 16 | 17 | %%-------------------------------------------------------------------- 18 | %% @spec suite() -> Info 19 | %% Info = [tuple()] 20 | %% @end 21 | %%-------------------------------------------------------------------- 22 | suite() -> 23 | [{timetrap,{seconds,30}}]. 24 | 25 | %%-------------------------------------------------------------------- 26 | %% @spec init_per_suite(Config0) -> 27 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 28 | %% Config0 = Config1 = [tuple()] 29 | %% Reason = term() 30 | %% @end 31 | %%-------------------------------------------------------------------- 32 | init_per_suite(Config) -> 33 | TestModules = [quote_example], 34 | astranaut_test_lib:load_data_modules(Config, TestModules). 35 | %%-------------------------------------------------------------------- 36 | %% @spec end_per_suite(Config0) -> term() | {save_config,Config1} 37 | %% Config0 = Config1 = [tuple()] 38 | %% @end 39 | %%-------------------------------------------------------------------- 40 | end_per_suite(_Config) -> 41 | ok. 42 | 43 | %%-------------------------------------------------------------------- 44 | %% @spec init_per_group(GroupName, Config0) -> 45 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 46 | %% GroupName = atom() 47 | %% Config0 = Config1 = [tuple()] 48 | %% Reason = term() 49 | %% @end 50 | %%-------------------------------------------------------------------- 51 | init_per_group(_GroupName, Config) -> 52 | Config. 53 | 54 | %%-------------------------------------------------------------------- 55 | %% @spec end_per_group(GroupName, Config0) -> 56 | %% term() | {save_config,Config1} 57 | %% GroupName = atom() 58 | %% Config0 = Config1 = [tuple()] 59 | %% @end 60 | %%-------------------------------------------------------------------- 61 | end_per_group(_GroupName, _Config) -> 62 | ok. 63 | 64 | %%-------------------------------------------------------------------- 65 | %% @spec init_per_testcase(TestCase, Config0) -> 66 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 67 | %% TestCase = atom() 68 | %% Config0 = Config1 = [tuple()] 69 | %% Reason = term() 70 | %% @end 71 | %%-------------------------------------------------------------------- 72 | init_per_testcase(_TestCase, Config) -> 73 | Config. 74 | 75 | %%-------------------------------------------------------------------- 76 | %% @spec end_per_testcase(TestCase, Config0) -> 77 | %% term() | {save_config,Config1} | {fail,Reason} 78 | %% TestCase = atom() 79 | %% Config0 = Config1 = [tuple()] 80 | %% Reason = term() 81 | %% @end 82 | %%-------------------------------------------------------------------- 83 | end_per_testcase(_TestCase, _Config) -> 84 | ok. 85 | 86 | %%-------------------------------------------------------------------- 87 | %% @spec groups() -> [Group] 88 | %% Group = {GroupName,Properties,GroupsAndTestCases} 89 | %% GroupName = atom() 90 | %% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] 91 | %% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] 92 | %% TestCase = atom() 93 | %% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} 94 | %% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | 95 | %% repeat_until_any_ok | repeat_until_any_fail 96 | %% N = integer() | forever 97 | %% @end 98 | %%-------------------------------------------------------------------- 99 | groups() -> 100 | []. 101 | 102 | %%-------------------------------------------------------------------- 103 | %% @spec all() -> GroupsAndTestCases | {skip,Reason} 104 | %% GroupsAndTestCases = [{group,GroupName} | TestCase] 105 | %% GroupName = atom() 106 | %% TestCase = atom() 107 | %% Reason = term() 108 | %% @end 109 | %%-------------------------------------------------------------------- 110 | all() -> 111 | [test_literal_atom, test_literal_integer, test_literal_tuple, 112 | test_pattern_match, test_pattern_function_1, test_pattern_function_2, test_pattern_function_3, 113 | test_pattern_case_1, test_pattern_case_2, test_pattern_case_3, 114 | test_function_expression, test_function_expression_error, 115 | test_named_function_expression_1, test_named_function_expression_2, 116 | test_pos_1, test_pos_2, 117 | test_unquote, test_unquote_map, test_unquote_map_match, test_unquote_map_match_list, 118 | test_unquote_record, test_unquote_record_match, test_unquote_record_match_list, 119 | test_binding, test_atom_binding, 120 | test_dynamic_binding, test_dynamic_binding_pattern, 121 | test_unquote_splicing_1, test_unquote_splicing_2, test_unquote_splicing_map, 122 | test_type, test_type_atom, test_type_map, test_type_tuple, test_exp_type, test_remote_type, 123 | test_record, test_spec, test_guard]. 124 | %%-------------------------------------------------------------------- 125 | %% @spec TestCase(Config0) -> 126 | %% ok | exit() | {skip,Reason} | {comment,Comment} | 127 | %% {save_config,Config1} | {skip_and_save,Reason,Config1} 128 | %% Config0 = Config1 = [tuple()] 129 | %% Reason = term() 130 | %% Comment = term() 131 | %% @end 132 | %%-------------------------------------------------------------------- 133 | test_literal_atom(_Config) -> 134 | Atom = quote_example:atom(), 135 | Ast = astranaut_lib:abstract_form(ok), 136 | ?assertEqual(Ast, Atom), 137 | ok. 138 | 139 | test_literal_integer(_Config) -> 140 | Integer = quote_example:integer(), 141 | Ast = astranaut_lib:abstract_form(10), 142 | ?assertEqual(Ast, Integer), 143 | ok. 144 | 145 | test_literal_tuple(_Config) -> 146 | Tuple = quote_example:tuple(), 147 | Ast = astranaut_lib:abstract_form({hello, world}), 148 | ?assertEqual(Ast, Tuple), 149 | ok. 150 | 151 | test_pattern_match(_Config) -> 152 | Ast1 = merl:quote(0, "hello(world, foo, bar)"), 153 | Pattern = quote_example:match_pattern(Ast1), 154 | Ast2 = astranaut_lib:abstract_form({hello, world, foo, bar}), 155 | ?assertEqual(Ast2, Pattern), 156 | ok. 157 | 158 | test_pattern_function_1(_Config) -> 159 | Ast1 = astranaut_lib:abstract_form({hello, world}), 160 | Pattern = quote_example:function_pattern(Ast1), 161 | Ast2 = astranaut_lib:abstract_form({ok, {hello2, world, world, {hello, world}}}), 162 | ?assertEqual(Ast2, Pattern), 163 | ok. 164 | 165 | test_pattern_function_2(_Config) -> 166 | Ast1 = astranaut_lib:abstract_form({hello2, world}), 167 | Pattern = quote_example:function_pattern(Ast1), 168 | Ast2 = astranaut_lib:abstract_form({ok, {hello3, world, world, {hello2, world}}}), 169 | ?assertEqual(Ast2, Pattern), 170 | ok. 171 | 172 | test_pattern_function_3(_Config) -> 173 | Ast1 = astranaut_lib:abstract_form({foo, bar}), 174 | Pattern = quote_example:function_pattern(Ast1), 175 | Ast2 = astranaut_lib:abstract_form({error, {foo, bar}}), 176 | ?assertEqual(Ast2, Pattern), 177 | ok. 178 | 179 | test_pattern_case_1(_Config) -> 180 | Ast1 = merl:quote(0, "F(10)"), 181 | Pattern = quote_example:case_pattern(Ast1), 182 | Ast2 = merl:quote(0, "{ok, F(10 + 1)}"), 183 | ?assertEqual(Ast2, Pattern), 184 | ok. 185 | 186 | test_pattern_case_2(_Config) -> 187 | Ast1 = merl:quote(0, "hello:world(foo, bar)"), 188 | Pattern = quote_example:case_pattern(Ast1), 189 | Ast2 = astranaut_lib:abstract_form({ok, {hello, world, foo, bar}}), 190 | ?assertEqual(Ast2, Pattern), 191 | ok. 192 | 193 | test_pattern_case_3(_Config) -> 194 | Ast1 = merl:quote(0, "task"), 195 | Pattern = quote_example:case_pattern(Ast1), 196 | Ast2 = astranaut_lib:abstract_form({error, task}), 197 | ?assertEqual(Ast2, Pattern), 198 | ok. 199 | 200 | test_function_expression(_Config) -> 201 | Expression = quote_example:function_expression(send), 202 | Ast = merl:quote(0, "fun send/3"), 203 | ?assertEqual(Ast, Expression), 204 | ok. 205 | 206 | test_function_expression_error(_Config) -> 207 | ?assertException(error, {unexpected_type_of_var, _, atom_value, 233}, quote_example:function_expression(233)), 208 | ok. 209 | 210 | test_named_function_expression_1(_Config) -> 211 | Expression = quote_example:named_function_expression_1('H'), 212 | Ast = merl:quote(0, "fun H(0) -> 0; H(N@quote_example) -> H(N@quote_example - 1) + N@quote_example end"), 213 | ?assertEqual(Ast, Expression), 214 | ok. 215 | 216 | test_named_function_expression_2(_Config) -> 217 | Expression = quote_example:named_function_expression_2(), 218 | Ast = merl:quote(0, "fun Name@quote_example(0) -> 0; Name@quote_example(N@quote_example) -> Name@quote_example(N@quote_example - 1) + N@quote_example end"), 219 | ?assertEqual(Ast, Expression), 220 | ok. 221 | 222 | test_pos_1(_Config) -> 223 | Ast = {atom, {10, 11}, world}, 224 | HelloWorld = quote_example:pos_1(Ast), 225 | ?assertEqual({tuple, {10, 11}, [{atom, {10, 11}, hello}, {atom, {10, 11}, world}]}, HelloWorld), 226 | ok. 227 | 228 | test_pos_2(_Config) -> 229 | Ast = {atom, {10, 11}, world}, 230 | HelloWorld = quote_example:pos_2(Ast), 231 | ?assertEqual({tuple, 12, [{atom, 12, ok}, {tuple, 13, [{atom, 13, hello}, {atom, {10, 11}, world}]}]}, HelloWorld), 232 | ok. 233 | 234 | test_unquote(_Config) -> 235 | Atom = quote_example:atom(), 236 | OkAtom = quote_example:unquote(Atom), 237 | Ast = astranaut_lib:abstract_form({ok, ok}), 238 | ?assertEqual(Ast, OkAtom), 239 | ok. 240 | 241 | test_unquote_map(_Config) -> 242 | Ast1 = merl:quote(0, "#{a => 1}"), 243 | [Ast2] = erl_syntax:map_expr_fields(Ast1), 244 | Map = quote_example:unquote_map(Ast2), 245 | Ast3 = merl:quote(0, "{ok, #{a => 1}}"), 246 | ?assertEqual(Ast3, Map), 247 | ok. 248 | 249 | test_unquote_map_match(_Config) -> 250 | Ast1 = merl:quote(0, "#{a => 1}"), 251 | [Ast2] = erl_syntax:map_expr_fields(Ast1), 252 | Matched = quote_example:unquote_map_match(Ast1), 253 | ?assertEqual(Ast2, Matched), 254 | ok. 255 | 256 | test_unquote_map_match_list(_Config) -> 257 | Ast1 = merl:quote(0, "#{a => 1, b => 2, c => 3}"), 258 | Ast2 = merl:quote(0, "#{b => 2, c => 3}"), 259 | Ast3 = erl_syntax:map_expr_fields(Ast2), 260 | Map = quote_example:unquote_map_match_list(Ast1), 261 | ?assertEqual(Ast3, Map), 262 | ok. 263 | 264 | test_unquote_record(_Config) -> 265 | Ast1 = merl:quote(0, "#test{a = 1}"), 266 | {record, _, _Name, [Ast2]} = Ast1, 267 | Rec = quote_example:unquote_record(Ast2), 268 | Ast3 = merl:quote(0, "{ok, #test{a = 1}}"), 269 | ?assertEqual(Ast3, Rec), 270 | ok. 271 | 272 | test_unquote_record_match(_Config) -> 273 | Ast1 = merl:quote(0, "#test{a = 1}"), 274 | {record, _, _Name, [Ast2]} = Ast1, 275 | Matched = quote_example:unquote_record_match(Ast1), 276 | ?assertEqual(Ast2, Matched), 277 | ok. 278 | 279 | test_unquote_record_match_list(_Config) -> 280 | Ast1 = merl:quote(0, "#test{a = 1, b = 2, c = 3}"), 281 | Ast2 = merl:quote(0, "#test{b = 2, c = 3}"), 282 | {record, _, _Name, Ast3} = Ast2, 283 | RecordFields = quote_example:unquote_record_match_list(Ast1), 284 | ?assertEqual(Ast3, RecordFields), 285 | ok. 286 | 287 | test_binding(_Config) -> 288 | Atom = quote_example:atom(), 289 | OkAtom = quote_example:binding(Atom), 290 | Ast = astranaut_lib:abstract_form({ok, ok}), 291 | ?assertEqual(Ast, OkAtom), 292 | ok. 293 | 294 | test_atom_binding(_Config) -> 295 | OkAtom = quote_example:atom_binding(hello), 296 | Ast = astranaut_lib:abstract_form({ok, hello}), 297 | ?assertEqual(Ast, OkAtom), 298 | ok. 299 | 300 | test_dynamic_binding(_Config) -> 301 | Ok = quote_example:dynamic_binding({hello, 10, 10.0}), 302 | Ast = astranaut_lib:abstract_form({ok, {hello, 10, 10.0}}), 303 | ?assertEqual(Ast, Ok), 304 | ok. 305 | 306 | test_dynamic_binding_pattern(_Config) -> 307 | World = quote_example:dynamic_binding_pattern(), 308 | ?assertEqual(world, World), 309 | ok. 310 | 311 | test_unquote_splicing_1(_Config) -> 312 | Hello = quote_example:atom(hello), 313 | World = quote_example:atom(world), 314 | HelloWorld = quote_example:unquote_splicing_1(Hello, World), 315 | Ast = astranaut_lib:abstract_form({ok, {hello, hello, world, world}}), 316 | ?assertEqual(Ast, HelloWorld), 317 | ok. 318 | 319 | test_unquote_splicing_2(_Config) -> 320 | Hello = quote_example:atom(hello), 321 | World = quote_example:atom(world), 322 | HelloWorld = quote_example:unquote_splicing_2(Hello, World), 323 | Ast = astranaut_lib:abstract_form({ok, [hello, hello, world, world]}), 324 | ?assertEqual(Ast, HelloWorld), 325 | ok. 326 | 327 | test_unquote_splicing_map(_Config) -> 328 | Ast1 = erl_syntax:map_expr_fields(merl:quote(0, "#{a => 0, b => 1}")), 329 | Ast2 = erl_syntax:map_expr_fields(merl:quote(0, "#{c => 2, d => 3}")), 330 | HelloWorld = quote_example:unquote_splicing_map(Ast1, Ast2), 331 | Ast3 = merl:quote(0, "{ok, #{hello => 1, a => 0, b => 1, c => 2, d => 3, world => 2}}"), 332 | ?assertEqual(Ast3, HelloWorld), 333 | ok. 334 | 335 | test_type(_Config) -> 336 | Type = quote_example:type(hello, world), 337 | Ast = merl:quote(0, "-type hello() :: world()."), 338 | ?assertEqual(Ast, Type), 339 | ok. 340 | 341 | test_type_atom(_Config) -> 342 | Type = quote_example:type(hello, atom), 343 | Ast = merl:quote(0, "-type hello() :: atom()."), 344 | ?assertEqual(Ast, Type), 345 | ok. 346 | 347 | test_type_tuple(_Config) -> 348 | Type = quote_example:type(hello, tuple), 349 | Ast = merl:quote(0, "-type hello() :: tuple()."), 350 | ?assertEqual(Ast, Type), 351 | ok. 352 | 353 | test_type_map(_Config) -> 354 | Type = quote_example:type(hello, map), 355 | Ast = merl:quote(0, "-type hello() :: map()."), 356 | ?assertEqual(Ast, Type), 357 | ok. 358 | 359 | test_exp_type(_Config) -> 360 | Type = quote_example:exp_type(hello), 361 | Ast1 = merl:quote(0, "-type hello() :: hello:world()."), 362 | ?assertEqual(Ast1, Type). 363 | 364 | test_remote_type(_Config) -> 365 | World = astranaut_lib:abstract_form(world), 366 | Type = quote_example:remote_type(hello, hello, World), 367 | Ast = merl:quote(0, "-type hello() :: hello:world()."), 368 | ?assertEqual(Ast, Type), 369 | ok. 370 | 371 | test_record(_Config) -> 372 | Record = quote_example:record(hello_world), 373 | Ast = merl:quote(0, "-record(hello_world, {id, hello, world})."), 374 | ?assertEqual(Ast, Record), 375 | ok. 376 | 377 | test_spec(_Config) -> 378 | Spec = quote_example:spec(hello, map, world), 379 | Ast = merl:quote(0, "-spec hello(map()) -> world()."), 380 | ?assertEqual(Ast, Spec), 381 | ok. 382 | 383 | 384 | test_guard(_Config) -> 385 | Var = merl:quote(0, "A"), 386 | Guard = merl:quote(0, "A =:= hello"), 387 | TestGuard = quote_example:guard(Var, Guard), 388 | Ast = merl:quote( 389 | ["case A of", 390 | " A when A =:= hello ->", 391 | " A;", 392 | " _ ->", 393 | " {error, not_match}" 394 | "end"]), 395 | Ast1 = astranaut_lib:replace_pos(Ast, 0), 396 | ?assertEqual(Ast1, TestGuard), 397 | ok. 398 | -------------------------------------------------------------------------------- /test/astranaut_quote_SUITE_data/quote_example.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2021, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 16 Jun 2021 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(quote_example). 10 | 11 | -include("quote.hrl"). 12 | 13 | %% API 14 | -compile(export_all). 15 | -compile(nowarn_export_all). 16 | 17 | atom() -> 18 | quote(ok). 19 | 20 | atom(Atom) -> 21 | quote(_A@Atom). 22 | 23 | integer() -> 24 | quote(10). 25 | 26 | tuple() -> 27 | quote({hello, world}). 28 | 29 | unquote(Ast) -> 30 | quote({ok, unquote(Ast)}). 31 | 32 | unquote_map(Ast) -> 33 | quote({ok, #{unquote => Ast}}). 34 | 35 | unquote_map_match(Ast) -> 36 | quote(#{unquote => Assoc}) = Ast, 37 | Assoc. 38 | 39 | unquote_map_match_list(Ast) -> 40 | quote(#{a => 1, unquote_splicing => Assocs}) = Ast, 41 | Assocs. 42 | 43 | unquote_record(Ast) -> 44 | quote({ok, #test{unquote = Ast}}). 45 | 46 | unquote_record_match(Ast) -> 47 | quote(#test{unquote = Assoc}) = Ast, 48 | Assoc. 49 | 50 | unquote_record_match_list(Ast) -> 51 | quote(#test{a = 1, unquote_splicing = Assocs}) = Ast, 52 | Assocs. 53 | 54 | binding(Ast) -> 55 | quote({ok, _@Ast}). 56 | 57 | atom_binding(Atom) -> 58 | quote({ok, _A@Atom}). 59 | 60 | dynamic_binding(Value) -> 61 | quote({ok, _D@Value}). 62 | 63 | dynamic_binding_pattern() -> 64 | quote({hello, _D@World}) = quote({hello, world}), 65 | World. 66 | 67 | unquote_splicing_1(Ast1, Ast2) -> 68 | quote({ok, {hello, unquote_splicing([Ast1, Ast2]), world}}). 69 | 70 | unquote_splicing_2(Ast1, Ast2) -> 71 | quote({ok, [hello, unquote_splicing([Ast1, Ast2]), world]}). 72 | 73 | unquote_splicing_map(Ast1, Ast2) -> 74 | quote({ok, #{hello => 1, unquote_splicing => Ast1 ++ Ast2, world => 2}}). 75 | 76 | unquote_splicing_mix(Ast1, Ast2) -> 77 | AstList = [Ast1, Ast2], 78 | Fun1 = 79 | quote( 80 | fun(unquote_splicing = AstList) -> 81 | List = [hello, unquote_splicing(AstList), world], 82 | Tuple = {hello, unquote_splicing(AstList), world}, 83 | {ok, List, Tuple} 84 | end), 85 | Fun2 = 86 | quote( 87 | fun(unquote = Ast1, Two) -> 88 | {error, unquote(Ast1), Two} 89 | end), 90 | astranaut_lib:merge_clauses([Fun1, Fun2]). 91 | 92 | match_pattern(Ast) -> 93 | quote(_A@Hello(_@Foo, _L@World)) = Ast, 94 | quote({_A@Hello, _@Foo, _L@World}). 95 | 96 | bind_string(String) -> 97 | quote({ok, _S@String}). 98 | 99 | function_pattern(quote = {hello, _A@World = World2} = C) -> 100 | quote({ok, {hello2, _A@World, _@World2, _@C}}); 101 | function_pattern(quote = {hello2, _@World} = C) -> 102 | quote(_A@World2) = World, 103 | quote({ok, {hello3, _A@World2, _@World, _@C}}); 104 | function_pattern(Ast) -> 105 | quote({error, unquote(Ast)}). 106 | 107 | case_pattern(Ast) -> 108 | case Ast of 109 | quote(_V@Function(_@Argument)) -> 110 | quote({ok, _V@Function(unquote(Argument) + 1)}); 111 | quote(_@Module:_@Function(_L@Arguments)) -> 112 | quote({ok, {_@Module, _@Function, _L@Arguments}}); 113 | _Other -> 114 | quote({error, unquote(Ast)}) 115 | end. 116 | 117 | function_expression(Name) -> 118 | quote(fun '_A@Name'/3). 119 | 120 | named_function_expression_1(Name) -> 121 | quote(fun _V@Name(0) -> 0; 122 | _V@Name(N) -> _V@Name(N -1) + N 123 | end). 124 | 125 | named_function_expression_2() -> 126 | quote(fun Name(0) -> 0; 127 | Name(N) -> Name(N -1) + N 128 | end). 129 | 130 | code() -> 131 | quote_code("test_fun()"). 132 | 133 | pos_1(Ast) -> 134 | Pos = erl_syntax:get_pos(Ast), 135 | quote({hello, unquote(Ast)}, Pos). 136 | 137 | pos_2(Ast) -> 138 | Pos = erl_syntax:get_pos(Ast), 139 | Line = erl_anno:line(Pos), 140 | Ast1 = quote({hello, unquote(Ast)}, Line + 3), 141 | quote({ok, unquote(Ast1)}, #{pos => Line + 2}). 142 | 143 | type(Name, Value) -> 144 | quote_code("-type '_A@Name'() :: '_A@Value'()."). 145 | 146 | exp_type(Name) -> 147 | Type = quote_type_code("hello:world()"), 148 | quote_code("-type '_A@Name'() :: _@Type."). 149 | 150 | remote_type(Name, Module, Type) -> 151 | quote_code("-type '_A@Name'() :: '_A@Module':'_@Type'()."). 152 | 153 | record(Name) -> 154 | quote_code("-record('_A@Name', {id, hello, world})."). 155 | 156 | spec(Name, Value1, Value2) -> 157 | quote_code("-spec '_A@Name'('_A@Value1'()) -> '_A@Value2'()."). 158 | 159 | guard(Var, Cond) -> 160 | quote( 161 | case _@Var of 162 | _@Var when _@Cond -> 163 | _@Var; 164 | _ -> 165 | {error, not_match} 166 | end). 167 | -------------------------------------------------------------------------------- /test/astranaut_rebinding_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2019, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 23 Sep 2019 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_rebinding_SUITE). 10 | 11 | -compile(export_all). 12 | -compile(nowarn_export_all). 13 | 14 | -include_lib("eunit/include/eunit.hrl"). 15 | -include_lib("common_test/include/ct.hrl"). 16 | 17 | %%-------------------------------------------------------------------- 18 | %% @spec suite() -> Info 19 | %% Info = [tuple()] 20 | %% @end 21 | %%-------------------------------------------------------------------- 22 | suite() -> 23 | [{timetrap,{seconds,30}}]. 24 | 25 | %%-------------------------------------------------------------------- 26 | %% @spec init_per_suite(Config0) -> 27 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 28 | %% Config0 = Config1 = [tuple()] 29 | %% Reason = term() 30 | %% @end 31 | %%-------------------------------------------------------------------- 32 | init_per_suite(Config) -> 33 | erlang:system_flag(backtrace_depth, 20), 34 | TestModules = [rebinding_example, rebinding_test], 35 | astranaut_test_lib:load_data_modules(Config, TestModules), 36 | Forms = astranaut_test_lib:test_module_forms(rebinding_test, Config), 37 | Forms1 = astranaut_rebinding:parse_transform(Forms, astranaut_test_lib:compile_opts()), 38 | Functions = 39 | lists:foldl( 40 | fun({function, _Pos, Name, _Arity, Clauses}, Acc) -> 41 | Clauses1 = astranaut_lib:replace_pos(Clauses, 0), 42 | maps:put(Name, Clauses1, Acc); 43 | (_Form, Acc) -> 44 | Acc 45 | end, #{}, Forms1), 46 | [{functions, Functions}|Config]. 47 | 48 | %%-------------------------------------------------------------------- 49 | %% @spec end_per_suite(Config0) -> term() | {save_config,Config1} 50 | %% Config0 = Config1 = [tuple()] 51 | %% @end 52 | %%-------------------------------------------------------------------- 53 | end_per_suite(_Config) -> 54 | ok. 55 | 56 | %%-------------------------------------------------------------------- 57 | %% @spec init_per_group(GroupName, Config0) -> 58 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 59 | %% GroupName = atom() 60 | %% Config0 = Config1 = [tuple()] 61 | %% Reason = term() 62 | %% @end 63 | %%-------------------------------------------------------------------- 64 | init_per_group(_GroupName, Config) -> 65 | Config. 66 | 67 | %%-------------------------------------------------------------------- 68 | %% @spec end_per_group(GroupName, Config0) -> 69 | %% term() | {save_config,Config1} 70 | %% GroupName = atom() 71 | %% Config0 = Config1 = [tuple()] 72 | %% @end 73 | %%-------------------------------------------------------------------- 74 | end_per_group(_GroupName, _Config) -> 75 | ok. 76 | 77 | %%-------------------------------------------------------------------- 78 | %% @spec init_per_testcase(TestCase, Config0) -> 79 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 80 | %% TestCase = atom() 81 | %% Config0 = Config1 = [tuple()] 82 | %% Reason = term() 83 | %% @end 84 | %%-------------------------------------------------------------------- 85 | init_per_testcase(_TestCase, Config) -> 86 | Config. 87 | 88 | %%-------------------------------------------------------------------- 89 | %% @spec end_per_testcase(TestCase, Config0) -> 90 | %% term() | {save_config,Config1} | {fail,Reason} 91 | %% TestCase = atom() 92 | %% Config0 = Config1 = [tuple()] 93 | %% Reason = term() 94 | %% @end 95 | %%-------------------------------------------------------------------- 96 | end_per_testcase(_TestCase, _Config) -> 97 | ok. 98 | 99 | %%-------------------------------------------------------------------- 100 | %% @spec groups() -> [Group] 101 | %% Group = {GroupName,Properties,GroupsAndTestCases} 102 | %% GroupName = atom() 103 | %% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] 104 | %% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] 105 | %% TestCase = atom() 106 | %% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} 107 | %% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | 108 | %% repeat_until_any_ok | repeat_until_any_fail 109 | %% N = integer() | forever 110 | %% @end 111 | %%-------------------------------------------------------------------- 112 | groups() -> 113 | []. 114 | 115 | %%-------------------------------------------------------------------- 116 | %% @spec all() -> GroupsAndTestCases | {skip,Reason} 117 | %% GroupsAndTestCases = [{group,GroupName} | TestCase] 118 | %% GroupName = atom() 119 | %% TestCase = atom() 120 | %% Reason = term() 121 | %% @end 122 | %%-------------------------------------------------------------------- 123 | all() -> 124 | [test_lc, test_function, test_case, test_if, 125 | test_map, test_map_update, 126 | test_rec, test_rec_update, 127 | test_operator, test_list, test_tuple, 128 | test_pattern_save_var, test_pattern_save_var_in_fun, test_pattern_save_var_in_case 129 | ]. 130 | 131 | %%-------------------------------------------------------------------- 132 | %% @spec TestCase() -> Info 133 | %% Info = [tuple()] 134 | %% @end 135 | %%-------------------------------------------------------------------- 136 | test_rebinding_lc() -> 137 | []. 138 | 139 | %%-------------------------------------------------------------------- 140 | %% @spec TestCase(Config0) -> 141 | %% ok | exit() | {skip,Reason} | {comment,Comment} | 142 | %% {save_config,Config1} | {skip_and_save,Reason,Config1} 143 | %% Config0 = Config1 = [tuple()] 144 | %% Reason = term() 145 | %% Comment = term() 146 | %% @end 147 | %%-------------------------------------------------------------------- 148 | test_lc(Config) -> 149 | equal_functions(test_lc, test_lc_origin, Config), 150 | A = rebinding_test:test_lc(10), 151 | B = rebinding_test:test_lc_origin(10), 152 | ?assertEqual(A, B), 153 | ok. 154 | 155 | test_function(Config) -> 156 | equal_functions(test_function, test_function_origin, Config), 157 | A = rebinding_test:test_function(10), 158 | B = rebinding_test:test_function_origin(10), 159 | ?assertEqual(A, B), 160 | ok. 161 | 162 | test_case(Config) -> 163 | equal_functions(test_case, test_case_origin, Config), 164 | equal_functions(test_case_pinned, test_case_origin, Config), 165 | A = rebinding_test:test_case(10), 166 | B = rebinding_test:test_case_origin(10), 167 | C = rebinding_test:test_case_pinned(10), 168 | ?assertEqual(A, B), 169 | ?assertEqual(A, C), 170 | ok. 171 | 172 | test_if(Config) -> 173 | equal_functions(test_if, test_if_origin, Config), 174 | A = rebinding_test:test_if(10), 175 | B = rebinding_test:test_if_origin(10), 176 | ?assertEqual(A, B), 177 | ok. 178 | 179 | test_rec(Config) -> 180 | equal_functions(test_rec, test_rec_origin, Config), 181 | A = rebinding_test:test_rec(10), 182 | B = rebinding_test:test_rec_origin(10), 183 | ?assertEqual(A, B), 184 | ok. 185 | 186 | test_rec_update(Config) -> 187 | equal_functions(test_rec_update, test_rec_update_origin, Config), 188 | A = rebinding_test:test_rec_update(10), 189 | B = rebinding_test:test_rec_update_origin(10), 190 | ?assertEqual(A, B), 191 | ok. 192 | 193 | test_map(Config) -> 194 | equal_functions(test_map, test_map_origin, Config), 195 | A = rebinding_test:test_map(10), 196 | B = rebinding_test:test_map_origin(10), 197 | ?assertEqual(A, B), 198 | ok. 199 | 200 | test_map_update(Config) -> 201 | equal_functions(test_map_update, test_map_update_origin, Config), 202 | A = rebinding_test:test_map_update(10), 203 | B = rebinding_test:test_map_update_origin(10), 204 | ?assertEqual(A, B), 205 | ok. 206 | 207 | test_operator(Config) -> 208 | equal_functions(test_operator, test_operator_origin, Config), 209 | A = rebinding_test:test_operator(10), 210 | B = rebinding_test:test_operator_origin(10), 211 | ?assertEqual(A, B), 212 | ok. 213 | 214 | test_tuple(Config) -> 215 | equal_functions(test_tuple, test_tuple_origin, Config), 216 | A = rebinding_test:test_tuple(10), 217 | B = rebinding_test:test_tuple_origin(10), 218 | ?assertEqual(A, B), 219 | ok. 220 | 221 | test_list(Config) -> 222 | equal_functions(test_list, test_list_origin, Config), 223 | A = rebinding_test:test_list(10), 224 | B = rebinding_test:test_list_origin(10), 225 | ?assertEqual(A, B), 226 | ok. 227 | 228 | test_pattern_save_var(_Config) -> 229 | A = rebinding_test:test_pattern_same_var(1, 2), 230 | B = rebinding_test:test_pattern_same_var(3, 3), 231 | ?assertEqual(3, A), 232 | ?assertEqual(7, B), 233 | ok. 234 | 235 | test_pattern_save_var_in_fun(_Config) -> 236 | A = rebinding_test:test_pattern_same_var_in_fun(1, 2), 237 | B = rebinding_test:test_pattern_same_var_in_fun(3, 3), 238 | ?assertEqual(3, A), 239 | ?assertEqual(7, B), 240 | ok. 241 | 242 | test_pattern_save_var_in_case(_Config) -> 243 | A = rebinding_test:test_pattern_same_var_in_case(1, 2), 244 | B = rebinding_test:test_pattern_same_var_in_case(3, 3), 245 | ?assertEqual(3, A), 246 | ?assertEqual(7, B), 247 | ok. 248 | 249 | 250 | equal_functions(F1, F2, Config) -> 251 | Functions = proplists:get_value(functions, Config), 252 | ?assertEqual(maps:get(F1, Functions), maps:get(F2, Functions)). 253 | -------------------------------------------------------------------------------- /test/astranaut_rebinding_SUITE_data/rebinding_example.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2019, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 24 Sep 2019 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(rebinding_example). 10 | 11 | %% API 12 | -export([]). 13 | 14 | -include("rebinding.hrl"). 15 | 16 | -rebinding_all([debug]). 17 | 18 | %%%=================================================================== 19 | %%% API 20 | %%%=================================================================== 21 | -export([test/1, hello/2, hello_f/1]). 22 | 23 | test(ABC) -> 24 | {ABC, ABC} = {ABC + 1, ABC + 1}, 25 | {ABC, ABC} = {ABC + 1, ABC + 1}, 26 | EFG = ABC + 1, 27 | +ABC = ABC + 1, 28 | F = fun EFG (0, {EFG, EFG} = ABC, ABC) -> 0; EFG (N, ABC, ABC) -> EFG(N - 1) end, 29 | ABC = 30 | case EFG of 31 | +ABC -> 32 | ABC = ABC + 1, 33 | ABC; 34 | EFG -> 35 | ABC = EFG + 2, 36 | ABC 37 | end, 38 | D = <> >>, 39 | io:format("d is ~p~n", [D]), 40 | D = [ABC || ABC <- [begin ABC = ABC + 1, ABC end]], 41 | EFG = 42 | case EFG of 43 | +ABC -> 44 | ABC = ABC + 1, 45 | ABC; 46 | +EFG -> 47 | ABC = EFG + 2, 48 | ABC = ABC + 2, 49 | ABC 50 | end, 51 | C = 52 | try ABC of 53 | +EFG -> 54 | ABC = ABC + 1, 55 | ABC; 56 | +ABC -> 57 | ABC + 1 58 | catch 59 | exception:eft -> 60 | efg; 61 | _:ABC when (ABC =:= 3) -> 62 | {ABC, ABC} 63 | end, 64 | Zero = 65 | lists:map( 66 | fun(Zero) -> 67 | ABC = Zero + 1, 68 | Zero = ABC + 1, 69 | Zero 70 | end, lists:seq(1, 10)), 71 | io:format("Zero is ~p~n", [Zero]), 72 | io:format("abc is ~p~n", [ABC]), 73 | io:format("efg is ~p~n", [EFG]), 74 | io:format("d is ~p~n", [D]), 75 | io:format("c is ~p~n", [C]), 76 | F(10). 77 | 78 | 79 | hello(A, B) -> 80 | A = 81 | case A of 82 | +B -> 83 | B = A + B, 84 | A = A + B, 85 | B = A + B, 86 | B; 87 | +A -> 88 | B = A + B, 89 | B 90 | end, 91 | B = 92 | case A of 93 | +B -> 94 | A = 95 | begin 96 | B = A + B, 97 | A = A + B, 98 | A 99 | end, 100 | B = A + B, 101 | B; 102 | +A -> 103 | B = A + B, 104 | B 105 | end, 106 | {A, B}. 107 | 108 | hello_f(A) -> 109 | A = A + 1, 110 | F = fun F (0) -> 0; F (A) -> A = F(A - 1), A end, 111 | A = F(A), 112 | F = fun F (0) -> 0; F (A) -> A = F(A - 1), A end, 113 | A = F(A), 114 | A. 115 | 116 | %%-------------------------------------------------------------------- 117 | %% @doc 118 | %% @spec 119 | %% @end 120 | %%-------------------------------------------------------------------- 121 | 122 | %%%=================================================================== 123 | %%% Internal functions 124 | %%%=================================================================== 125 | -------------------------------------------------------------------------------- /test/astranaut_rebinding_SUITE_data/rebinding_test.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2019, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 20 Sep 2019 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(rebinding_test). 10 | 11 | -include("rebinding.hrl"). 12 | -include("stacktrace.hrl"). 13 | 14 | -rebinding_all([debug]). 15 | 16 | -rebinding_fun({[test_lc, test_case, test_if], []}). 17 | -rebinding_fun({[test_case_pinned], [clause_pinned]}). 18 | -rebinding_fun({[test_function], [strict]}). 19 | -rebinding_fun({[test_operator, test_tuple, test_list, test_try, test_function_guard], [strict]}). 20 | -rebinding_fun({[test_map, test_map_update], [strict, debug]}). 21 | -rebinding_fun({[test_rec, test_rec_update], [strict, debug]}). 22 | -rebinding_fun({[test_lc_origin, test_function_origin, test_case_origin], no_rebinding}). 23 | -rebinding_fun({[test_operator_origin, test_tuple_origin, test_list_origin], no_rebinding}). 24 | -rebinding_fun({[test_map_origin, test_map_update_origin], no_rebinding}). 25 | -rebinding_fun({[test_rec_origin, test_rec_update_origin], no_rebinding}). 26 | -rebinding_fun({[test_pattern_same_var, test_pattern_same_var_in_fun, test_pattern_same_var_in_case], []}). 27 | 28 | -record(rec, {a, b, c, d}). 29 | 30 | -compile(export_all). 31 | -compile(nowarn_export_all). 32 | %% API 33 | 34 | %%%=================================================================== 35 | %%% API 36 | %%%=================================================================== 37 | 38 | test_lc(A) -> 39 | A = [{A, B} || 40 | B <- [begin A = A + 2, A end], 41 | begin A = A + 3, A, true end, 42 | A <- [begin A = A + 1, A end], 43 | is_ok(A + 3, begin A = A + 4, A end), 44 | true 45 | ], 46 | A. 47 | 48 | test_lc_origin(A) -> 49 | A_1 = [{A_3, B} 50 | || B <- [begin A_1 = A + 2, A_1 end], 51 | begin A_1 = A + 3, A_1, true end, 52 | A_2 <- [begin A_2 = A_1 + 1, A_2 end], 53 | is_ok(A_2 + 3, begin A_3 = A_2 + 4, A_3 end), 54 | true], 55 | A_1. 56 | 57 | test_function(A) -> 58 | B = 10, 59 | B = ok(begin A = A + 1, A end, begin A = A + B, A end), 60 | B = bind(A + B, fun(A) -> A = A + B, A end), 61 | A = B + 1, 62 | A = A + 1, 63 | A. 64 | 65 | test_function_origin(A) -> 66 | B = 10, 67 | B_1 = ok(begin A_1 = A + 1, A_1 end, 68 | begin A_2 = A + B, A_2 end), 69 | B_2 = bind(A_2 + B_1, fun (A_3) -> A_4 = A_3 + B_1, A_4 end), 70 | A_3 = B_2 + 1, 71 | A_4 = A_3 + 1, 72 | A_4. 73 | 74 | test_case(A) -> 75 | B = 15, 76 | {A, B} = {B, A}, 77 | A = A + B, 78 | B = A + B, 79 | B = case A of 80 | 10 -> A = A + 1, A = A + 1, A; 81 | +B -> B = A + 1, A = B + 1, A; 82 | +A -> B = A + B, B 83 | end, 84 | B = A + B, 85 | A = A + B, 86 | A. 87 | 88 | test_case_pinned(A) -> 89 | B = 15, 90 | {A, B} = {B, A}, 91 | A = A + B, 92 | B = A + B, 93 | B = case A of 94 | 10 -> A = A + 1, A = A + 1, A; 95 | B -> B = A + 1, A = B + 1, A; 96 | A -> B = A + B, B 97 | end, 98 | B = A + B, 99 | A = A + B, 100 | A. 101 | 102 | test_case_origin(A) -> 103 | B = 15, 104 | {A_1, B_1} = {B, A}, 105 | A_2 = A_1 + B_1, 106 | B_2 = A_2 + B_1, 107 | B_4 = case A_2 of 108 | 10 -> A_3 = A_2 + 1, A_4 = A_3 + 1, A_4; 109 | B_2 -> B_3 = A_2 + 1, A_3 = B_3 + 1, A_3; 110 | A_2 -> B_3 = A_2 + B_2, B_3 111 | end, 112 | B_5 = A_2 + B_4, 113 | A_5 = A_2 + B_5, 114 | A_5. 115 | 116 | test_if(A) -> 117 | B = 15, 118 | {A, B} = {B, A}, 119 | A = A + B, 120 | B = A + B, 121 | B = if 122 | A =:= 10 -> A = A + 1, A = A + 1, A; 123 | A =:= B -> B = A + 1, A = B + 1, A; 124 | true -> B = A + B, B 125 | end, 126 | B = A + B, 127 | A = A + B, 128 | A. 129 | 130 | test_if_pinned(A) -> 131 | B = 15, 132 | {A, B} = {B, A}, 133 | A = A + B, 134 | B = A + B, 135 | B = if 136 | A =:= 10 -> A = A + 1, A = A + 1, A; 137 | A =:= B -> B = A + 1, A = B + 1, A; 138 | true -> B = A + B, B 139 | end, 140 | B = A + B, 141 | A = A + B, 142 | A. 143 | 144 | test_if_origin(A) -> 145 | B = 15, 146 | {A_1, B_1} = {B, A}, 147 | A_2 = A_1 + B_1, 148 | B_2 = A_2 + B_1, 149 | B_4 = if 150 | A_2 =:= 10 -> A_3 = A_2 + 1, A_4 = A_3 + 1, A_4; 151 | A_2 =:= B_2 -> B_3 = A_2 + 1, A_3 = B_3 + 1, A_3; 152 | true -> B_3 = A_2 + B_2, B_3 153 | end, 154 | B_5 = A_2 + B_4, 155 | A_5 = A_2 + B_5, 156 | A_5. 157 | 158 | test_try() -> 159 | A = 10, 160 | try 161 | A 162 | catch 163 | Class:Exception?CAPTURE_STACKTRACE -> 164 | erlang:raise(Class, Exception, ?GET_STACKTRACE) 165 | end. 166 | 167 | test_operator(A) -> 168 | A = (begin A = A + 1, A end + begin A = A + 1, A end), 169 | A. 170 | 171 | test_tuple(A) -> 172 | {B, C} = {begin A = A + 1, A end, 173 | begin A = A + 2, A end}, 174 | {A, B, C}. 175 | 176 | test_map(A) -> 177 | B = #{b => begin A = A + 1, A end, 178 | c => begin A = A + 2, A end}, 179 | B#{a => A}. 180 | 181 | test_map_update(A) -> 182 | K = #{}, 183 | B = (begin K = K#{d => begin A = A + 3, A end}, K end)#{b => begin A = A + 1, A end, c => begin A = A + 2, A end}, 184 | B#{a => A}. 185 | 186 | test_rec(A) -> 187 | B = #rec{b = begin A = A + 1, A end, 188 | c = begin A = A + 2, A end}, 189 | B#rec{a = A}. 190 | 191 | test_rec_update(A) -> 192 | K = #rec{}, 193 | B = (begin K = K#rec{d = begin A = A + 3, A end}, K end)#rec{b = begin A = A + 1, A end, c = begin A = A + 2, A end}, 194 | B#rec{a = A}. 195 | 196 | test_list(A) -> 197 | B = [begin A = A + 1, A end, 198 | begin A = A + 2, A end], 199 | [A|B]. 200 | 201 | test_operator_origin(A) -> 202 | A_3 = begin A_1 = A + 1, A_1 end + begin A_2 = A + 1, A_2 end, 203 | A_3. 204 | 205 | test_tuple_origin(A) -> 206 | {B, C} = {begin A_1 = A + 1, A_1 end, 207 | begin A_2 = A + 2, A_2 end}, 208 | {A_2, B, C}. 209 | 210 | test_map_origin(A) -> 211 | B = #{b => begin A_1 = A + 1, A_1 end, 212 | c => begin A_2 = A + 2, A_2 end}, 213 | B#{a => A_2}. 214 | 215 | test_map_update_origin(A) -> 216 | K = #{}, 217 | B = begin 218 | K_1 = K#{d => begin A_1 = A + 3, A_1 end}, K_1 219 | end#{b => begin A_2 = A + 1, A_2 end, 220 | c => begin A_3 = A + 2, A_3 end}, 221 | B#{a => A_3}. 222 | 223 | test_rec_origin(A) -> 224 | B = #rec{b = begin A_1 = A + 1, A_1 end, 225 | c = begin A_2 = A + 2, A_2 end}, 226 | B#rec{a = A_2}. 227 | 228 | test_rec_update_origin(A) -> 229 | K = #rec{}, 230 | B = begin 231 | K_1 = K#rec{d = begin A_1 = A + 3, A_1 end}, K_1 232 | end#rec{b = begin A_2 = A + 1, A_2 end, 233 | c = begin A_3 = A + 2, A_3 end}, 234 | B#rec{a = A_3}. 235 | 236 | test_list_origin(A) -> 237 | B = [begin A_1 = A + 1, A_1 end, 238 | begin A_2 = A + 2, A_2 end], 239 | [A_2 | B]. 240 | 241 | test_function_guard(As) when is_list(As) -> 242 | As. 243 | 244 | test_pattern_same_var(A, A) -> 245 | A * 2 + 1; 246 | test_pattern_same_var(A, B) -> 247 | A + B. 248 | 249 | test_pattern_same_var_in_fun(Ax, B) -> 250 | F = fun(Ax, Ax) -> 251 | Ax * 2 + 1; 252 | (A, B) -> 253 | A + B 254 | end, 255 | F(Ax, B). 256 | 257 | test_pattern_same_var_in_case(Ax, B) -> 258 | case {Ax, B} of 259 | {Ax, Ax} -> 260 | Ax * 2 + 1; 261 | {Ax, B} -> 262 | Ax + B 263 | end. 264 | 265 | %%-------------------------------------------------------------------- 266 | %% @doc 267 | %% @spec 268 | %% @end 269 | %%-------------------------------------------------------------------- 270 | 271 | %%%=================================================================== 272 | %%% Internal functions 273 | %%%=================================================================== 274 | is_ok(_A1, _A2) -> 275 | true. 276 | 277 | ok(A_1, A_2) -> 278 | A_1 + A_2. 279 | 280 | bind(A_1, K) -> 281 | K(A_1 + 10). 282 | 283 | %% test_errors_1(A) -> 284 | %% [begin A_1 = A + 1, A_1 end, begin A_2 = A_1 + 1, A_2 end]. 285 | 286 | %% test_errors_2(A) -> 287 | %% [begin A_1 = A + 1, A_1 end|begin A_2 = A_1 + 1, A_2 end]. 288 | 289 | %% test_errors_3(A) -> 290 | %% (begin A_1 = A + 1, A_1 end + begin A_2 = A_1 + 1, A_2 end). 291 | 292 | %% test_errors_4(A) -> 293 | %% {begin A_1 = A + 1, A_1 end, begin A_2 = A_1 + 1, A_2 end}. 294 | 295 | %% test_errors_5(A) -> 296 | %% #{a => begin A_1 = A + 1, A_1 end, b => begin A_2 = A_1 + 1, A_2 end}. 297 | 298 | %% test_errors_5(A) -> 299 | %% X = #{}, 300 | %% (begin X_2 = X#{a => A}, X end)#{a => begin A_1 = A + 1, A_1 end, b => begin A_2 = X, A_2 end}, 301 | %% X_2. 302 | 303 | %% test_errors_5(A) -> 304 | %% begin A_1 = A + 1, A_1 end, 305 | %% begin A_2 = A_1 + 1, A_2 end. 306 | 307 | -------------------------------------------------------------------------------- /test/astranaut_test_lib.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2021, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 20 Feb 2021 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_test_lib). 10 | 11 | -include("compile_opts.hrl"). 12 | -include_lib("eunit/include/eunit.hrl"). 13 | %% API 14 | -export([get_baseline/2, realize_with_baseline/2, test_module_forms/2, compile_test_module/2, compile_test_forms/1, load_data_modules/2]). 15 | 16 | %%%=================================================================== 17 | %%% API 18 | %%%=================================================================== 19 | get_baseline(Mark, Forms) -> 20 | case astranaut_return:run( 21 | astranaut_lib:with_attribute( 22 | fun(Mark1, _Acc, #{pos := Pos}) when Mark =:= Mark1 -> 23 | Pos; 24 | (_Mark2, Acc, #{}) -> 25 | Acc 26 | end, undefined, Forms, baseline, #{formatter => ?MODULE})) of 27 | {just, undefined} -> 28 | Msg = io_lib:format("attribute -baseline(~p) expected", [Mark]), 29 | io:format("~s", [Msg]), 30 | exit(list_to_binary(Msg)); 31 | {just, Baseline} when is_integer(Baseline) -> 32 | Baseline; 33 | {just, {Baseline, _Column}} when is_integer(Baseline) -> 34 | Baseline 35 | end. 36 | 37 | realize_with_baseline(Baseline, ErrorStruct) -> 38 | ErrorStruct1 = 39 | astranaut_error:with_all_formatted_failure( 40 | fun({Pos, Formatter, Error}) when is_integer(Pos) -> 41 | {Pos - Baseline, Formatter, Error}; 42 | ({{Line, _Column}, Formatter, Error}) when is_integer(Line) -> 43 | {Line - Baseline, Formatter, Error}; 44 | (Error) -> 45 | Error 46 | end, ErrorStruct), 47 | astranaut_error:realize(ErrorStruct1). 48 | 49 | compile_test_module(Module, Config) -> 50 | DataDir = proplists:get_value(data_dir, Config), 51 | Forms = test_module_forms(Module, DataDir, Config), 52 | Opts = compile_opts(), 53 | Outfile = filename:join(filename:dirname(filename:absname(DataDir)), atom_to_list(Module) ++ ".beam"), 54 | file:delete(Outfile), 55 | astranaut_return:bind( 56 | astranaut_lib:load_forms(Forms, Opts), 57 | fun({Mod, Binary}) -> 58 | %% write beam file to make edts works. 59 | ok = file:write_file(Outfile, Binary, []), 60 | astranaut_return:return(Mod) 61 | end). 62 | 63 | compile_test_forms(Forms) -> 64 | Opts = compile_opts(), 65 | Opts1 = Opts -- [report_warnings, report_errors] ++ [return_warnings], 66 | astranaut_lib:load_forms(Forms, Opts1). 67 | 68 | test_module_forms(Module, Config) -> 69 | DataDir = proplists:get_value(data_dir, Config), 70 | test_module_forms(Module, DataDir, Config). 71 | 72 | test_module_forms(Module, DataDir, _Config) -> 73 | File = filename:join(DataDir, atom_to_list(Module) ++ ".erl"), 74 | Opts = compile_opts(), 75 | case filelib:is_file(File) of 76 | true -> 77 | case astranaut_lib:parse_file(File, Opts) of 78 | {error, Errors, []} -> 79 | exit({compile_module_failed, Errors}); 80 | Forms -> 81 | Forms 82 | end; 83 | false -> 84 | exit({file_not_detected, File}) 85 | end. 86 | 87 | load_data_modules(Config, TestModules) -> 88 | lists:foreach( 89 | fun(TestModule) -> 90 | Return = compile_test_module(TestModule, Config), 91 | astranaut_return:with_error( 92 | fun(Error) -> 93 | ?assertEqual(#{}, maps:without([warnings, formatted_warnings, file_warnings], 94 | astranaut_error:printable(Error))), 95 | Error 96 | end, Return) 97 | end, TestModules), 98 | Config. 99 | %%-------------------------------------------------------------------- 100 | %% @doc 101 | %% @spec 102 | %% @end 103 | %%-------------------------------------------------------------------- 104 | 105 | %%%=================================================================== 106 | %%% Internal functions 107 | %%%=================================================================== 108 | -------------------------------------------------------------------------------- /test/astranaut_traverse_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2020, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 6 Jul 2020 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_traverse_SUITE). 10 | 11 | -compile(export_all). 12 | -compile(nowarn_export_all). 13 | 14 | -include("do.hrl"). 15 | -include_lib("eunit/include/eunit.hrl"). 16 | -include_lib("common_test/include/ct.hrl"). 17 | 18 | %%-------------------------------------------------------------------- 19 | %% @spec suite() -> Info 20 | %% Info = [tuple()] 21 | %% @end 22 | %%-------------------------------------------------------------------- 23 | suite() -> 24 | [{timetrap,{seconds,30}}]. 25 | 26 | %%-------------------------------------------------------------------- 27 | %% @spec init_per_suite(Config0) -> 28 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 29 | %% Config0 = Config1 = [tuple()] 30 | %% Reason = term() 31 | %% @end 32 | %%-------------------------------------------------------------------- 33 | init_per_suite(Config) -> 34 | Config. 35 | 36 | %%-------------------------------------------------------------------- 37 | %% @spec end_per_suite(Config0) -> term() | {save_config,Config1} 38 | %% Config0 = Config1 = [tuple()] 39 | %% @end 40 | %%-------------------------------------------------------------------- 41 | end_per_suite(_Config) -> 42 | ok. 43 | 44 | %%-------------------------------------------------------------------- 45 | %% @spec init_per_group(GroupName, Config0) -> 46 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 47 | %% GroupName = atom() 48 | %% Config0 = Config1 = [tuple()] 49 | %% Reason = term() 50 | %% @end 51 | %%-------------------------------------------------------------------- 52 | init_per_group(_GroupName, Config) -> 53 | Config. 54 | 55 | %%-------------------------------------------------------------------- 56 | %% @spec end_per_group(GroupName, Config0) -> 57 | %% term() | {save_config,Config1} 58 | %% GroupName = atom() 59 | %% Config0 = Config1 = [tuple()] 60 | %% @end 61 | %%-------------------------------------------------------------------- 62 | end_per_group(_GroupName, _Config) -> 63 | ok. 64 | 65 | %%-------------------------------------------------------------------- 66 | %% @spec init_per_testcase(TestCase, Config0) -> 67 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 68 | %% TestCase = atom() 69 | %% Config0 = Config1 = [tuple()] 70 | %% Reason = term() 71 | %% @end 72 | %%-------------------------------------------------------------------- 73 | init_per_testcase(_TestCase, Config) -> 74 | Config. 75 | 76 | %%-------------------------------------------------------------------- 77 | %% @spec end_per_testcase(TestCase, Config0) -> 78 | %% term() | {save_config,Config1} | {fail,Reason} 79 | %% TestCase = atom() 80 | %% Config0 = Config1 = [tuple()] 81 | %% Reason = term() 82 | %% @end 83 | %%-------------------------------------------------------------------- 84 | end_per_testcase(_TestCase, _Config) -> 85 | ok. 86 | 87 | %%-------------------------------------------------------------------- 88 | %% @spec groups() -> [Group] 89 | %% Group = {GroupName,Properties,GroupsAndTestCases} 90 | %% GroupName = atom() 91 | %% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] 92 | %% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] 93 | %% TestCase = atom() 94 | %% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} 95 | %% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | 96 | %% repeat_until_any_ok | repeat_until_any_fail 97 | %% N = integer() | forever 98 | %% @end 99 | %%-------------------------------------------------------------------- 100 | groups() -> 101 | []. 102 | 103 | %%-------------------------------------------------------------------- 104 | %% @spec all() -> GroupsAndTestCases | {skip,Reason} 105 | %% GroupsAndTestCases = [{group,GroupName} | TestCase] 106 | %% GroupName = atom() 107 | %% TestCase = atom() 108 | %% Reason = term() 109 | %% @end 110 | %%-------------------------------------------------------------------- 111 | all() -> 112 | [test_return, test_bind, test_error_0, test_state, 113 | test_pos, test_pos_2, test_file_pos, test_fail]. 114 | 115 | %%-------------------------------------------------------------------- 116 | %% @spec TestCase() -> Info 117 | %% Info = [tuple()] 118 | %% @end 119 | %%-------------------------------------------------------------------- 120 | test_return() -> 121 | []. 122 | 123 | %%-------------------------------------------------------------------- 124 | %% @spec TestCase(Config0) -> 125 | %% ok | exit() | {skip,Reason} | {comment,Comment} | 126 | %% {save_config,Config1} | {skip_and_save,Reason,Config1} 127 | %% Config0 = Config1 = [tuple()] 128 | %% Reason = term() 129 | %% Comment = term() 130 | %% @end 131 | %%-------------------------------------------------------------------- 132 | test_return(_Config) -> 133 | MA = astranaut_traverse:return(10), 134 | Result = astranaut_return:ok({10, ok}), 135 | ?assertEqual(Result, astranaut_traverse:run(MA, undefined, #{}, ok)), 136 | ok. 137 | 138 | test_bind(_Config) -> 139 | MA = 140 | do([ traverse || 141 | A <- astranaut_traverse:return(10), 142 | return(A + 10) 143 | ]), 144 | Result = astranaut_return:ok({20, ok}), 145 | ?assertEqual(Result, astranaut_traverse:run(MA, undefined, #{}, ok)), 146 | ok. 147 | 148 | test_error_0(_Config) -> 149 | MA = 150 | do([ traverse || 151 | A <- astranaut_traverse:return(10), 152 | astranaut_traverse:update_pos( 153 | 10, astranaut_traverse:error(error_0)), 154 | return(A + 10) 155 | ]), 156 | ErrorState = astranaut_error:new(), 157 | ErrorState1 = astranaut_error:append_formatted_errors([{10, formatter_0, error_0}], ErrorState), 158 | ErrorState2 = astranaut_error:printable(ErrorState1), 159 | ErrorStateM0 = astranaut_return:run_error(astranaut_traverse:run(MA, formatter_0, #{}, ok)), 160 | ErrorStateM1 = astranaut_error:printable(ErrorStateM0), 161 | ?assertEqual(ErrorState2, ErrorStateM1), 162 | ok. 163 | 164 | test_state(_Config) -> 165 | MA = 166 | do([ traverse || 167 | astranaut_traverse:put(10), 168 | astranaut_traverse:state( 169 | fun(A) -> 170 | {A + 10, A + 20} 171 | end) 172 | ]), 173 | Result = astranaut_return:ok({20, 30}), 174 | ?assertEqual(Result, astranaut_traverse:run(MA, undefined, #{}, ok)), 175 | ok. 176 | 177 | test_pos(_Config) -> 178 | MA = 179 | do([ traverse || 180 | astranaut_traverse:put(10), 181 | astranaut_traverse:update_pos( 182 | 20, astranaut_traverse:error(error_0)), 183 | astranaut_traverse:state( 184 | fun(A) -> 185 | {A + 10, A + 20} 186 | end) 187 | ]), 188 | Errors = [{20, formatter_0, error_0}], 189 | #{return := Return, error := Error} = astranaut_traverse:run(MA, formatter_0, #{}, ok), 190 | ?assertEqual({{20, 30}, Errors}, {Return, astranaut_error:formatted_errors(Error)}), 191 | ok. 192 | 193 | 194 | test_pos_2(_Config) -> 195 | MA = 196 | do([ traverse || 197 | astranaut_traverse:update_pos( 198 | 20, astranaut_traverse:error(error_0)), 199 | return(10) 200 | ]), 201 | Errors = [{20, formatter_0, error_0}], 202 | #{return := Return, error := Error} = astranaut_traverse:run(MA, formatter_0, #{}, ok), 203 | ?assertEqual({{10, ok}, Errors}, {Return, astranaut_error:formatted_errors(Error)}), 204 | ok. 205 | 206 | test_file_pos(_Config) -> 207 | MA = 208 | do([ traverse || 209 | astranaut_traverse:update_file(?FILE), 210 | astranaut_traverse:put(10), 211 | astranaut_traverse:with_formatter( 212 | astranaut_traverse, 213 | astranaut_traverse:update_pos( 214 | 20, 215 | astranaut_traverse:error(error_0) 216 | )), 217 | astranaut_traverse:update_pos( 218 | 25, astranaut_traverse:warning(warning_0)), 219 | B <- astranaut_traverse:get(), 220 | astranaut_traverse:modify( 221 | fun(A) -> 222 | A + 20 223 | end), 224 | astranaut_traverse:eof(), 225 | return(B + 10) 226 | ]), 227 | FileErrors = [{?FILE, [{20, astranaut_traverse, error_0}]}], 228 | FileWarnings = [{?FILE, [{25, ?MODULE, warning_0}]}], 229 | #{return := Result, error := Error} = astranaut_traverse:run(MA, ?MODULE, #{}, ok), 230 | ?assertEqual({{20, 30}, {FileErrors, FileWarnings}}, {Result, astranaut_error:realize(Error)}), 231 | ok. 232 | 233 | test_fail(_Config) -> 234 | MA = 235 | do([ traverse || 236 | astranaut_traverse:put(10), 237 | astranaut_traverse:with_formatter( 238 | astranaut_traverse, 239 | astranaut_traverse:update_pos( 240 | 20, 241 | astranaut_traverse:error(error_0)) 242 | ), 243 | astranaut_traverse:update_pos( 244 | 25, astranaut_traverse:warning(warning_0)), 245 | B <- astranaut_traverse:get(), 246 | astranaut_traverse:modify( 247 | fun(A) -> 248 | A + 20 249 | end), 250 | return(B) 251 | ]), 252 | MB = do([ traverse || 253 | astranaut_traverse:fail_on_error(MA), 254 | astranaut_traverse:put(30), 255 | astranaut_traverse:update_pos( 256 | 30, astranaut_traverse:error(error_1)) 257 | ]), 258 | Errors = [{20, astranaut_traverse, error_0}], 259 | Warnings = [{25, ?MODULE, warning_0}], 260 | #{error := Error} = astranaut_traverse:run(MB, ?MODULE, #{}, ok), 261 | ?assertEqual({Errors, Warnings}, {astranaut_error:formatted_errors(Error), 262 | astranaut_error:formatted_warnings(Error)}), 263 | ok. 264 | 265 | 266 | %% test_bind_node(_Config) -> 267 | %% NodeA = {atom, 10, 'A'}, 268 | %% Walk = astranaut_walk_return:new(#{}), 269 | %% Return = 270 | %% bind_pre(NodeA, Walk, 271 | %% fun({atom, _Pos, A}) -> 272 | %% NodeC = {atom, 20, A}, 273 | %% astranaut_walk_return:new(#{node => NodeC}) 274 | %% end), 275 | %% ?assertEqual({atom, 20, 'A'}, Return), 276 | %% ok. 277 | 278 | %% test_bind_node_continue(_Config) -> 279 | %% NodeA = {atom, 10, 'A'}, 280 | %% Walk = astranaut_walk_return:new(#{continue => true, node => {atom, 10, 'B'}}), 281 | %% Return = 282 | %% bind_pre(NodeA, Walk, 283 | %% fun({atom, _Pos, A}) -> 284 | %% NodeC = {atom, 20, A}, 285 | %% astranaut_walk_return:new(#{node => NodeC}) 286 | %% end), 287 | %% ?assertEqual({atom, 10, 'B'}, Return), 288 | %% ok. 289 | 290 | %% test_bind_node_update(_Config) -> 291 | %% NodeA = {atom, 10, 'A'}, 292 | %% Walk = astranaut_walk_return:new(#{node => {atom, 10, 'B'}}), 293 | %% Return = 294 | %% bind_pre(NodeA, Walk, 295 | %% fun({atom, _Pos, A}) -> 296 | %% NodeC = {atom, 20, A}, 297 | %% astranaut_walk_return:new(#{node => NodeC}) 298 | %% end), 299 | %% ?assertEqual({atom, 20, 'B'}, Return), 300 | %% ok. 301 | -------------------------------------------------------------------------------- /test/astranaut_uniplate_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2021, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 9 Apr 2021 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(astranaut_uniplate_SUITE). 10 | 11 | -compile(export_all). 12 | -compile(nowarn_export_all). 13 | 14 | -include_lib("eunit/include/eunit.hrl"). 15 | -include_lib("common_test/include/ct.hrl"). 16 | 17 | 18 | -record(uniplate_node_context, {node, 19 | withs = [], 20 | reduces = [], 21 | skip = false, 22 | up_attrs = [], 23 | entries = [], 24 | exits = [] 25 | }). 26 | 27 | %%-------------------------------------------------------------------- 28 | %% @spec suite() -> Info 29 | %% Info = [tuple()] 30 | %% @end 31 | %%-------------------------------------------------------------------- 32 | suite() -> 33 | [{timetrap,{seconds,30}}]. 34 | 35 | %%-------------------------------------------------------------------- 36 | %% @spec init_per_suite(Config0) -> 37 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 38 | %% Config0 = Config1 = [tuple()] 39 | %% Reason = term() 40 | %% @end 41 | %%-------------------------------------------------------------------- 42 | init_per_suite(Config) -> 43 | erlang:system_flag(backtrace_depth, 20), 44 | Forms = astranaut_test_lib:test_module_forms(sample_plus, Config), 45 | [{forms, Forms}|Config]. 46 | 47 | %%-------------------------------------------------------------------- 48 | %% @spec end_per_suite(Config0) -> term() | {save_config,Config1} 49 | %% Config0 = Config1 = [tuple()] 50 | %% @end 51 | %%-------------------------------------------------------------------- 52 | end_per_suite(_Config) -> 53 | ok. 54 | 55 | %%-------------------------------------------------------------------- 56 | %% @spec init_per_group(GroupName, Config0) -> 57 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 58 | %% GroupName = atom() 59 | %% Config0 = Config1 = [tuple()] 60 | %% Reason = term() 61 | %% @end 62 | %%-------------------------------------------------------------------- 63 | init_per_group(_GroupName, Config) -> 64 | Config. 65 | 66 | %%-------------------------------------------------------------------- 67 | %% @spec end_per_group(GroupName, Config0) -> 68 | %% term() | {save_config,Config1} 69 | %% GroupName = atom() 70 | %% Config0 = Config1 = [tuple()] 71 | %% @end 72 | %%-------------------------------------------------------------------- 73 | end_per_group(_GroupName, _Config) -> 74 | ok. 75 | 76 | %%-------------------------------------------------------------------- 77 | %% @spec init_per_testcase(TestCase, Config0) -> 78 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 79 | %% TestCase = atom() 80 | %% Config0 = Config1 = [tuple()] 81 | %% Reason = term() 82 | %% @end 83 | %%-------------------------------------------------------------------- 84 | init_per_testcase(_TestCase, Config) -> 85 | Config. 86 | 87 | %%-------------------------------------------------------------------- 88 | %% @spec end_per_testcase(TestCase, Config0) -> 89 | %% term() | {save_config,Config1} | {fail,Reason} 90 | %% TestCase = atom() 91 | %% Config0 = Config1 = [tuple()] 92 | %% Reason = term() 93 | %% @end 94 | %%-------------------------------------------------------------------- 95 | end_per_testcase(_TestCase, _Config) -> 96 | ok. 97 | 98 | %%-------------------------------------------------------------------- 99 | %% @spec groups() -> [Group] 100 | %% Group = {GroupName,Properties,GroupsAndTestCases} 101 | %% GroupName = atom() 102 | %% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] 103 | %% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] 104 | %% TestCase = atom() 105 | %% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} 106 | %% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | 107 | %% repeat_until_any_ok | repeat_until_any_fail 108 | %% N = integer() | forever 109 | %% @end 110 | %%-------------------------------------------------------------------- 111 | groups() -> 112 | []. 113 | 114 | %%-------------------------------------------------------------------- 115 | %% @spec all() -> GroupsAndTestCases | {skip,Reason} 116 | %% GroupsAndTestCases = [{group,GroupName} | TestCase] 117 | %% GroupName = atom() 118 | %% TestCase = atom() 119 | %% Reason = term() 120 | %% @end 121 | %%-------------------------------------------------------------------- 122 | all() -> 123 | [test_writer_or, test_map, test_map_attr, 124 | test_reduce, test_reduce_attr, test_reduce_traverse_all, 125 | test_mapfold_attr, test_f_return_list, test_all_return_list, 126 | test_with_subtrees, test_af_with, 127 | test_invalid_pre_transform_exception, test_invalid_post_transform_exception, 128 | test_invalid_post_transform_context_exception, test_invalid_transform_maketree_exception, 129 | test_invalid_node_exception, test_invalid_uniplate_subnode_exception, 130 | test_invalid_transform_exception, test_invalid_subtree_transform_exception 131 | ]. 132 | 133 | %%-------------------------------------------------------------------- 134 | %% @spec TestCase() -> Info 135 | %% Info = [tuple()] 136 | %% @end 137 | %%-------------------------------------------------------------------- 138 | my_test_case() -> 139 | []. 140 | 141 | %%-------------------------------------------------------------------- 142 | %% @spec TestCase(Config0) -> 143 | %% ok | exit() | {skip,Reason} | {comment,Comment} | 144 | %% {save_config,Config1} | {skip_and_save,Reason,Config1} 145 | %% Config0 = Config1 = [tuple()] 146 | %% Reason = term() 147 | %% Comment = term() 148 | %% @end 149 | %%-------------------------------------------------------------------- 150 | test_writer_or(_Config) -> 151 | Monad = identity, 152 | Mempty = astranaut_monad:mempty('or'), 153 | Mappend = astranaut_monad:mappend('or'), 154 | Bind = astranaut_monad:monad_bind(Monad), 155 | Return = astranaut_monad:monad_return(Monad), 156 | BindW = astranaut_monad:writer_bind(Bind, Return, Mappend), 157 | ReturnW = astranaut_monad:writer_return(Return, Mempty), 158 | Writer = astranaut_monad:writer_writer(Return), 159 | WA = 160 | BindW( 161 | ReturnW(1), 162 | fun(A) -> 163 | Writer({A + 1, true}) 164 | end), 165 | ?assertEqual({2, true}, WA), 166 | Monad1 = reader, 167 | Bind1 = astranaut_monad:monad_bind(Monad1), 168 | Return1 = astranaut_monad:monad_return(Monad1), 169 | BindW1 = astranaut_monad:writer_bind(Bind1, Return1, Mappend), 170 | ReturnW1 = astranaut_monad:writer_return(Return1, Mempty), 171 | Writer1 = astranaut_monad:writer_writer(Return1), 172 | Lift = astranaut_monad:writer_lift(Bind1, Return1, Mempty), 173 | Ask1 = astranaut_monad:writer_ask(Lift, astranaut_monad:monad_ask(Monad1)), 174 | WA1 = 175 | BindW1( 176 | ReturnW1(1), 177 | fun(A) -> 178 | BindW1( 179 | Ask1(), 180 | fun(B) -> 181 | Writer1({A + B, true}) 182 | end) 183 | end), 184 | ?assertEqual({4, true}, WA1(3)), 185 | ok. 186 | 187 | test_map(_Config) -> 188 | Ast = merl:quote("A + (B + C)"), 189 | Ast1 = 190 | astranaut:smap( 191 | fun({var, _Pos, Varname}) -> 192 | {var, _Pos, list_to_atom(atom_to_list(Varname) ++ "_1")}; 193 | (Node) -> 194 | Node 195 | end, Ast, #{traverse => pre}), 196 | Ast2 = merl:quote("A_1 + (B_1 + C_1)"), 197 | ?assertEqual(Ast2, Ast1), 198 | ok. 199 | 200 | test_map_attr(_Config) -> 201 | Ast = merl:quote("E = A + (D = B + C)"), 202 | Ast1 = 203 | astranaut:smap( 204 | fun({var, _Pos, Varname}, #{pattern := true}) -> 205 | {var, _Pos, list_to_atom(atom_to_list(Varname) ++ "_1")}; 206 | (Node, #{}) -> 207 | case erl_syntax:type(Node) of 208 | match_expr -> 209 | astranaut_uniplate:with_subtrees( 210 | fun([MatchLeft, MatchRight]) -> 211 | [MatchRight, astranaut_uniplate:up_attr(#{pattern => true}, MatchLeft)] 212 | end, fun lists:reverse/1, Node); 213 | _Type -> 214 | Node 215 | end 216 | end, Ast, #{traverse => pre}), 217 | Ast2 = merl:quote("E_1 = A + (D_1 = B + C)"), 218 | ?assertEqual(Ast2, Ast1), 219 | ok. 220 | 221 | test_reduce(_Config) -> 222 | Ast = merl:quote("E = A + (D = B + C)"), 223 | Acc1 = 224 | astranaut:sreduce( 225 | fun({var, _Pos, Varname}, Acc) -> 226 | [Varname|Acc]; 227 | (_Node, Acc) -> 228 | Acc 229 | end, [], Ast, #{traverse => pre}), 230 | ?assertEqual(['E', 'A', 'D', 'B', 'C'], lists:reverse(Acc1)), 231 | ok. 232 | 233 | test_reduce_attr(_Config) -> 234 | Ast = merl:quote("E = A + (D = B + C)"), 235 | Acc1 = 236 | astranaut:sreduce( 237 | fun(#{node := {var, _Pos, Varname}, pattern := true}, Acc) -> 238 | [Varname|Acc]; 239 | (_Node, Acc) -> 240 | Acc 241 | end, [], Ast, #{traverse => pre, uniplate => fun uniplate_node_attr/1}), 242 | ?assertEqual(['E', 'D'], lists:reverse(Acc1)), 243 | ok. 244 | 245 | uniplate_node_attr(#{node := Node}) -> 246 | uniplate_node_attr(Node); 247 | uniplate_node_attr(Node) -> 248 | case erl_syntax:type(Node) of 249 | match_expr -> 250 | {[MatchLefts, MatchRights], Context} = astranaut:uniplate(Node), 251 | MatchLefts1 = 252 | lists:map( 253 | fun(MatchLeft) -> 254 | #{node => MatchLeft, pattern => true} 255 | end, MatchLefts), 256 | {[MatchLefts1, MatchRights], Context}; 257 | _ -> 258 | astranaut:uniplate(Node) 259 | end. 260 | 261 | test_reduce_traverse_all(_Config) -> 262 | Ast = merl:quote("E = A + (D = B + C)"), 263 | Acc1 = 264 | astranaut:sreduce( 265 | fun(Node, Acc, #{step := Step}) -> 266 | case erl_syntax:type(Node) of 267 | match_expr -> 268 | {var, _Pos, Var} = erl_syntax:match_expr_pattern(Node), 269 | [{Step, Var}|Acc]; 270 | _ -> 271 | Acc 272 | end 273 | end, [], Ast, #{traverse => all}), 274 | ?assertEqual([{pre, 'E'}, {pre, 'D'}, {post, 'D'}, {post, 'E'}], lists:reverse(Acc1)), 275 | ok. 276 | 277 | test_mapfold(_Config) -> 278 | Ast = merl:quote("E = A + (D = B + C)"), 279 | {Ast1, Acc1} = 280 | astranaut:smapfold( 281 | fun({var, Pos, Varname}, Acc) -> 282 | {{var, Pos, list_to_atom(atom_to_list(Varname) ++ "_1")}, Acc}; 283 | (Node, Acc) -> 284 | case erl_syntax:type(Node) of 285 | match_expr -> 286 | {var, _Pos, Var} = erl_syntax:match_expr_pattern(Node), 287 | {Node, [Var|Acc]}; 288 | _ -> 289 | {Node, Acc} 290 | end 291 | end, [], Ast, #{traverse => post}), 292 | Ast2 = merl:quote("E_1 = A_1 + (D_1 = B_1 + C_1)"), 293 | ?assertEqual(Ast2, Ast1), 294 | ?assertEqual(['D_1', 'E_1'], lists:reverse(Acc1)), 295 | ok. 296 | 297 | test_mapfold_attr(_Config) -> 298 | Ast = merl:quote("E = A + (D = B + C)"), 299 | {Ast1, Acc1} = 300 | astranaut:smapfold( 301 | fun({var, Pos, Varname}, Acc, #{step := pre}) -> 302 | {{var, Pos, list_to_atom(atom_to_list(Varname) ++ "_1")}, Acc}; 303 | (Node, Acc, #{step := Step}) -> 304 | case erl_syntax:type(Node) of 305 | match_expr -> 306 | {var, _Pos, Var} = erl_syntax:match_expr_pattern(Node), 307 | {Node, [{Step, Var}|Acc]}; 308 | _ -> 309 | {Node, Acc} 310 | end; 311 | (Node, Acc, #{})-> 312 | {Node, Acc} 313 | end, [], Ast, #{traverse => all}), 314 | Ast2 = merl:quote("E_1 = A_1 + (D_1 = B_1 + C_1)"), 315 | ?assertEqual(Ast2, Ast1), 316 | ?assertEqual([{pre, 'E'}, {pre, 'D'}, {post, 'D_1'}, {post, 'E_1'}], lists:reverse(Acc1)), 317 | ok. 318 | 319 | test_f_return_list(_Config) -> 320 | Ast = merl:quote("hello(A, B, world(C))"), 321 | Ast1 = 322 | astranaut:smap( 323 | fun({var, Pos, Varname}) -> 324 | [{var, Pos, list_to_atom(atom_to_list(Varname) ++ "_1")}, 325 | {var, Pos, list_to_atom(atom_to_list(Varname) ++ "_2")}]; 326 | (Node) -> 327 | Node 328 | end, Ast, #{traverse => post}), 329 | Ast2 = merl:quote("hello(A_1, A_2, B_1, B_2, world(C_1, C_2))"), 330 | ?assertEqual(Ast2, Ast1), 331 | ok. 332 | 333 | test_all_return_list(_Config) -> 334 | Ast = merl:quote("hello(A, B, world(C))"), 335 | Ast1 = 336 | astranaut:smap( 337 | fun({var, Pos, Varname}) -> 338 | [{var, Pos, list_to_atom(atom_to_list(Varname) ++ "_1")}, 339 | {var, Pos, list_to_atom(atom_to_list(Varname) ++ "_2")}]; 340 | (Node) -> 341 | Node 342 | end, Ast, #{traverse => all}), 343 | Ast2 = merl:quote("hello(A_1_1, A_1_2, A_2_1, A_2_2, B_1_1, B_1_2, B_2_1, B_2_2, world(C_1_1, C_1_2, C_2_1, C_2_2))"), 344 | ?assertEqual(Ast2, Ast1), 345 | ok. 346 | 347 | test_with_subtrees(_Config) -> 348 | TopNode = merl:quote("case A of 10 -> B = A + 1, B; C -> D = C + 2, B end"), 349 | F = 350 | fun(match_expr, Node, Variables, _Attr) -> 351 | {astranaut_uniplate:with_subtrees( 352 | fun([Patterns, Expressions]) -> 353 | [astranaut_uniplate:up_attr(#{match_pattern => false}, Expressions), 354 | astranaut_uniplate:with( 355 | fun(Variables1) -> 356 | [before_pattern|Variables1] 357 | end, 358 | fun(Variables1) -> 359 | [after_pattern|Variables1] 360 | end, 361 | astranaut_uniplate:up_attr(#{match_pattern => true}, Patterns))] 362 | end, fun lists:reverse/1, Node), Variables}; 363 | (variable, {var, _Pos, VarName} = Var, Variables, #{match_pattern := true}) -> 364 | {Var, [{pattern, VarName}|Variables]}; 365 | (variable, {var, _Pos, VarName} = Var, Variables, #{match_pattern := false}) -> 366 | {Var, [{expression, VarName}|Variables]}; 367 | (_Type, Node, Variables, #{}) -> 368 | {Node, Variables} 369 | end, 370 | {TopNode1, State1} = 371 | astranaut:smapfold( 372 | fun(Node, Acc, Attr) -> 373 | Type = erl_syntax:type(Node), 374 | F(Type, Node, Acc, Attr) 375 | end, [], TopNode, #{traverse => pre}), 376 | ?assertEqual([after_pattern, {pattern, 'D'}, before_pattern, {expression, 'C'}, after_pattern, {pattern, 'B'}, before_pattern, {expression, 'A'}], State1), 377 | ?assertEqual(TopNode, TopNode1), 378 | ok. 379 | 380 | test_af_with(_Config) -> 381 | Datas1 = [[], [a, b], [c, d], []], 382 | Datas2 = astranaut_uniplate:with(g, h, Datas1), 383 | ?assertEqual([[], [#uniplate_node_context{node = a, entries = [g]}, b], 384 | [c, #uniplate_node_context{node = d, exits = [h]}], []], Datas2), 385 | Datas3 = [[a, b], [c, d], []], 386 | Datas4 = astranaut_uniplate:with(g, h, Datas3), 387 | 388 | ?assertEqual([[#uniplate_node_context{node = a, entries = [g]}, b], 389 | [c, #uniplate_node_context{node = d, exits = [h]}], []], Datas4), 390 | Datas5 = astranaut_uniplate:up_attr(#{name => data}, [astranaut_uniplate:skip([a, b]), [c, d], []]), 391 | Datas6 = astranaut_uniplate:with(g, h, Datas5), 392 | ?assertEqual([[#uniplate_node_context{node = a, entries = [g], skip = true}, 393 | #uniplate_node_context{node = b, skip = true}], 394 | [#uniplate_node_context{node = c, up_attrs = [#{name => data}]}, 395 | #uniplate_node_context{node = d, up_attrs = [#{name => data}], exits = [h]}], []], Datas6), 396 | ok. 397 | 398 | test_invalid_pre_transform_exception(Config) -> 399 | Forms = proplists:get_value(forms, Config), 400 | ?assertException( 401 | error, 402 | {invalid_pre_transform, {var, _, _}, invalid_node, _OriginalException}, 403 | astranaut:smap( 404 | fun({var, _Pos, _Value}) -> 405 | invalid_node; 406 | (Node) -> 407 | Node 408 | end, Forms, #{})), 409 | ok. 410 | 411 | test_invalid_post_transform_exception(Config) -> 412 | Forms = proplists:get_value(forms, Config), 413 | ?assertException( 414 | error, 415 | {invalid_post_transform, {var, _, _}, invalid_node, _OriginalException}, 416 | astranaut:smap( 417 | fun({var, _Pos, _Value}) -> 418 | invalid_node; 419 | (Node) -> 420 | Node 421 | end, Forms, #{traverse => post})), 422 | ok. 423 | 424 | test_invalid_post_transform_context_exception(Config) -> 425 | Forms = proplists:get_value(forms, Config), 426 | ?assertException( 427 | error, 428 | {invalid_post_transform_with_context, {var, _, _}, _}, 429 | astranaut:smap( 430 | fun({var, _Pos, _Value} = Atom) -> 431 | astranaut_uniplate:skip(Atom); 432 | (Node) -> 433 | Node 434 | end, Forms, #{traverse => post})), 435 | ok. 436 | 437 | test_invalid_transform_maketree_exception(Config) -> 438 | Forms = proplists:get_value(forms, Config), 439 | ?assertException( 440 | error, 441 | {invalid_transform_maketree, {op, _, _, _, _}, _, _, _}, 442 | astranaut:smap( 443 | fun({var, _Pos, _Value}) -> 444 | []; 445 | (Node) -> 446 | Node 447 | end, Forms, #{traverse => post})), 448 | ok. 449 | 450 | test_invalid_node_exception(_Config) -> 451 | ?assertException( 452 | error, 453 | {invalid_node, undefined, _}, 454 | astranaut:smap( 455 | fun({var, _Pos, _Value}) -> 456 | []; 457 | (Node) -> 458 | astranaut_uniplate:skip(Node) 459 | end, undefined, #{traverse => post})), 460 | ok. 461 | 462 | test_invalid_uniplate_subnode_exception(Config) -> 463 | Forms = proplists:get_value(forms, Config), 464 | ?assertException( 465 | error, 466 | {invalid_uniplate_subnode, {var, _Pos, _Value}, invalid_subnode_a, _}, 467 | astranaut:smap( 468 | fun(Node) -> 469 | Node 470 | end, Forms, #{traverse => pre, uniplate => fun invalid_uniplate/1})), 471 | ok. 472 | 473 | test_invalid_transform_exception(Config) -> 474 | Forms = proplists:get_value(forms, Config), 475 | ?assertException( 476 | error, 477 | {invalid_transform, {function, _Pos, _Name, _Arity, _Clauses}, invalid_node, _OriginalException}, 478 | astranaut:smap( 479 | fun({function, _Pos, _Name, _Arity, _Clauses}) -> 480 | invalid_node; 481 | (Node) -> 482 | Node 483 | end, Forms, #{traverse => none})), 484 | ok. 485 | 486 | test_invalid_subtree_transform_exception(Config) -> 487 | Forms = proplists:get_value(forms, Config), 488 | ?assertException( 489 | error, 490 | {invalid_subtree_transform, {clause, _Pos, _Pattern, _Guard, _Expression}, invalid_clause, _OriginalException}, 491 | astranaut:smap( 492 | fun({clause, _Pos, _Pattern, _Guard, _Expression}) -> 493 | invalid_clause; 494 | (Node) -> 495 | Node 496 | end, Forms, #{traverse => subtree})), 497 | ok. 498 | 499 | uniplate(Node) -> 500 | Subtrees = erl_syntax:subtrees(Node), 501 | MakeTree = fun(Subtrees1) -> erl_syntax:make_tree(Node, Subtrees1) end, 502 | {Subtrees, MakeTree}. 503 | 504 | invalid_uniplate({var, _Pos, _Varname} = Node) -> 505 | Subtrees = [[invalid_subnode_a]], 506 | MakeTree = fun(_) -> Node end, 507 | {Subtrees, MakeTree}; 508 | invalid_uniplate(Node) -> 509 | uniplate(Node). 510 | -------------------------------------------------------------------------------- /test/astranaut_uniplate_SUITE_data/sample_plus.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2020, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 9 Jul 2020 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(sample_plus). 10 | 11 | %% API 12 | -export([plus/2]). 13 | 14 | plus(A, B) -> 15 | A + B. 16 | 17 | %%-------------------------------------------------------------------- 18 | %% @doc 19 | %% @spec 20 | %% @end 21 | %%-------------------------------------------------------------------- 22 | 23 | %%%=================================================================== 24 | %%% Internal functions 25 | %%%=================================================================== 26 | -------------------------------------------------------------------------------- /test/disable_tco_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Chen Slepher 3 | %%% @copyright (C) 2018, Chen Slepher 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 10 Dec 2018 by Chen Slepher 8 | %%%------------------------------------------------------------------- 9 | -module(disable_tco_SUITE). 10 | 11 | -compile(export_all). 12 | -include_lib("eunit/include/eunit.hrl"). 13 | -include_lib("common_test/include/ct.hrl"). 14 | -include("stacktrace.hrl"). 15 | %%-------------------------------------------------------------------- 16 | %% @spec suite() -> Info 17 | %% Info = [tuple()] 18 | %% @end 19 | %%-------------------------------------------------------------------- 20 | suite() -> 21 | [{timetrap,{seconds,30}}]. 22 | 23 | %%-------------------------------------------------------------------- 24 | %% @spec init_per_suite(Config0) -> 25 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 26 | %% Config0 = Config1 = [tuple()] 27 | %% Reason = term() 28 | %% @end 29 | %%-------------------------------------------------------------------- 30 | init_per_suite(Config) -> 31 | Config. 32 | 33 | %%-------------------------------------------------------------------- 34 | %% @spec end_per_suite(Config0) -> term() | {save_config,Config1} 35 | %% Config0 = Config1 = [tuple()] 36 | %% @end 37 | %%-------------------------------------------------------------------- 38 | end_per_suite(_Config) -> 39 | ok. 40 | 41 | 42 | %%-------------------------------------------------------------------- 43 | %% @spec init_per_group(GroupName, Config0) -> 44 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 45 | %% GroupName = atom() 46 | %% Config0 = Config1 = [tuple()] 47 | %% Reason = term() 48 | %% @end 49 | %%-------------------------------------------------------------------- 50 | init_per_group(_GroupName, Config) -> 51 | Config. 52 | 53 | %%-------------------------------------------------------------------- 54 | %% @spec end_per_group(GroupName, Config0) -> 55 | %% term() | {save_config,Config1} 56 | %% GroupName = atom() 57 | %% Config0 = Config1 = [tuple()] 58 | %% @end 59 | %%-------------------------------------------------------------------- 60 | end_per_group(_GroupName, _Config) -> 61 | ok. 62 | 63 | %%-------------------------------------------------------------------- 64 | %% @spec init_per_testcase(TestCase, Config0) -> 65 | %% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} 66 | %% TestCase = atom() 67 | %% Config0 = Config1 = [tuple()] 68 | %% Reason = term() 69 | %% @end 70 | %%-------------------------------------------------------------------- 71 | init_per_testcase(_TestCase, Config) -> 72 | Config. 73 | 74 | %%-------------------------------------------------------------------- 75 | %% @spec end_per_testcase(TestCase, Config0) -> 76 | %% term() | {save_config,Config1} | {fail,Reason} 77 | %% TestCase = atom() 78 | %% Config0 = Config1 = [tuple()] 79 | %% Reason = term() 80 | %% @end 81 | %%-------------------------------------------------------------------- 82 | end_per_testcase(_TestCase, _Config) -> 83 | ok. 84 | 85 | %%-------------------------------------------------------------------- 86 | %% @spec groups() -> [Group] 87 | %% Group = {GroupName,Properties,GroupsAndTestCases} 88 | %% GroupName = atom() 89 | %% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] 90 | %% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] 91 | %% TestCase = atom() 92 | %% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} 93 | %% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | 94 | %% repeat_until_any_ok | repeat_until_any_fail 95 | %% N = integer() | forever 96 | %% @end 97 | %%-------------------------------------------------------------------- 98 | groups() -> 99 | []. 100 | 101 | %%-------------------------------------------------------------------- 102 | %% @spec all() -> GroupsAndTestCases | {skip,Reason} 103 | %% GroupsAndTestCases = [{group,GroupName} | TestCase] 104 | %% GroupName = atom() 105 | %% TestCase = atom() 106 | %% Reason = term() 107 | %% @end 108 | %%-------------------------------------------------------------------- 109 | all() -> 110 | [disable_tco]. 111 | 112 | %%-------------------------------------------------------------------- 113 | %% @spec TestCase() -> Info 114 | %% Info = [tuple()] 115 | %% @end 116 | %%-------------------------------------------------------------------- 117 | disable_tco() -> 118 | []. 119 | 120 | %%-------------------------------------------------------------------- 121 | %% @spec TestCase(Config0) -> 122 | %% ok | exit() | {skip,Reason} | {comment,Comment} | 123 | %% {save_config,Config1} | {skip_and_save,Reason,Config1} 124 | %% Config0 = Config1 = [tuple()] 125 | %% Reason = term() 126 | %% Comment = term() 127 | %% @end 128 | %%-------------------------------------------------------------------- 129 | disable_tco(_Config) -> 130 | try 131 | disable_tco_example:f(1) 132 | catch 133 | _:_?CAPTURE_STACKTRACE -> 134 | ?assertEqual([{s, [1]}, {g, 2}, {'-f/1-fun-0-', 2}, {f, 1}], extract_stacktrace(?GET_STACKTRACE)) 135 | end. 136 | 137 | extract_stacktrace(StackTrace) -> 138 | lists:reverse( 139 | lists:foldl( 140 | fun({disable_tco_example, Function, Arity, _Attrs}, Acc) -> 141 | [{Function, Arity}|Acc]; 142 | (_, Acc) -> 143 | Acc 144 | end, [], StackTrace)). 145 | 146 | -------------------------------------------------------------------------------- /test/disable_tco_example.erl: -------------------------------------------------------------------------------- 1 | -module(disable_tco_example). 2 | -compile({parse_transform, astranaut_disable_tco}). 3 | -export([f/1,a/1]). 4 | 5 | % Auxilary function to print stack trace (by throwing an exception). 6 | 7 | s(X) when X < 0 -> 0. 8 | 9 | % Example 1: Stack trace in presence of a higher order function. 10 | % > disable_tco_example:f(1). 11 | 12 | % before disable tco 13 | % ** exception error: no function clause matching disable_tco_example:s(1) (src/disable_tco_example.erl, line 9) 14 | % in function disable_tco_example:g/2 (src/disable_tco_example.erl, line 28) 15 | 16 | % after disable tco 17 | % ** exception error: no function clause matching disable_tco_example:s(1) (src/disable_tco_example.erl, line 9) 18 | % in function disable_tco_example:g/2 (src/disable_tco_example.erl, line 28) 19 | % in call from disable_tco_example:'-f/1-fun-0-'/2 (src/disable_tco_example.erl, line 25) 20 | % in call from disable_tco_example:f/1 (src/disable_tco_example.erl, line 26) 21 | 22 | f(X) -> 23 | H = fun (I, A) -> I(A, 3) end, 24 | H(fun g/2,X). 25 | g(X,Y) -> 26 | s(X) - Y. 27 | 28 | % Example 2: Stack trace for chain of 1st order function applications. 29 | % > test:a(1). 30 | 31 | % before disable tco 32 | % ** exception error: no function clause matching disable_tco_example:s(7) (src/disable_tco_example.erl, line 9) 33 | 34 | % after disable tco 35 | % ** exception error: no function clause matching disable_tco_example:s(7) (src/disable_tco_example.erl, line 9) 36 | % in function disable_tco_example:c/1 (src/disable_tco_example.erl, line 44) 37 | % in call from disable_tco_example:b/1 (src/disable_tco_example.erl, line 43) 38 | % in call from disable_tco_example:a/1 (src/disable_tco_example.erl, line 42) 39 | 40 | a(X) -> b(X + 1). 41 | b(X) -> c(X + 2). 42 | c(X) -> s(X + 3). 43 | --------------------------------------------------------------------------------