<commit_before>-module(clj_analyzer).

-include("clojerl.hrl").

-export([ analyze/2
        , macroexpand_1/2
        , is_special/1
        ]).

-spec analyze(any(), clj_env:env()) -> clj_env:env().
analyze(Form, Env0) ->
  {Expr, Env1} =  clj_env:pop_expr(analyze_form(Form, Env0)),
  clj_env:push_expr(Expr#{top_level => true}, Env1).

-spec is_special('clojerl.Symbol':type()) -> boolean().
is_special(S) ->
  clj_rt:'symbol?'(S) andalso
    maps:is_key(clj_rt:str(S), special_forms()).

-spec macroexpand_1(any(), clj_env:env()) -> any().
macroexpand_1(Form, Env) ->
  Op        = clj_rt:first(Form),
  IsSymbol  = clj_rt:'symbol?'(Op),
  IsSpecial = is_special(Op),
  MacroVar  = case IsSymbol of
                true -> lookup_var(Op, false);
                false -> ?NIL
              end,

  case
    not IsSpecial
    andalso (MacroVar =/= ?NIL)
    andalso ('clojerl.Var':is_macro(MacroVar))
  of
    true ->
      Args = [Form, Env | clj_rt:to_list(clj_rt:rest(Form))],
      'clojerl.IFn':apply(MacroVar, Args);
    false ->
      case IsSymbol andalso not IsSpecial of
        true  -> maybe_macroexpand_symbol(Form, Op);
        false -> Form
      end
  end.

%%------------------------------------------------------------------------------
%% Internal
%%------------------------------------------------------------------------------

-spec maybe_macroexpand_symbol(any(), 'clojerl.Symbol':type()) -> any().
maybe_macroexpand_symbol(Form, OpSym) ->
  OpBin = clj_rt:name(OpSym),
  case 'clojerl.String':char_at(OpBin, 0) of
    <<".">> ->
      DotSym     = clj_rt:symbol(<<".">>),
      Length     = 'clojerl.String':count(OpBin),
      NameBin    = 'clojerl.String':substring(OpBin, 1, Length),
      NameSym    = clj_rt:symbol(NameBin),
      [_, Target | Args] = clj_rt:to_list(Form),

      clj_rt:list([DotSym, Target, NameSym | Args]);
    _ ->
      case 'clojerl.String':ends_with(OpBin, <<".">>) of
        true ->
          NewSym  = clj_rt:symbol(<<"new">>),
          Length  = 'clojerl.String':count(OpBin),
          NameBin = 'clojerl.String':substring(OpBin, 0, Length - 1),
          NameSym = clj_rt:symbol(NameBin),
          [_ | Args]  = clj_rt:to_list(Form),

          clj_rt:list([NewSym, NameSym | Args]);
        false ->
          Form
      end
  end.

-spec special_forms() -> #{'clojerl.Symbol':type() => fun() | ?NIL}.
special_forms() ->
  #{ <<"def">>          => fun parse_def/2
   , <<"quote">>        => fun parse_quote/2
   , <<"fn*">>          => fun parse_fn/2
   , <<"do">>           => fun parse_do/2
   , <<"if">>           => fun parse_if/2
   , <<"let*">>         => fun parse_let/2
   , <<"letfn*">>       => fun parse_letfn/2
   , <<"loop*">>        => fun parse_loop/2
   , <<"recur">>        => fun parse_recur/2
   , <<"case*">>        => fun parse_case/2
   , <<"throw">>        => fun parse_throw/2
   , <<"try">>          => fun parse_try/2

   , <<"var">>          => fun parse_var/2
<commit_msg>[#341] Keep metadata after macro expansion<commit_after>-module(clj_analyzer).

-include("clojerl.hrl").

-export([ analyze/2
        , macroexpand_1/2
        , is_special/1
        ]).

-spec analyze(any(), clj_env:env()) -> clj_env:env().
analyze(Form, Env0) ->
  {Expr, Env1} =  clj_env:pop_expr(analyze_form(Form, Env0)),
  clj_env:push_expr(Expr#{top_level => true}, Env1).

-spec is_special('clojerl.Symbol':type()) -> boolean().
is_special(S) ->
  clj_rt:'symbol?'(S) andalso
    maps:is_key(clj_rt:str(S), special_forms()).

-spec macroexpand_1(any(), clj_env:env()) -> any().
macroexpand_1(Form, Env) ->
  Op        = clj_rt:first(Form),
  IsSymbol  = clj_rt:'symbol?'(Op),
  IsSpecial = is_special(Op),
  MacroVar  = case IsSymbol of
                true -> lookup_var(Op, false);
                false -> ?NIL
              end,

  Expanded  = case
                not IsSpecial
                andalso MacroVar =/= ?NIL
                andalso 'clojerl.Var':is_macro(MacroVar)
              of
                true ->
                  Args = [Form, Env | clj_rt:to_list(clj_rt:rest(Form))],
                  'clojerl.IFn':apply(MacroVar, Args);
                false ->
                  case IsSymbol andalso not IsSpecial of
                    true  -> maybe_macroexpand_symbol(Form, Op);
                    false -> Form
                  end
              end,
  keep_location_meta(Expanded, Form).

%%------------------------------------------------------------------------------
%% Internal
%%------------------------------------------------------------------------------

-spec maybe_macroexpand_symbol(any(), 'clojerl.Symbol':type()) -> any().
maybe_macroexpand_symbol(Form, OpSym) ->
  OpBin = clj_rt:name(OpSym),
  case 'clojerl.String':char_at(OpBin, 0) of
    <<".">> ->
      DotSym  = clj_rt:symbol(<<".">>),
      Length  = 'clojerl.String':count(OpBin),
      NameBin = 'clojerl.String':substring(OpBin, 1, Length),
      NameSym = clj_rt:symbol(NameBin),
      [_, Target | Args] = clj_rt:to_list(Form),
      clj_rt:list([DotSym, Target, NameSym | Args]);
    _ ->
      case 'clojerl.String':ends_with(OpBin, <<".">>) of
        true ->
          NewSym  = clj_rt:symbol(<<"new">>),
          Length  = 'clojerl.String':count(OpBin),
          NameBin = 'clojerl.String':substring(OpBin, 0, Length - 1),
          NameSym = clj_rt:symbol(NameBin),
          [_ | Args]  = clj_rt:to_list(Form),

          clj_rt:list([NewSym, NameSym | Args]);
        false ->
          Form
      end
  end.

-spec keep_location_meta(any(), any()) -> any().
keep_location_meta(Form, Form) ->
  Form;
keep_location_meta(Expanded, Form) ->
  case clj_rt:'meta?'(Form) andalso clj_rt:'meta?'(Expanded) of
    true  ->
      LocationMeta = clj_reader:location_meta(Form),
      ExpandedMeta = clj_rt:meta(Expanded),
      clj_rt:with_meta(Expanded, clj_rt:merge([ExpandedMeta, LocationMeta]));
    false ->
      Expanded
  end.

-spec special_forms() -> #{'clojerl.Symbol':type() => fun() | ?NIL}.
special_forms() ->
  #{ <<"def">>          => fun parse_def/2
   , <<"quote">>        => fun parse_quote/2
   , <<"fn*">>          => fun parse_fn/2
   , <<"do">>           => fun parse_do/2
   , <<"if">>           => fun parse_if/2
   , <<"let*">>         => fun parse_let/2
   , <<"letfn*">>       => fun parse_letfn/2
   , <<"loop*">>        => fun parse_loop/2
   , <<"recur">>        => fun parse_recur/2
   , <<"case*">>        => fun parse_case/2
   , <<"throw">>        => fun parse_throw/2
   , <<"try">>          => fun parse_try/2

   , <<"var">>          => fun parse_var/2

{'fraction_non_alphanumeric': 0.21860252702085553, 'fraction_numerical': 0.00867711980514538, 'mean_word_length': 2.002742230347349, 'pattern_counts': {'":': 0, '<': 71, '<?xml version=': 0, '>': 146, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5969231', 'n_tokens_mistral': 2358, 'n_tokens_neox': 2290, 'n_words': 389}