![Travis CI](https://travis-ci.org/slepher/astranaut.svg?branch=master)
# requirements
   erlang R19 or higher
# traverse
### traverse functions:
```erlang
astranaut_traverse:map(map_fun(), form(), Opts :: opts()) ->
traverse_return(node()) | parse_transform_return(node()).
astranaut_traverse:reduce(reduce_fun(), state(), form(), Opts :: opts()) ->
traverse_return(state()).
astranaut_traverse:map_with_state(map_state_fun(), state(), form(), Opts :: opts()) ->
traverse_return(node()) | parse_transform_return(node()).
astranaut_traverse:mapfold(mapfold_fun(), state(), form(), Opts :: opts()) ->
traverse_return({form(), state()}).
```
*arguments*
```erlang
form() :: node() | [node()].
node() :: erlang ast node.
state() :: any().
```
*traverse_fun()*
```erlang
map_fun() :: (node(), Attr :: attr()) -> TraverseFunReturn :: traverse_fun_return(node()).
reduce_fun() :: (node(), state(), Attr :: attr()) -> TraverseFunReturn :: traverse_fun_return(state()).
map_state_fun() :: (node(), state(), Attr :: attr()) -> TraverseFunReturn :: traverse_fun_return(node()).
mapfold_fun() :: (node(), state(), Attr :: attr()) -> TraverseFunReturn :: traverse_fun_return({node(), state()}).
```
*Attr*
```erlang
attr() :: #{step => Step :: step(), node :: NodeType :: node_type(), attribute :: Attribute}.
```
*Step*
  which traverse step while traversing, very useful while traverse_style() in opts() is all.
```erlang
step() :: pre | post | leaf.
```
*NodeType*
  ast node type.
```erlang
node_type() :: form | attribute | pattern | expression | guard.
```
*Attribute*
  if NodeType is attribute, Attribute is name of attribute, or Attribute does not exists.
*TraverseFunReturn*
```erlang
traverse_fun_return(SA) :: SA | {error, error()} | {error, SA, error()} |
{warning, SA, error()} | {warning, error()} |
continue | {continue, SA} |
astranaut_walk_return:astranaut_walk_return(A) |
astranaut_traverse_m:astranaut_traverse_m(S, A) |
astranaut_return_m:astranaut_return_m(A) |
astranaut_base_m:astranaut_base_m(A).
SA is same return type in traverse_fun(), but A is always node(), and S is always state().
```
*Node*
  node transformed to new node in traverse_walk_fun(), default is node() provided in traverse_walk_fun().
*State*
  state used in traverse\_walk\_fun(), default is state() provided in traverse_walk_fun().
*Continue*
  if Continue is true or traverse\_fun\_return(A) is continue | {continue, A}, and Step of attr() is pre
  skip traverse children of currrent node and go to next node, nothing affected when Step of attr() is leaf or post.
*error()*
```erlang
error() :: Reason.
```
*Line*
  expected error line, default is line of node in traverse_walk_fun().
*Module*
  error formatter module which provide format_error/1, default is formatter option in opts().
*Opts*
```erlang
opts() :: {traverse => TraverseStyle :: traverse_style(), parse_transform => ParseTransform :: boolean(),
node => FormType :: form_type(), formatter => Formatter,
children => Children, sequence_children => SequenceChildren}.
```
*Formatter*
  error formatter module which provide format_error/1, default is astranaut_traverse.
*ParseTransform*
  traverse_return(node()) will be transformed to parse_transform_return()
  which could directed used as return in parse_transform/2, useful in map/3, map_with_state/3.
*NodeType*
  node\_type(). if from() is incomplete erlang ast, this should be provided to help generate node_type() in attr().
  if top node is function or attribute, default top node\_type() in attr() is form.
  else, default top node\_type() in attr() is expression.
*TraverseStyle*
  pre | post | all | leaf.
*Children*
   true: Only traverse children of node, not traverse node its self.
*SequenceChildren*
   callback to defined your own traverse children method
```erlang
SequenceChildren = fun(DeepListOfChildrenM) -> MChildren end.
```
   traverse right expression first in match expression
```erlang
SequenceChildren =
fun([PatternMs, ExpressionMs]) ->
%% reverse the traverse order, traverse ExpressionMs first
%% deep_r_sequence_m means reverse sequence_m the first level of deep list.
astranaut_traverse:deep_r_sequence_m([PatternMs, ExpressionMs])
end.
```
   do something special to Clause Patterns
```erlang
SequenceChildren =
fun([PatternMs|GuardsAndExpressionMs]) ->
%% PatternMs is a list of monad, sequence_m it to get a monad of list.
PatternsM = astranaut_traverse:deep_sequence_m(PatternMs),
%% do something special to PatternsM monad.
PatternsM1 = do_something_special(PatternsM),
%% deep_sequence_m the new tree.
astranaut_traverse:deep_sequence_m([PatternsM1|GuardsAndExpressionMs])
end.
```
   do something special to Each Clause Patterns
```erlang
SequenceChildren =
fun([PatternMs|GuardsAndExpressionMs]) ->
%% PatternMs is a list of monad, sequence_m it to get a monad of list.
PatternMs1 = lists:map(fun(PatternM) -> do_something_special(PatternM) end, PatternMs),
%% deep_sequence_m the new tree.
astranaut_traverse:deep_sequence_m([PatternMs1|GuardsAndExpressionMs])
end.
```
*traverse_return(Return)*
```erlang
traverse_return(Return) :: Return | {ok, Return, Errors :: traverse_return_error(), Warnings :: traverse_return_error()} |
{error, Errors, Warnings}.
```
*parse_transform_return(Return)*
```erlang
parse_transform_return(Return) :: Return | {warning, Return, Warnings :: prase_transform_error()} |
{error, Errors :: parse_transform_error(), Warnings}.
```
*ReturnError*
```erlang
traverse_return_error() :: [{Line :: line(), Module :: module(), Reason :: term()}].
parse_transform_error() :: [{File, traverse_retrun_error()}].
```
*Structs*
```erlang
astranaut_traverse:traverse_fun_return(#{}) -> traverse_fun_return().
astranaut_traverse:traverse_error(#{}) -> error().
```
*Advanced*
  powerful map\_m function if you famillar with monad.
```erlang
astranaut_traverse:map_m((A, attr()) => monad(A), map_m_opts()) -> monad(A).
```
# monad modules
### astranaut\_traverse\_m
   the main monad of astranaut\_traverse.
### astranaut\_base\_m
  a monad with errors and warnings.
  you could just append errors or warnings to it.
```erlang
astranaut_base_m:then(
astranaut_base_m:warning(warning_0),
astranaut_base_m:return(ok)).
```
### astranaut\_return\_m
  the monad result of astranaut\_traverse\_m:run(MA, Formatter, State).
  could be transformed to compiler return format with astranaut\_return\_m:to\_compiler/1.
  could transforme compiler return format to astranaut\_return\_m with astranaut\_return\_m:from_compiler/1.
### astranaut\_error\_state
### astranaut\_walk\_return
   return type of Fun in astranut\_traverse:(map\_m|map|reduce|map\_with\_state|mapfold|)(Fun, Forms, Opts).
# Quote
### quick start
with
-include_lib("astranaut/include/quote.hrl").
you can use quote(Code) to represent ast of the code.
quote(Code) | quote(Code, Options)
*Options*
```
atom() => {atom() => true}
proplists() => map(),
Line => #{line => Line}
#{line => Line, code_line => CodeLine, debug => Debug}.
```
*Line*
   Line could be any expression, the ast will be transformed.
```erlang
quote(
fun(_) ->
ok
end, 10).
=>
astranaut:replace_line_zero(quote(fun(_) -> ok end), 10).
=>
{'fun', 10, {clauses, [{clause, 10, [{var, 10, '_'}], [], [{atom, 10, ok}]}]}}.
```
*CodeLine*
   if CodeLine is true
```erlang
10: quote(
11: fun(_) ->
12: ok
13: end, code_line).
=>
{'fun' 10, {clauses, [{clause, 11, [{var, 11, '_'}], [], [{atom, 12, ok}]}]}}.
```
*Debug*
  if Debug is true, ast generated by quote will be printed to console at compile time.__
### unquote
```erlang
unquote(Ast)
unquote = Ast.
unquote_splicing(Asts)
unquote_splicing = Asts.
```
*why two forms*
  unquote(Var) is not a valid ast in function clause pattern.__
```erlang
Var = {var, 0, A}
quote(fun(unquote = Var) -> unquote(Var) end).
```
### variable binding
*bind one ast*
  \_@V, same as unquote(V)
```erlang
V = {var, 10, 'Var'},
quote({hello, World, unquote(V)}) =>
{tuple, 1, [{atom, 1, hello}, {var, 1, 'World'}, V]} =>
{tuple, 1, [{atom, 1, hello}, {var, 1, 'World'}, {var, 10, 'Var'}]}
```
*bind a list of ast*
  \_L@Vs,same as unquote_splicing(Vs)
```erlang
Vs = [{var, 2, 'Var'}, {atom, 2, atom}],
quote({A, unquote_splicing(Vs), B}) =>
{tuple, 1, [{var, 1, 'A'}, Vs ++ [{var, 1, 'B'}]]} =>
{tuple, 1, [{var, 1, 'A'}, {var, 2, 'Var'}, {atom, 2, atom}, {var, 1, 'B'}]}
```
*bind a value*
```erlang
Atom = hello,
Integer = 10,
Float = 1.3,
String = "123",
Variable = 'Var',
_A@Atom => {atom, 0, Atom} => {atom, 0, hello}
_I@Integer => {integer, 0, Integer} => {integer, 0, 10}
_F@Float => {float, 0, Float} => {float, 0, 1.3}
_S@String => {string, 0, String} => {string, 0, "123"}
_V@Variable => {var, 0, Variable} => {var, 0, 'Var'}
```
*why binding*
  \_X@V could be used in any part of quoted ast.
  it's legal:
```erlang
Class = 'Class0',
Exception = 'Exception0',
StackTrace = 'StackTrace0',
quote(
try
throw(hello)
catch
_V@Class:_V@Exception:_V@StackTrace ->
erlang:raise(_V@Class, _V@Exception, _V@StackTrace)
end).
```
  it's illegal
```erlang
Class = {var, 0, 'Class0'},
Exception = {var, 0, 'Exception0'},
StackTrace = {var, 0, 'StackTrace0'},
quote(
try
A
catch
unquote(Class):unquote(Exception):unquote(StackTrace) ->
erlang:raise(_@Class, _@Exception, _@StackTrace)
end).
```
in other hand, V in unquote_xxx(V) could be any expression, it's more powerful than _X@V
### unquote and variable binding in pattern
  quote macro could also be used in pattern match such as
  for limit of erlang ast format in pattern, some special forms is used
left side of match
```erlang
quote(_A@Atom) = {atom, 1, A}
=>
{atom, _, Atom} = {atom, 1, A}
```
  function pattern
```erlang
macro_clause(quote = {hello, _A@World = World2} = C) ->
quote({hello2, _A@World, _@World2,_@C});
=>
macro_clause({tuple, _, [{atom, _, hello}, {atom, _, World} = World2]} = C) ->
{tuple, 2, {atom, 2, hello2}, {atom, 2, World}, World2, C}
```
  case clause pattern:
```erlang
case Ast of
quote(_A@Atom) ->
Atom;
_ ->
other
end.
=>
case ast of
{atom, _, Atom} ->
Atom;
_ ->
other
end.
```
# Macro
*Usage*
```erlang
-include_lib("astranaut/include/macro.hrl").
```
macro.hrl add three attribute: use\_macro, exec\_macro debug\_macro
*use_macro*
```erlang
-use_macro({Macro/A, opts()}).
-use_macro({Module, Macro/A, opts()}).
```
*exec_macro*
  execute macro and add result to current ast.
```erlang
-exec_macro({Macro, Arguments}).
-exec_macro({Module, Macro, Arguments}).
```
*export_macro*
  used in where macro defined, options in export\_macro will be merged to options in use_macro.
```erlang
-export_macro({[MacroA/A, MacroB/B], opts()}).
```
*debug_macro*
```erlang
-debug_macro(true).
```
   module will be printed to console after astranaut\_macro transform.
*opts()*
```erlang
#{debug => Debug, debug_ast => DebugAst, alias => Alias,
formatter => Formatter, attrs => Attrs, order => Order,
as_attr => AsAttr, merge_function => MergeFunction, auto_export => AutoExport,
group_args => GroupArgs}
}
```
   opts() could also be proplists, same usage of map().
*Debug*
  print code generated when macro called compile time.
*DebugAst*
  print ast generated when macro called compile time.
*Alias*
   use Alias(Arguments) instead of Module:Macro(Arguments).
*Formatter*
   module include format\_error/1 to format macro errors,
   if formatter is true, formatter is the module where macro defined,
   default is astranaut\_traverse.
*Attrs*
   module attributes as extra args while calling macro.
```
-module(a).
-behaviour(gen_server).
-use_macro({macro/2, [{attrs, [module, line, behaviour]}]}).
hello() ->
macro_a:macro(world).
macro(Ast, #{module => Module, line => Line, behaviour => Behaviours} = Attributes) ->
{warning, Ast, {attributes, Module, Line, Behaviours}}.
```
*Order*
   macro expand order for nested macro , value is pre | post. default is post.
   pre is expand macro from outside to inside, post is expand macro from inside to outside.
*AsAttr*
   user defined attribute name replace of -exec\_macro.
*MergeFunction*
   -exec\_macro ast function merge to function with same name and arity if exists.
*AutoExport*
   -exec\_macro ast function auto export, merge to current export if exists.
*GroupArgs*
   treat macro arguments as list
```erlang
-use_macro({a, [group_args]}).
test() ->
a(hello, world).
a(Asts) ->
quote({unquote_splicing(Asts)}).
```
  define macro as normal erlang functions.
  macro expand order is the order of -use\_macro in file.
  macro will be expand at compile time by parse\_transformer astranaut\_macro.
  macro does not know runtime value of arguments.
  arguments passed in macro is erlang ast.
  arguments passed in -exec\_macro is term.
  -export will be moved to appropriate location in ast forms.
  macro return value is same meaning of traverse\_fun\_return().
```erlang
-use_macro({macro_1/1, []}).
-use_macro({macro_2/1, []}).
-export([test/0]).
test() ->
macro_1(hello()).
macro_1(Ast) ->
quote(
fun() -> unquote(Ast) end
).
-exec_macro({macro_2, [hello]}).
macro_2(Name) ->
astranaut:function(
Name,
quote(
fun() ->
unquote_atom(Name)
end)).
```
=>
```erlang
-use_macro({macro_1/1, []}).
-export([test/0]).
-export([hello/0]).
test_macro_1() ->
fun() -> hello() end.
macro_1(Ast) ->
quote(
fun() -> unquote(Ast) end
).
hello() ->
hello.
macro_2(Name) ->
astranaut:function(
Name,
quote(
fun() ->
unquote_atom(Name)
end)).
```
*hygienic macro*
   each macro expansion has it's unique namespace.
   @{macro\_module\_name}@\_{counter} is added to it's original name.
```erlang
-module(macro_example).
macro_with_vars_1(Ast) ->
quote(
begin
A = 10,
B = unquote(Ast),
A + B
end
).
macro_with_vars_2(Ast) ->
quote(
begin
A = 10,
B = unquote(Ast),
A + B
end
).
```
```erlang
test_macro_with_vars(N) ->
A1 = macro_with_vars_1(N),
A2 = macro_with_vars_2(A1),
A3 = macro_with_vars_2(N),
A4 = macro_with_vars_1(A1),
A1 + A2.
```
=>
```erlang
test_macro_with_vars(N) ->
A1 =
begin
A@macro_example@_1 = 10,
B@macro_example@_1 = N,
A@macro_example@_1 + B@macro_example@_1
end,
A2 =
begin
A@macro_example@_3 = 10,
B@macro_example@_3 = A1,
A@macro_example@_3 + B@macro_example@_3
end,
A3 =
begin
A@macro_example@_4 = 10,
B@macro_example@_4 = N,
A@macro_example@_4 + B@macro_example@_4
end,
A4 =
begin
A@macro_example@_2 = 10,
B@macro_example@_2 = A1,
A@macro_example@_2 + B@macro_example@_2
end,
A1 + A2 + A3 + A4.
```
*parse_transform*
   for old parse_transform module which is used widely, two function is provided.
```erlang
*astranaut_macro:transform_macro(Module, Function, Arity, Opts, Forms).
*astranaut_macro:transform_macros([Macro...], Forms).
Macro = {Module, Function, Arity, Opts}.
```
   example:
```
-module(do).
-include_lib("astranaut/include/quote.hrl").
-export([parse_transform/2]).
parse_transform(Forms, _Options) ->
astranaut_macro:transform_macro(do_macro, do, 1, [{alias, do}, formatter], Forms).
```
# Rebinding
```erlang
-include_lib("erlando/include/rebinding.hrl").
-rebinding_all(Opts).
-rebinding_fun(FAs).
-rebinding_fun({FAs, Opts}).
FAs = FA | [FA...].
FA = F | F/A.
Opts = Opt | [Opt...] | #{OptKey => OptValue}.
Opt = OptKey | {OptKey, OptValue}.
#{OptKey => OptValue} = #{debug => true | false}.
```
*Rebinding Attributes*
   -rebinding\_all -rebinding\_fun defines rebinding scope.
   -rebinding\_all meaning rebinding scope is all function.
   -rebinding\_fun meaning rebinding scope is in functions mentioned.
   rebinding options is avaliable in scope mentioned.
   rebinding option debug means print code after rebinding rules applied.
   if neither -rebinding\_fun nor -rebinding\_all is used, rebinding scope is all function and rebinding options is [].
*Rebinding Rules*
   pattern variables will be renamed while already used include:
     function pattern variables
     match pattern variables
     list comprehension pattern variables
     bitstring comprehension pattern variables
   pattern variables with same name in same pattern scope will be renamed to same name.
   other variable will be renamed follow last renamed vaiable last avaliable scope used.
   +{pattern variable} means pinned variable like Elixir ^{pattern variable}, also works like other variable.
*Examples*
```erlang
hello(A, A, B) ->
{A, A, B} = {A + 1, A + 1, B + 1},
{A, A, B}.
```
=>
```erlang
hello(A, A, B) ->
{A_1, A_1, B_1} = {A + 1, A + 1, B + 1},
{A_1, A_1, B_1}.
```
```erlang
hello(A, B) ->
A =
case A of
B ->
B = A + B,
A = A + B,
B = A + B,
B;
A ->
B = A + B
B
end,
B =
case A of
B ->
B = A + B,
A = A + B,
B = A + B,
B;
A ->
B = A + B
B
end,
{A, B}.
```
=>
```erlang
hello(A, B) ->
A_2 =
case A of
B ->
B_1 = A + B,
A_1 = A + B_1,
B_2 = A_1 + B_1,
B_2;
A ->
B_1 = A + B
B_1
end,
B_5 =
case A_2 of
B ->
%% B_1 and B_2 is already used, next var name is B_3, last var name in scope is B.
B_3 = A_2 + B,
A_3 = A_2 + B_3,
B_4 = A_3 + B_3,
B_4,
A_2 ->
B_3 = A_2 + B
B_3
end,
{A_2, B_5}.
```
```erlang
hello_f(A) ->
A = A + 1,
F = fun F (0) -> 0; F (A) -> A = F(A - 1), A end,
A = F(A),
A.
```
=>
```erlang
hello_f(A) ->
A_1 = A + 1,
F = fun F(0) -> 0; F(A_2) -> A_3 = F(A_2 - 1), A_3 end,
A_2 = F(A_1),
F_1 = fun F_1(0) -> 0; F_1(A_3) -> A_4 = F_1(A_3 - 1), A_4 end,
A_3 = F_1(A_2),
A_3.
```
# Struct
*Usage*
```erlang
-include_lib("erlando/include/struct.hrl").
-record(test, {name = hello, value}).
-astranaut_struct([test]).
-export([new/0, update_name/2]).
new() ->
#test{}.
update_name(Name, #test{} = Test) ->
Test#test{name = Name}.
```
*Desc*
   convert erlang record to elixir like struct
   code above is converted to code below
```erlang
-include_lib("erlando/include/struct.hrl").
-record(test, {name = hello, value}).
-astranaut_struct([test]).
-export([new/0, update_name/2]).
new() ->
#{'__struct__' => test, name => hello, value => undefined}.
update_name(Name, #{'__struct__' := test} = Test) ->
Test#{name => Name}.
```
*Struct Options*
   -astranaut\_struct could have extra options:
   non\_auto\_fill : means fields will not set default to undefined when not defined and initialized.
   enforce\_keys : means compile will failed when field is not setted when construct struct, works like elixir.
```erlang
-astranaut_struct({test, [non_auto_fill, {enforce_keys, [name]}]}).
test_failed() ->
#test{}.
%% compile failed
%% the following keys must also be given when building struct test: [name]
test_non_auto_fill() ->
#test{name = test}.
%% ==>
test_non_auto_fill_transformed() ->
#{'__struct__' => test, name => test}. %% value is not set to undefined
test_auto_fill() ->
#test{name = test}.
%% ==>
test_auto_fill_transformed() ->
#{'__struct__' => test, name => test, value => undefined}. %% value is set to undefined at default.
```
*Macros*
```erlang
astranaut_struct:from_record(StructName, Record) -> Struct. %% convert a recrod to struct with same name.
astranaut_struct:to_record(StructName, Struct) -> Record. %% convert a struct to record with same name.
astranaut_struct:from_map(StructName, Struct) -> Struct. %% build a struct from map, enforce_keys will be checked.
astranaut_struct:update(StructName, Struct) -> Struct. %% update a struct from it's old version.
```