THRIFT-4270 Generate Erlang mapping functions for const maps and lists
Client: Erlang
Patch: David Hull <david.hull@openx.com>

This closes #1320
diff --git a/compiler/cpp/src/thrift/generate/t_erl_generator.cc b/compiler/cpp/src/thrift/generate/t_erl_generator.cc
index 9a5ce16..f63b3e9 100644
--- a/compiler/cpp/src/thrift/generate/t_erl_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_erl_generator.cc
@@ -118,6 +118,8 @@
   void generate_type_metadata(std::string function_name, vector<string> names);
   void generate_enum_info(t_enum* tenum);
   void generate_enum_metadata();
+  void generate_const_function(t_const* tconst, ostringstream& exports, ostringstream& functions);
+  void generate_const_functions();
 
   /**
    * Service-level generation functions
@@ -136,6 +138,7 @@
   std::string erl_imports();
   std::string render_includes();
   std::string type_name(t_type* ttype);
+  std::string render_const_list_values(t_type* type, t_const_value* value);
 
   std::string function_signature(t_function* tfunction, std::string prefix = "");
 
@@ -188,7 +191,6 @@
   void export_function(t_function* tfunction, std::string prefix = "");
   void export_string(std::string name, int num);
 
-  void export_types_function(t_function* tfunction, std::string prefix = "");
   void export_types_string(std::string name, int num);
 
   /**
@@ -218,7 +220,9 @@
   std::ofstream f_types_file_;
   std::ofstream f_types_hrl_file_;
 
-  std::ofstream f_consts_;
+  std::ofstream f_consts_file_;
+  std::ofstream f_consts_hrl_file_;
+
   std::ostringstream f_service_;
   std::ofstream f_service_file_;
   std::ofstream f_service_hrl_;
@@ -230,6 +234,7 @@
   std::vector<std::string> v_enum_names_;
   std::vector<std::string> v_exception_names_;
   std::vector<t_enum*> v_enums_;
+  std::vector<t_const*> v_consts_;
 };
 
 /**
@@ -246,31 +251,41 @@
   export_lines_first_ = true;
   export_types_lines_first_ = true;
 
+  string program_module_name = make_safe_for_module_name(program_name_);
+
   // types files
-  string f_types_name = get_out_dir() + make_safe_for_module_name(program_name_) + "_types.erl";
-  string f_types_hrl_name = get_out_dir() + make_safe_for_module_name(program_name_) + "_types.hrl";
+  string f_types_name = get_out_dir() + program_module_name + "_types.erl";
+  string f_types_hrl_name = get_out_dir() + program_module_name + "_types.hrl";
 
   f_types_file_.open(f_types_name.c_str());
   f_types_hrl_file_.open(f_types_hrl_name.c_str());
 
-  hrl_header(f_types_hrl_file_, make_safe_for_module_name(program_name_) + "_types");
+  hrl_header(f_types_hrl_file_, program_module_name + "_types");
 
-  f_types_file_ << erl_autogen_comment() << endl << "-module("
-                << make_safe_for_module_name(program_name_) << "_types)." << endl << erl_imports()
+  f_types_file_ << erl_autogen_comment() << endl
+                << "-module(" << program_module_name << "_types)." << endl
+                << erl_imports() << endl;
+
+  f_types_file_ << "-include(\"" << f_types_hrl_name << "\")." << endl
                 << endl;
 
-  f_types_file_ << "-include(\"" << make_safe_for_module_name(program_name_) << "_types.hrl\")."
-                << endl << endl;
-
   f_types_hrl_file_ << render_includes() << endl;
 
-  // consts file
-  string f_consts_name = get_out_dir() + make_safe_for_module_name(program_name_)
-                         + "_constants.hrl";
-  f_consts_.open(f_consts_name.c_str());
+  // consts files
+  string f_consts_name = get_out_dir() + program_module_name + "_constants.erl";
+  string f_consts_hrl_name = get_out_dir() + program_module_name + "_constants.hrl";
 
-  f_consts_ << erl_autogen_comment() << endl << erl_imports() << endl << "-include(\""
-            << make_safe_for_module_name(program_name_) << "_types.hrl\")." << endl << endl;
+  f_consts_file_.open(f_consts_name.c_str());
+  f_consts_hrl_file_.open(f_consts_hrl_name.c_str());
+
+  f_consts_file_ << erl_autogen_comment() << endl
+                 << "-module(" << program_module_name << "_constants)." << endl
+                 << erl_imports() << endl
+                 << "-include(\"" << f_types_hrl_name << "\")." << endl
+                 << endl;
+
+  f_consts_hrl_file_ << erl_autogen_comment() << endl << erl_imports() << endl
+                     << "-include(\"" << f_types_hrl_name << "\")." << endl << endl;
 }
 
 /**
@@ -351,6 +366,8 @@
   f_types_file_ << f_info_ext_.str();
   f_types_file_ << "struct_info_ext(_) -> erlang:error(function_clause)." << endl << endl;
 
+  generate_const_functions();
+
   generate_type_metadata("struct_names", v_struct_names_);
   generate_enum_metadata();
   generate_type_metadata("enum_names", v_enum_names_);
@@ -360,7 +377,8 @@
 
   f_types_file_.close();
   f_types_hrl_file_.close();
-  f_consts_.close();
+  f_consts_file_.close();
+  f_consts_hrl_file_.close();
 }
 
 void t_erl_generator::generate_type_metadata(std::string function_name, vector<string> names) {
@@ -393,6 +411,68 @@
   (void)ttypedef;
 }
 
+
+void t_erl_generator::generate_const_function(t_const* tconst, ostringstream& exports, ostringstream& functions) {
+  t_type* type = get_true_type(tconst->get_type());
+  string name = tconst->get_name();
+  t_const_value* value = tconst->get_value();
+
+  if (type->is_map()) {
+    t_type* ktype = ((t_map*)type)->get_key_type();
+    t_type* vtype = ((t_map*)type)->get_val_type();
+    string const_fun_name = lowercase(name);
+
+    // Emit const function export.
+    if (exports.tellp() > 0) { exports << ", "; }
+    exports << const_fun_name << "/1, " << const_fun_name << "/2";
+
+    // Emit const function definition.
+    map<t_const_value*, t_const_value*>::const_iterator i, end = value->get_map().end();
+    // The one-argument form throws an error if the key does not exist in the map.
+    for (i = value->get_map().begin(); i != end;) {
+      functions << const_fun_name << "(" << render_const_value(ktype, i->first) << ") -> "
+                << render_const_value(vtype, i->second);
+      ++i;
+      functions << (i != end ? ";\n" : ".\n\n");
+    }
+
+    // The two-argument form returns a default value if the key does not exist in the map.
+    for (i = value->get_map().begin(); i != end; ++i) {
+      functions << const_fun_name << "(" << render_const_value(ktype, i->first) << ", _) -> "
+                << render_const_value(vtype, i->second) << ";\n";
+    }
+    functions << const_fun_name << "(_, Default) -> Default.\n\n";
+  } else if (type->is_list()) {
+    string const_fun_name = lowercase(name);
+
+    if (exports.tellp() > 0) { exports << ", "; }
+    exports << const_fun_name << "/1, " << const_fun_name << "/2";
+
+    size_t list_size = value->get_list().size();
+    string rendered_list = render_const_list_values(type, value);
+    functions << const_fun_name << "(N) when N >= 1, N =< " << list_size << " ->\n"
+              << indent_str() << "element(N, {" << rendered_list << "}).\n";
+    functions << const_fun_name << "(N, _) when N >= 1, N =< " << list_size << " ->\n"
+              << indent_str() << "element(N, {" << rendered_list << "});\n"
+              << const_fun_name << "(_, Default) -> Default.\n\n";
+    indent_down();
+  }
+}
+
+void t_erl_generator::generate_const_functions() {
+  ostringstream exports;
+  ostringstream functions;
+  vector<t_const*>::iterator c_iter;
+  for (c_iter = v_consts_.begin(); c_iter != v_consts_.end(); ++c_iter) {
+    generate_const_function(*c_iter, exports, functions);
+  }
+  if (exports.tellp() > 0) {
+    f_consts_file_ << "-export([" << exports.str() << "]).\n\n"
+                   << functions.str();
+  }
+}
+
+
 /**
  * Generates code for an enumerated type. Done using a class to scope
  * the values.
@@ -459,8 +539,11 @@
   string name = tconst->get_name();
   t_const_value* value = tconst->get_value();
 
-  f_consts_ << "-define(" << constify(make_safe_for_module_name(program_name_)) << "_"
-            << constify(name) << ", " << render_const_value(type, value) << ")." << endl << endl;
+  // Save the tconst so that function can be emitted in generate_const_functions().
+  v_consts_.push_back(tconst);
+
+  f_consts_hrl_file_ << "-define(" << constify(make_safe_for_module_name(program_name_)) << "_"
+                     << constify(name) << ", " << render_const_value(type, value) << ")." << endl << endl;
 }
 
 /**
@@ -561,28 +644,32 @@
     }
     out << "])";
   } else if (type->is_list()) {
-    t_type* etype;
-    etype = ((t_list*)type)->get_elem_type();
-    out << "[";
-
-    bool first = true;
-    const vector<t_const_value*>& val = value->get_list();
-    vector<t_const_value*>::const_iterator v_iter;
-    for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
-      if (first) {
-        first = false;
-      } else {
-        out << ",";
-      }
-      out << render_const_value(etype, *v_iter);
-    }
-    out << "]";
+    out << "[" << render_const_list_values(type, value) << "]";
   } else {
     throw "CANNOT GENERATE CONSTANT FOR TYPE: " + type->get_name();
   }
   return out.str();
 }
 
+string t_erl_generator::render_const_list_values(t_type* type, t_const_value* value) {
+  std::ostringstream out;
+  t_type* etype = ((t_list*)type)->get_elem_type();
+
+  bool first = true;
+  const vector<t_const_value*>& val = value->get_list();
+  vector<t_const_value*>::const_iterator v_iter;
+  for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
+    if (first) {
+      first = false;
+    } else {
+      out << ",";
+    }
+    out << render_const_value(etype, *v_iter);
+  }
+  return out.str();
+}
+
+
 string t_erl_generator::render_default_value(t_field* field) {
   t_type* type = field->get_type();
   if (type->is_struct() || type->is_xception()) {
@@ -968,16 +1055,6 @@
   export_lines_ << name << "/" << num;
 }
 
-void t_erl_generator::export_types_function(t_function* tfunction, string prefix) {
-  t_struct::members_type::size_type num = tfunction->get_arglist()->get_members().size();
-  if (num > static_cast<t_struct::members_type::size_type>(std::numeric_limits<int>().max())) {
-    throw "integer overflow in t_erl_generator::export_types_function, name " + tfunction->get_name();
-  }
-  export_types_string(prefix + tfunction->get_name(),
-                      1 // This
-                      + static_cast<int>(num));
-}
-
 void t_erl_generator::export_types_string(string name, int num) {
   if (export_types_lines_first_) {
     export_types_lines_first_ = false;
diff --git a/lib/erl/test/test_const.erl b/lib/erl/test/test_const.erl
index ed0cf03..627777b 100644
--- a/lib/erl/test/test_const.erl
+++ b/lib/erl/test/test_const.erl
@@ -31,4 +31,24 @@
   {struct, _} = constants_demo_types:struct_info('consts_Blah'),
   ok.
 
+const_map_test() ->
+  ?assertEqual(233, constants_demo_constants:gen_map(35532)),
+  ?assertError(function_clause, constants_demo_constants:gen_map(0)),
+
+  ?assertEqual(853, constants_demo_constants:gen_map(43523, default)),
+  ?assertEqual(default, constants_demo_constants:gen_map(10110, default)),
+
+  ?assertEqual(98325, constants_demo_constants:gen_map2("lkjsdf")),
+  ?assertError(function_clause, constants_demo_constants:gen_map2("nonexist")),
+
+  ?assertEqual(233, constants_demo_constants:gen_map2("hello", 321)),
+  ?assertEqual(321, constants_demo_constants:gen_map2("goodbye", 321)).
+
+const_list_test() ->
+  ?assertEqual(23598352, constants_demo_constants:gen_list(2)),
+  ?assertError(function_clause, constants_demo_constants:gen_list(0)),
+
+  ?assertEqual(3253523, constants_demo_constants:gen_list(3, default)),
+  ?assertEqual(default, constants_demo_constants:gen_list(10, default)).
+
 -endif. %% TEST