anchor.diff:      end.
   
   
  -%% ===================================================================
  -%% Private
  -%% ===================================================================
  -
  -
  -%% @private
  -parse_defaults(#parse{defaults=Defs1, syntax=Syntax}=Parse) ->
  +%% @private
  +parse_defaults(#parse{syntax = Syntax} = Parse) ->
       SynDefs = maps:get('__defaults', Syntax, #{}),
  -    Defs2 = maps:merge(SynDefs, Defs1),
  -    parse_defaults(maps:to_list(Defs2), Parse).
  +    parse_defaults(maps:to_list(SynDefs), Parse).
   
   
   %% @private
   parse_defaults([], Parse) ->
       {ok, Parse};
   
  -parse_defaults([{Key, Val}|Rest], #parse{ok=Ok}=Parse) ->
  +parse_defaults([{Key, Val} | Rest], #parse{ok = Ok} = Parse) ->
       case lists:keymember(Key, 1, Ok) of
           true ->
               parse_defaults(Rest, Parse);
  
positive.code:     end;
  
  spec(string, Val) ->
      if 
          is_list(Val) ->
              case catch erlang:list_to_binary(Val) of
                  {'EXIT', _} -> error;
  
negative.code: %% Types
  %% ===================================================================
  
  -type syntax() :: #{key() => syntax_opt()}.
  
  -type syntax_opt() ::
      syntax_term() |
      {list|slist|ulist, syntax_term()}.
  
  
  %% __defaults (#{atom() => term()})
  %%   you can set any default at any level
  %%   however, if the level is empty in the object, its defaults will not be processed
  %%   you must set the whole level as default (key=>#{})
  %%
  %% __mandatory ([atom()])
  %%   sets mandatory fields
  %%   for nested objects, the parent object should include the child as mandatory
  %%
  %%
  
  
  -type syntax_term() ::
      ignore |
      any |
      atom | {atom, [atom()]} |
      boolean |
      list |
      pid |
      proc |
      module |
      integer | pos_integer | nat_integer | {integer, none|integer(), none|integer()} |
      {integer, [integer()]} |
      float |
      {record, atom()} |
      string |
      binary |
      base64 | base64url |
      lower |
      upper |
      ip | ip4 | ip6 | host | host6 |
      email |
      {function, pos_integer()} |
      unquote |
      path | fullpath |
      uri | uris |
      tokens | words |
      map |
      log_level |
      map() |                     % Allow for nested objects
      list() |                    % First matching option is used
      syntax_fun() |
      {syntax, syntax_opt()} |    % Nested syntax (i.e. {list, {syntax, Syntax}}). __mandatory is local.
      '__defaults' |              % Defaults for this level #{atom() => term()}
      '__mandatory'.              % Mandatory fields for this level [atom()]
  
  -type key() :: atom().
  -type val() :: term().
  
  
  -type syntax_fun() ::
      fun((val()) -> syntax_fun_out()) |
      fun((key(), val()) -> syntax_fun_out()) |
      fun((key(), val(), fun_ctx()) -> syntax_fun_out()).
  
  
  -type syntax_fun_out() ::
      ok |
      {ok, val()} |
      {ok, key(), val()} |
      error |
      {error, term()}.
  
  -type fun_ctx() ::
      parse_opts() |
      #{
          ok => [{key(), val()}],
          ok_all => [{binary(), val()}],
          no_ok => [binary()],
          path => binary()
      }.
  
  -type parse_opts() ::
      #{
          path => binary(),           % Use base path instead of <<>>
          warning_unknown => boolean()
      }.
  
  -type error() ::
      {syntax_error, Path :: binary()} |
      {missing_field, binary()} |
      term().                             % When syntax_fun() returns {error, term()}
  
  -type out() :: #{key() => term()}.
  
  
  -type unknown_keys() :: [binary()].
  
  
  -record(parse, {
      ok = [] :: [{key(), val()}],
      no_ok = [] :: [binary()],
      ok_all = [] :: [{binary(), val()}],
      syntax :: map(),
      path :: binary(),
      opts :: parse_opts()
  }).
  
  