anchor.diff:      [f("~p(<<_:~w, Rest/binary>>, Msg) ->~n", [SkipFn, BitLen]),
        f("    ~p(Rest, 0, 0, Msg).~n~n",  [mk_fn(d_read_field_def_, MsgName)])].
   
  +
  +format_nif_decoder_error_wrappers(Defs, _AnRes, _Opts) ->
  +    [format_msg_nif_error_wrapper(MsgName)
  +     || {{msg, MsgName}, _MsgDef} <- Defs].
  +
  +format_msg_nif_error_wrapper(MsgName) ->
  +    FnName = mk_fn(d_msg_, MsgName),
  +    f("~p(Bin) ->~n"
  +      "    erlang:nif_error({error,{nif_not_loaded,~p}}, [Bin]).~n~n",
  +     [FnName, MsgName]).
  +
   %% -- verifiers -----------------------------------------------------
   
   format_verifier_topcase(Indent, Defs, MsgVar) ->
  
positive.code: parse_opt({"I", [Dir]})          -> {true, {i,Dir}};
  parse_opt({"I"++Dir, []})        -> {true, {i,Dir}};
  parse_opt({"o", [Dir]})          -> {true, {o,Dir}};
  parse_opt({"v", ["optionally"]}) -> {true, {verify,optionally}};
  parse_opt({"v", ["always"]})     -> {true, {verify,always}};
  parse_opt({"v", ["never"]})      -> {true, {verify,never}};
  
negative.code: mk_fn(Prefix, Middlefix, Suffix) ->
      list_to_atom(lists:concat([Prefix, Middlefix, "_", Suffix])).
  
  mk_c_fn(Prefix, Suffix) ->
      dot_to_underscore(lists:concat([Prefix, Suffix])).
  
  mk_c_var(Prefix, Suffix) ->
      dot_to_underscore(lists:concat([Prefix, Suffix])).
  
  dot_to_underscore(X) when is_atom(X) -> dot_to_underscore(atom_to_list(X));
  dot_to_underscore(X) when is_list(X) -> dot_replace_s(X, "_").
  
  dot_replace_s(S, New) when is_list(S) -> d_r(S, New);
  dot_replace_s(S, New) when is_atom(S) -> d_r(atom_to_list(S), New).
  
  d_r("."++Rest, New) -> New ++ d_r(Rest, New);
  d_r([C|Rest], New)  -> [C | d_r(Rest, New)];
  d_r("", _New)       -> "".
  
  %% -- nif c++ code -----------------------------------------------------
  
  format_nif_cc(Mod, Defs, Opts) ->
      [format_nif_cc_includes(Mod, Defs, Opts),
       format_nif_cc_local_function_decls(Mod, Defs, Opts),
       format_nif_cc_mk_atoms(Mod, Defs, Opts),
       format_nif_cc_decoders(Mod, Defs, Opts),
       format_nif_cc_unpackers(Mod, Defs, Opts),
       format_nif_cc_foot(Mod, Defs, Opts)].
  
  get_cc_pkg(Defs) ->
      case lists:keyfind(package, 1, Defs) of
          false              -> "";
          {package, Package} -> "::"++dot_replace_s(Package, "::")
      end.
  
  is_lite_rt(Defs) ->
      OptimizeOpts = [Opt || {option,{optimize_for,Opt}} <- Defs],
      lists:any(fun(OptOpt) -> OptOpt == 'LITE_RUNTIME' end,
                OptimizeOpts).
  
  format_nif_cc_includes(Mod, Defs, _Opts) ->
      IsLiteRT = is_lite_rt(Defs),
      %% FIXME:
      ["#include <string.h>\n",
       "#include <string>\n",
       "\n",
       "#include <erl_driver.h>\n",
       "#include <erl_nif.h>\n",
       "\n",
       f("#include \"~s.pb.h\"\n", [Mod]),
       ["#include <google/protobuf/message_lite.h>\n" || IsLiteRT],
       "\n"].
  
  format_nif_cc_local_function_decls(_Mod, Defs, _Opts) ->
      CPkg = get_cc_pkg(Defs),
      [[begin
            UnpackFnName = mk_c_fn(u_msg_, MsgName),
            CMsgType = CPkg ++ "::" ++ dot_replace_s(MsgName, "::"),
            ["static ERL_NIF_TERM ",UnpackFnName,["(ErlNifEnv *env, ",
                                                  "const ",CMsgType," *m);\n"]]
        end
        || {{msg, MsgName}, _Fields} <- Defs],
       "\n"].
  
  format_nif_cc_mk_atoms(_Mod, Defs, _Opts) ->
      EnumAtoms = lists:flatten([[Sym || {Sym, _V} <- EnumDef]
                                 || {{enum, _}, EnumDef} <- Defs]),
      RecordAtoms = [MsgName || {{msg, MsgName}, _Fields} <- Defs],
      MiscAtoms = [undefined, true, false],
      Atoms = lists:usort(EnumAtoms ++ RecordAtoms ++ MiscAtoms),
      AtomVars = [{mk_c_var(aa_, A), A} || A <- Atoms],
  
      [[f("static ERL_NIF_TERM ~s;\n", [Var]) || {Var,_Atom} <- AtomVars],
       "\n",
       ["static void install_atoms(ErlNifEnv *env)\n"
        "{\n",
        [f("    ~s = enif_make_atom(env, \"~s\");\n", [AtomVar, Atom])
         || {AtomVar, Atom} <- AtomVars],
        "}\n",
        "\n"]].
  
  format_nif_cc_foot(Mod, Defs, _Opts) ->
      ["static int\n",
       "load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)\n",
       "{\n",
       "    install_atoms(env);\n"
       "    return 0;\n",
       "}\n",
       "\n",
       "static int\n",
       "reload(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)\n",
       "{\n",
       "    return 0;\n",
       "}\n",
       "\n",
       "void\n",
       "unload(ErlNifEnv *env, void *priv_data)\n",
       "{\n",
       "}\n",
       "\n",
       "static int\n",
       "upgrade(ErlNifEnv *env, void **priv_data, void **old_priv_data,\n",
       "        ERL_NIF_TERM load_info)\n",
       "{\n",
       "    return 0;\n",
       "}\n",
       "\n",
       "static ErlNifFunc nif_funcs[] =\n",
       "{\n",
       [begin
            FnName = mk_fn(d_msg_, MsgName),
            CFnName = mk_c_fn(d_msg_, MsgName),
            IsLast = I == length(Defs),
            Comma = ["," || not IsLast],
            f("    {\"~s\", 1, ~s}~s\n", [FnName, CFnName, Comma])
        end
        || {I, {{msg, MsgName}, _MsgFields}} <- index_seq(Defs)],
       "\n",
       "};\n",
       "\n",
       f("ERL_NIF_INIT(~s, nif_funcs, load, reload, upgrade, unload)\n",
         [Mod])].
  
  format_nif_cc_decoders(Mod, Defs, Opts) ->
      CPkg = get_cc_pkg(Defs),
      [format_nif_cc_decoder(Mod, CPkg, MsgName, Fields, Opts)
       || {{msg, MsgName}, Fields} <- Defs].
  
  format_nif_cc_decoder(_Mod, CPkg, MsgName, _Fields, _Opts) ->
      FnName = mk_c_fn(d_msg_, MsgName),
      UnpackFnName = mk_c_fn(u_msg_, MsgName),
      CMsgType = CPkg ++ "::" ++ dot_replace_s(MsgName, "::"),
      ["static ERL_NIF_TERM\n",
       FnName,"(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])\n",
       "{\n",
       "    ErlNifBinary data;\n",
       "    ERL_NIF_TERM res;\n",
       "    ",CMsgType," *m = new ",CMsgType,"();\n",
       "\n"
       "    if (argc != 1)\n",
       "        return enif_make_badarg(env);\n",
       "\n",
       "    if (m == NULL)\n",
       "        return enif_make_badarg(env);\n", %% FIXME: enomem?
       "\n",
       "    if (!enif_inspect_binary(env, argv[0], &data))\n"
       "        return enif_make_badarg(env);\n",
       "\n",
       "    if (!m->ParseFromArray(data.data, data.size))\n",
       "    {\n",
       "        delete m;\n",
       "        return enif_make_badarg(env);\n",
       "    }\n",
       "\n",
       "    res = ",UnpackFnName,"(env, m);\n",
       "    delete m;\n",
       "    return res;\n",
       "}\n"
       "\n"].
  
  format_nif_cc_unpackers(_Mod, Defs, _Opts) ->
      CPkg = get_cc_pkg(Defs),
      [format_nif_cc_unpacker(CPkg, MsgName, Fields, Defs)
       || {{msg, MsgName}, Fields} <- Defs].
  
  format_nif_cc_unpacker(CPkg, MsgName, Fields, Defs) ->
      UnpackFnName = mk_c_fn(u_msg_, MsgName),
      CMsgType = CPkg ++ "::" ++ dot_replace_s(MsgName, "::"),
      Is = [I || {I,_} <- index_seq(Fields)],
      ["static ERL_NIF_TERM\n",
       UnpackFnName,"(ErlNifEnv *env, const ",CMsgType," *m)\n",
       "{\n",
       "    ERL_NIF_TERM res;\n",
       f("    ERL_NIF_TERM rname = ~s;\n", [mk_c_var(aa_, MsgName)]),
       [f("    ERL_NIF_TERM elem~w;\n", [I]) || I <- Is],
       "\n",
       [begin
            DestVar = f("elem~w",[I]),
            format_nif_cc_field_unpacker(DestVar, "m", Field, Defs)
        end
        || {I, Field} <- index_seq(Fields)],
       "\n",
       f("    res = enif_make_tuple(env, ~w, rname~s);\n",
         [length(Fields) + 1, [f(", elem~w",[I]) || I <- Is]]),
       "    return res;\n"
       "}\n",
      "\n"].
  
  format_nif_cc_field_unpacker(DestVar, MsgVar, Field, Defs) ->
      #field{occurrence=Occurrence}=Field,
      case Occurrence of
          required ->
              format_nif_cc_field_unpacker_single(DestVar, MsgVar, Field, Defs);
          optional ->
              format_nif_cc_field_unpacker_single(DestVar, MsgVar, Field, Defs);
          repeated ->
              format_nif_cc_field_unpacker_repeated(DestVar, MsgVar, Field, Defs)
      end.
  
  
  format_nif_cc_field_unpacker_single(DestVar, MsgVar, Field, Defs) ->
      #field{name=FName, type=FType} = Field,
      LCFName = to_lower(FName),
      [f("    if (!~s->has_~s())\n", [MsgVar, LCFName]),
       f("        ~s = aa_undefined;\n", [DestVar]),
       f("    else\n"),
       indent_lines(
         8,
         case FType of
             float ->
                 [f("~s = enif_make_double(env, (double)~s->~s());\n",
                    [DestVar, MsgVar, LCFName])];
             double ->
                 [f("~s = enif_make_double(env, ~s->~s());\n",
                    [DestVar, MsgVar, LCFName])];
             _S32 when FType == sint32;
                       FType == int32;
                       FType == sfixed32 ->
                 [f("~s = enif_make_int(env, ~s->~s());\n",
                    [DestVar, MsgVar, LCFName])];
             _S64 when FType == sint64;
                       FType == int64;
                       FType == sfixed64 ->
                 [f("~s = enif_make_int64(env, (ErlNifSInt64)~s->~s());\n",
                    [DestVar, MsgVar, LCFName])];
             _U32 when FType == uint32;
                       FType == fixed32 ->
                 [f("~s = enif_make_uint(env, ~s->~s());\n",
                    [DestVar, MsgVar, LCFName])];
             _U64 when FType == uint64;
                       FType == fixed64 ->
                 [f("~s = enif_make_uint64(env, (ErlNifUInt64)~s->~s());\n",
                    [DestVar, MsgVar, LCFName])];
             bool ->
                 [f("if (~s->~s())\n", [MsgVar, LCFName]),
                  f("    ~s = aa_true;\n", [DestVar]),
                  f("else\n"),
                  f("    ~s = aa_false;\n", [DestVar])];
             {enum, EnumName} ->
                 {value, {{enum,EnumName}, Enumerations}} =
                     lists:keysearch({enum,EnumName}, 1, Defs),
                 [] ++
                     [f("switch (~s->~s()) {\n", [MsgVar, LCFName])] ++
                     [f("    case ~w: ~s = ~s; break;\n",
                        [Value, DestVar, mk_c_var(aa_, Sym)])
                      || {Sym, Value} <- Enumerations] ++
                     [f("    default: ~s = aa_undefined;\n", [DestVar])] ++
                     [f("}\n")];
             string ->
                 %% FIXME: utf-8 unpack!!
                 [f("~s = enif_make_string_len(\n", [DestVar]),
                  f("            env,\n"),
                  f("            ~s->~s().data(),\n",[MsgVar, LCFName]),
                  f("            ~s->~s().size(),\n",[MsgVar, LCFName]),
                  f("            ERL_NIF_LATIN1);\n")];
             bytes ->
                 [f("{\n"),
                  f("    unsigned char *data;\n"),
                  f("    unsigned int   bSize = ~s->~s().size();\n",
                    [    MsgVar, LCFName]),
                  f("    const char    *bData = ~s->~s().data();\n",
                    [    MsgVar, LCFName]),
                  f("    data = enif_make_new_binary(\n"), %% can data be NULL??
                  f("               env,\n"),
                  f("               bSize,\n"),
                  f("               &~s);\n", [DestVar]),
                  f("    memmove(data, bData, bSize);\n"),
                  f("}\n")];
             {msg, Msg2Name} ->
                 UnpackFnName = mk_c_fn(u_msg_, Msg2Name),
                 [f("~s = ~s(env, &~s->~s());\n",
                    [DestVar, UnpackFnName, MsgVar, LCFName])]
         end),
       "\n"].
  
  format_nif_cc_field_unpacker_repeated(DestVar, MsgVar, Field, Defs) ->
      #field{name=FName, type=FType} = Field,
      LCFName = to_lower(FName),
      [f("    {\n"),
       f("        unsigned int numElems = ~s->~s_size();\n", [MsgVar, LCFName]),
       f("        ERL_NIF_TERM relem[numElems];\n"),
       f("        unsigned int i;\n"),
       "\n",
       f("        for (i = 0; i < numElems; i++)\n"),
       indent_lines(
         12,
         case FType of
             float ->
                 [f("relem[i] = enif_make_double(env, (double)~s->~s(i));\n",
                    [MsgVar, LCFName])];
             double ->
                 [f("relem[i] = enif_make_double(env, ~s->~s(i));\n",
                    [MsgVar, LCFName])];
             _S32 when FType == sint32;
                       FType == int32;
                       FType == sfixed32 ->
                 [f("relem[i] = enif_make_int(env, ~s->~s(i));\n",
                    [MsgVar, LCFName])];
             _S64 when FType == sint64;
                       FType == int64;
                       FType == sfixed64 ->
                 [f("relem[i] = enif_make_int64(env, (ErlNifSInt64)~s->~s(i));\n",
                    [MsgVar, LCFName])];
             _U32 when FType == uint32;
                       FType == fixed32 ->
                 [f("relem[i] = enif_make_uint(env, ~s->~s(i));\n",
                    [MsgVar, LCFName])];
             _U64 when FType == uint64;
                       FType == fixed64 ->
                 [f("relem[i] = enif_make_uint64(env,\n"
                    "                            (ErlNifUInt64)~s->~s(i));\n",
                    [MsgVar, LCFName])];
             bool ->
                 [f("if (~s->~s(i))\n", [MsgVar, LCFName]),
                  f("    relem[i] = aa_true;\n"),
                  f("else\n"),
                  f("    relem[i] = aa_false;\n")];
             {enum, EnumName} ->
                 {value, {{enum,EnumName}, Enumerations}} =
                     lists:keysearch({enum,EnumName}, 1, Defs),
                 [] ++
                     [f("switch (~s->~s(i)) {\n", [MsgVar, LCFName])] ++
                     [f("    case ~w: relem[i] = ~s; break;\n",
                        [Value, mk_c_var(aa_, Sym)])
                      || {Sym, Value} <- Enumerations] ++
                     [f("    default: relem[i] = aa_undefined;\n")] ++
                     [f("}\n")];
             string ->
                 %% FIXME: utf-8 unpack!!
                 [f("relem[i] = enif_make_string_len(\n"),
                  f("               env,\n"),
                  f("               ~s->~s(i).data(),\n",[MsgVar, LCFName]),
                  f("               ~s->~s(i).size(),\n",[MsgVar, LCFName]),
                  f("               ERL_NIF_LATIN1);\n")];
             bytes ->
                 [f("{\n"),
                  f("    unsigned char *data;\n"),
                  f("    unsigned int   bSize = ~s->~s(i).size();\n",
                    [    MsgVar, LCFName]),
                  f("    const char    *bData = ~s->~s(i).data();\n",
                    [    MsgVar, LCFName]),
                  f("    data = enif_make_new_binary(\n"), %% can data be NULL??
                  f("               env,\n"),
                  f("               bSize,\n"),
                  f("               &relem[i]);\n"),
                  f("    memmove(data, bData, bSize);\n"),
                  f("}\n")];
             {msg, Msg2Name} ->
                 UnpackFnName = mk_c_fn(u_msg_, Msg2Name),
                 [f("relem[i] = ~s(env, &~s->~s(i));\n",
                    [UnpackFnName, MsgVar, LCFName])]
         end),
       f("        ~s = enif_make_list_from_array(env, relem, numElems);\n",
         [DestVar]),
       "    }\n",
       "\n"].
  
  format_load_nif(Mod, Opts) ->
      case proplists:get_value(nif_load_code, Opts, '$undefined') of
          '$undefined' ->
              ["load_nif() ->\n",
               "    SelfDir = filename:dirname(code:which(?MODULE)),\n",
               "    NifDir = case lists:reverse(filename:split(SelfDir)) of\n"
               "                 [\"ebin\" | PDR] ->\n"
               "                      PD = filename:join(lists:reverse(PDR)),\n",
               "                      filename:join(PD, \"priv\");\n",
               "                 _ ->\n",
               "                      SelfDir\n",
               "             end,\n",
               "    NifBase = \"", atom_to_list(Mod) ++ ".nif", "\",\n",
               "    Nif = filename:join(NifDir, NifBase),\n",
               f("    erlang:load_nif(Nif, ~w).\n", [gpb:version_as_list()])];
          CodeBody when is_list(CodeBody); is_binary(CodeBody) ->
              ["load_nif() ->\n",
               CodeBody];
          CodeFn when is_function(CodeFn, 0) ->
              ["load_nif() ->\n",
               CodeFn()];
          CodeFn when is_function(CodeFn, 1) ->
              NifBase = atom_to_list(Mod) ++ ".nif",
              ["load_nif() ->\n",
               CodeFn(NifBase)]
      end.
  
  to_lower(A) when is_atom(A) ->
      list_to_atom(string:to_lower(atom_to_list(A))).
  
  %% -- compile to memory -----------------------------------------------------
  
  compile_to_binary(MsgDefs, ErlCode, Opts) ->
  