anchor.diff:  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) ->
  
positive.code:        f("gpb_version_as_list() ->~n"),
         f("    ~w.~n", [gpb:version_as_list()])]).
  
  %% -- encoders -----------------------------------------------------
  
  format_encoder_topcase(Indent, Defs, MsgVar) ->
  
negative.code:       "    -o Dir~n"
        "          Specify output directory for where to generate~n"
        "          the <Protofile>.erl and <Protofile>.hrl~n"
        "    -nif~n"
        "          Generate nifs for linking with the protobuf C(++) library.~n"
        "    -v optionally | always | never~n"
        "          Specify how the generated encoder should~n"
        "          verify the message to be encoded.~n"
  