1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 #include <string>
21 #include <fstream>
22 #include <iostream>
23 #include <limits>
24 #include <vector>
25 
26 #include <stdlib.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <sstream>
30 #include "thrift/platform.h"
31 #include "thrift/version.h"
32 #include "thrift/generate/t_generator.h"
33 
34 using std::map;
35 using std::ofstream;
36 using std::ostream;
37 using std::ostringstream;
38 using std::string;
39 using std::stringstream;
40 using std::vector;
41 
42 static const std::string endl = "\n"; // avoid ostream << std::endl flushes
43 
44 /**
45  * Erlang code generator.
46  *
47  */
48 class t_erl_generator : public t_generator {
49 public:
t_erl_generator(t_program * program,const std::map<std::string,std::string> & parsed_options,const std::string & option_string)50   t_erl_generator(t_program* program,
51                   const std::map<std::string, std::string>& parsed_options,
52                   const std::string& option_string)
53     : t_generator(program) {
54     (void)option_string;
55     std::map<std::string, std::string>::const_iterator iter;
56 
57     legacy_names_ = false;
58     delimiter_ = ".";
59     app_prefix_ = "";
60     maps_ = false;
61     export_lines_first_ = true;
62     export_types_lines_first_ = true;
63 
64     for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) {
65       if( iter->first.compare("legacynames") == 0) {
66         legacy_names_ = true;
67       } else if( iter->first.compare("maps") == 0) {
68         maps_ = true;
69       } else if( iter->first.compare("delimiter") == 0) {
70         delimiter_ = iter->second;
71       } else if( iter->first.compare("app_prefix") == 0) {
72         app_prefix_ = iter->second;
73       } else {
74         throw "unknown option erl:" + iter->first;
75       }
76     }
77 
78     out_dir_base_ = "gen-erl";
79   }
80 
81   /**
82    * Init and close methods
83    */
84 
85   void init_generator() override;
86   void close_generator() override;
87   std::string display_name() const override;
88 
89   /**
90    * Program-level generation functions
91    */
92 
93   void generate_typedef(t_typedef* ttypedef) override;
94   void generate_enum(t_enum* tenum) override;
95   void generate_const(t_const* tconst) override;
96   void generate_struct(t_struct* tstruct) override;
97   void generate_xception(t_struct* txception) override;
98   void generate_service(t_service* tservice) override;
99   void generate_member_type(std::ostream& out, t_type* type);
100   void generate_member_value(std::ostream& out, t_type* type, t_const_value* value);
101 
102   std::string render_member_type(t_field* field);
103   std::string render_member_value(t_field* field);
104   std::string render_member_requiredness(t_field* field);
105 
106   //  std::string render_default_value(t_type* type);
107   std::string render_default_value(t_field* field);
108   std::string render_const_value(t_type* type, t_const_value* value);
109   std::string render_type_term(t_type* ttype, bool expand_structs, bool extended_info = false);
110 
111   /**
112    * Struct generation code
113    */
114 
115   void generate_erl_struct(t_struct* tstruct, bool is_exception);
116   void generate_erl_struct_definition(std::ostream& out, t_struct* tstruct);
117   void generate_erl_struct_member(std::ostream& out, t_field* tmember);
118   void generate_erl_struct_info(std::ostream& out, t_struct* tstruct);
119   void generate_erl_extended_struct_info(std::ostream& out, t_struct* tstruct);
120   void generate_erl_function_helpers(t_function* tfunction);
121   void generate_type_metadata(std::string function_name, vector<string> names);
122   void generate_enum_info(t_enum* tenum);
123   void generate_enum_metadata();
124   void generate_const_function(t_const* tconst, ostringstream& exports, ostringstream& functions);
125   void generate_const_functions();
126 
127   /**
128    * Service-level generation functions
129    */
130 
131   void generate_service_helpers(t_service* tservice);
132   void generate_service_metadata(t_service* tservice);
133   void generate_service_interface(t_service* tservice);
134   void generate_function_info(t_service* tservice, t_function* tfunction);
135 
136   /**
137    * Helper rendering functions
138    */
139 
140   std::string erl_autogen_comment();
141   std::string erl_imports();
142   std::string render_includes();
143   std::string type_name(t_type* ttype);
144   std::string render_const_list_values(t_type* type, t_const_value* value);
145 
146   std::string function_signature(t_function* tfunction, std::string prefix = "");
147 
148   std::string argument_list(t_struct* tstruct);
149   std::string type_to_enum(t_type* ttype);
150   std::string type_module(t_type* ttype);
151 
make_safe_for_module_name(std::string in)152   std::string make_safe_for_module_name(std::string in) {
153     if (legacy_names_) {
154       return decapitalize(app_prefix_ + in);
155     } else {
156       return underscore(app_prefix_) + underscore(in);
157     }
158   }
159 
atomify(std::string in)160   std::string atomify(std::string in) {
161     if (legacy_names_) {
162       return "'" + decapitalize(in) + "'";
163     } else {
164       return "'" + in + "'";
165     }
166   }
167 
constify(std::string in)168   std::string constify(std::string in) {
169     if (legacy_names_) {
170       return capitalize(in);
171     } else {
172       return uppercase(in);
173     }
174   }
175 
176   static std::string comment(string in);
177 
178 private:
179   bool has_default_value(t_field*);
180 
181   /* if true retain pre 0.9.2 naming scheme for functions, atoms and consts */
182   bool legacy_names_;
183 
184   /* if true use maps instead of dicts in generated code */
185   bool maps_;
186 
187   /* delimiter between namespace and record name */
188   std::string delimiter_;
189 
190   /* used to avoid module name clashes for different applications */
191   std::string app_prefix_;
192 
193   /**
194    * add function to export list
195    */
196 
197   void export_function(t_function* tfunction, std::string prefix = "");
198   void export_string(std::string name, int num);
199 
200   void export_types_string(std::string name, int num);
201 
202   /**
203    * write out headers and footers for hrl files
204    */
205 
206   void hrl_header(std::ostream& out, std::string name);
207   void hrl_footer(std::ostream& out, std::string name);
208 
209   /**
210    * stuff to spit out at the top of generated files
211    */
212 
213   bool export_lines_first_;
214   std::ostringstream export_lines_;
215 
216   bool export_types_lines_first_;
217   std::ostringstream export_types_lines_;
218 
219   /**
220    * File streams
221    */
222 
223   std::ostringstream f_info_;
224   std::ostringstream f_info_ext_;
225 
226   ofstream_with_content_based_conditional_update f_types_file_;
227   ofstream_with_content_based_conditional_update f_types_hrl_file_;
228 
229   ofstream_with_content_based_conditional_update f_consts_file_;
230   ofstream_with_content_based_conditional_update f_consts_hrl_file_;
231 
232   std::ostringstream f_service_;
233   ofstream_with_content_based_conditional_update f_service_file_;
234   ofstream_with_content_based_conditional_update f_service_hrl_;
235 
236   /**
237    * Metadata containers
238    */
239   std::vector<std::string> v_struct_names_;
240   std::vector<std::string> v_enum_names_;
241   std::vector<std::string> v_exception_names_;
242   std::vector<t_enum*> v_enums_;
243   std::vector<t_const*> v_consts_;
244 };
245 
246 /**
247  * UI for file generation by opening up the necessary file output
248  * streams.
249  *
250  * @param tprogram The program to generate
251  */
init_generator()252 void t_erl_generator::init_generator() {
253   // Make output directory
254   MKDIR(get_out_dir().c_str());
255 
256   // setup export lines
257   export_lines_first_ = true;
258   export_types_lines_first_ = true;
259 
260   string program_module_name = make_safe_for_module_name(program_name_);
261 
262   // types files
263   string f_types_name = get_out_dir() + program_module_name + "_types.erl";
264   string f_types_hrl_name = get_out_dir() + program_module_name + "_types.hrl";
265 
266   f_types_file_.open(f_types_name.c_str());
267   f_types_hrl_file_.open(f_types_hrl_name.c_str());
268 
269   hrl_header(f_types_hrl_file_, program_module_name + "_types");
270 
271   f_types_file_ << erl_autogen_comment() << endl
272                 << "-module(" << program_module_name << "_types)." << endl
273                 << erl_imports() << endl;
274 
275   f_types_file_ << "-include(\"" << program_module_name << "_types.hrl\")." << endl
276                   << endl;
277 
278   f_types_hrl_file_ << render_includes() << endl;
279 
280   // consts files
281   string f_consts_name = get_out_dir() + program_module_name + "_constants.erl";
282   string f_consts_hrl_name = get_out_dir() + program_module_name + "_constants.hrl";
283 
284   f_consts_file_.open(f_consts_name.c_str());
285   f_consts_hrl_file_.open(f_consts_hrl_name.c_str());
286 
287   f_consts_file_ << erl_autogen_comment() << endl
288                  << "-module(" << program_module_name << "_constants)." << endl
289                  << erl_imports() << endl
290                  << "-include(\"" << program_module_name << "_types.hrl\")." << endl
291                  << endl;
292 
293   f_consts_hrl_file_ << erl_autogen_comment() << endl << erl_imports() << endl
294                      << "-include(\"" << program_module_name << "_types.hrl\")." << endl << endl;
295 }
296 
297 /**
298  * Boilerplate at beginning and end of header files
299  */
hrl_header(ostream & out,string name)300 void t_erl_generator::hrl_header(ostream& out, string name) {
301   out << erl_autogen_comment() << endl
302       << "-ifndef(_" << name << "_included)." << endl << "-define(_" << name << "_included, yeah)."
303       << endl;
304 }
305 
hrl_footer(ostream & out,string name)306 void t_erl_generator::hrl_footer(ostream& out, string name) {
307   (void)name;
308   out << "-endif." << endl;
309 }
310 
311 /**
312  * Renders all the imports necessary for including another Thrift program
313  */
render_includes()314 string t_erl_generator::render_includes() {
315   const vector<t_program*>& includes = program_->get_includes();
316   string result = "";
317   for (auto include : includes) {
318     result += "-include(\"" + make_safe_for_module_name(include->get_name())
319               + "_types.hrl\").\n";
320   }
321   if (includes.size() > 0) {
322     result += "\n";
323   }
324   return result;
325 }
326 
327 /**
328  * Autogen'd comment
329  */
erl_autogen_comment()330 string t_erl_generator::erl_autogen_comment() {
331   return std::string("%%\n") + "%% Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n"
332          + "%%\n" + "%% DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n"
333          + "%%\n";
334 }
335 
336 /**
337  * Comment out text
338  */
339 
comment(string in)340 string t_erl_generator::comment(string in) {
341   size_t pos = 0;
342   in.insert(pos, "%% ");
343   while ((pos = in.find_first_of('\n', pos)) != string::npos) {
344     in.insert(++pos, "%% ");
345   }
346   return in;
347 }
348 
349 /**
350  * Prints standard thrift imports
351  */
erl_imports()352 string t_erl_generator::erl_imports() {
353   return "";
354 }
355 
356 /**
357  * Closes the type files
358  */
close_generator()359 void t_erl_generator::close_generator() {
360 
361   export_types_string("struct_info", 1);
362   export_types_string("struct_info_ext", 1);
363   export_types_string("enum_info", 1);
364   export_types_string("enum_names", 0);
365   export_types_string("struct_names", 0);
366   export_types_string("exception_names", 0);
367 
368   f_types_file_ << "-export([" << export_types_lines_.str() << "])." << endl << endl;
369 
370   f_types_file_ << f_info_.str();
371   f_types_file_ << "struct_info(_) -> erlang:error(function_clause)." << endl << endl;
372 
373   f_types_file_ << f_info_ext_.str();
374   f_types_file_ << "struct_info_ext(_) -> erlang:error(function_clause)." << endl << endl;
375 
376   generate_const_functions();
377 
378   generate_type_metadata("struct_names", v_struct_names_);
379   generate_enum_metadata();
380   generate_type_metadata("enum_names", v_enum_names_);
381   generate_type_metadata("exception_names", v_exception_names_);
382 
383   hrl_footer(f_types_hrl_file_, string("BOGUS"));
384 
385   f_types_file_.close();
386   f_types_hrl_file_.close();
387   f_consts_file_.close();
388   f_consts_hrl_file_.close();
389 }
390 
emit_double_as_string(const double value)391 const std::string emit_double_as_string(const double value) {
392   std::stringstream double_output_stream;
393   // sets the maximum precision: http://en.cppreference.com/w/cpp/io/manip/setprecision
394   // sets the output format to fixed: http://en.cppreference.com/w/cpp/io/manip/fixed (not in scientific notation)
395   double_output_stream << std::setprecision(std::numeric_limits<double>::digits10 + 1);
396 
397   #ifdef _MSC_VER
398       // strtod is broken in MSVC compilers older than 2015, so std::fixed fails to format a double literal.
399       // more details: https://blogs.msdn.microsoft.com/vcblog/2014/06/18/
400       //               c-runtime-crt-features-fixes-and-breaking-changes-in-visual-studio-14-ctp1/
401       //               and
402       //               http://www.exploringbinary.com/visual-c-plus-plus-strtod-still-broken/
403       #if _MSC_VER >= MSC_2015_VER
404           double_output_stream << std::fixed;
405       #else
406           // note that if this function is called from the erlang generator and the MSVC compiler is older than 2015,
407           // the double literal must be output in the scientific format. There can be some cases where the
408           // mantissa of the output does not have fractionals, which is illegal in Erlang.
409           // example => 10000000000000000.0 being output as 1e+16
410           double_output_stream << std::scientific;
411       #endif
412   #else
413       double_output_stream << std::fixed;
414   #endif
415 
416   double_output_stream << value;
417 
418   return double_output_stream.str();
419 }
420 
generate_type_metadata(std::string function_name,vector<string> names)421 void t_erl_generator::generate_type_metadata(std::string function_name, vector<string> names) {
422   size_t num_structs = names.size();
423 
424   indent(f_types_file_) << function_name << "() ->\n";
425   indent_up();
426   indent(f_types_file_) << "[";
427 
428 
429   for(size_t i=0; i < num_structs; i++) {
430     f_types_file_ << names.at(i);
431 
432     if (i < num_structs - 1) {
433       f_types_file_ << ", ";
434     }
435   }
436 
437   f_types_file_ << "].\n\n";
438   indent_down();
439 }
440 
441 /**
442  * Generates a typedef. no op
443  *
444  * @param ttypedef The type definition
445  */
generate_typedef(t_typedef * ttypedef)446 void t_erl_generator::generate_typedef(t_typedef* ttypedef) {
447   (void)ttypedef;
448 }
449 
450 
generate_const_function(t_const * tconst,ostringstream & exports,ostringstream & functions)451 void t_erl_generator::generate_const_function(t_const* tconst, ostringstream& exports, ostringstream& functions) {
452   t_type* type = get_true_type(tconst->get_type());
453   string name = tconst->get_name();
454   t_const_value* value = tconst->get_value();
455 
456   if (type->is_map()) {
457     t_type* ktype = ((t_map*)type)->get_key_type();
458     t_type* vtype = ((t_map*)type)->get_val_type();
459     string const_fun_name = lowercase(name);
460 
461     // Emit const function export.
462     if (exports.tellp() > 0) { exports << ", "; }
463     exports << const_fun_name << "/1, " << const_fun_name << "/2";
464 
465     // Emit const function definition.
466     map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator i, end = value->get_map().end();
467     // The one-argument form throws an error if the key does not exist in the map.
468     for (i = value->get_map().begin(); i != end;) {
469       functions << const_fun_name << "(" << render_const_value(ktype, i->first) << ") -> "
470                 << render_const_value(vtype, i->second);
471       ++i;
472       functions << (i != end ? ";\n" : ".\n\n");
473     }
474 
475     // The two-argument form returns a default value if the key does not exist in the map.
476     for (i = value->get_map().begin(); i != end; ++i) {
477       functions << const_fun_name << "(" << render_const_value(ktype, i->first) << ", _) -> "
478                 << render_const_value(vtype, i->second) << ";\n";
479     }
480     functions << const_fun_name << "(_, Default) -> Default.\n\n";
481   } else if (type->is_list()) {
482     string const_fun_name = lowercase(name);
483 
484     if (exports.tellp() > 0) { exports << ", "; }
485     exports << const_fun_name << "/1, " << const_fun_name << "/2";
486 
487     size_t list_size = value->get_list().size();
488     string rendered_list = render_const_list_values(type, value);
489     functions << const_fun_name << "(N) when N >= 1, N =< " << list_size << " ->\n"
490               << indent_str() << "element(N, {" << rendered_list << "}).\n";
491     functions << const_fun_name << "(N, _) when N >= 1, N =< " << list_size << " ->\n"
492               << indent_str() << "element(N, {" << rendered_list << "});\n"
493               << const_fun_name << "(_, Default) -> Default.\n\n";
494     indent_down();
495   }
496 }
497 
generate_const_functions()498 void t_erl_generator::generate_const_functions() {
499   ostringstream exports;
500   ostringstream functions;
501   vector<t_const*>::iterator c_iter;
502   for (c_iter = v_consts_.begin(); c_iter != v_consts_.end(); ++c_iter) {
503     generate_const_function(*c_iter, exports, functions);
504   }
505   if (exports.tellp() > 0) {
506     f_consts_file_ << "-export([" << exports.str() << "]).\n\n"
507                    << functions.str();
508   }
509 }
510 
511 
512 /**
513  * Generates code for an enumerated type. Done using a class to scope
514  * the values.
515  *
516  * @param tenum The enumeration
517  */
generate_enum(t_enum * tenum)518 void t_erl_generator::generate_enum(t_enum* tenum) {
519   vector<t_enum_value*> constants = tenum->get_constants();
520   vector<t_enum_value*>::iterator c_iter;
521 
522   v_enums_.push_back(tenum);
523   v_enum_names_.push_back(atomify(tenum->get_name()));
524 
525   for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
526     int value = (*c_iter)->get_value();
527     string name = (*c_iter)->get_name();
528     indent(f_types_hrl_file_) << "-define(" << constify(make_safe_for_module_name(program_name_))
529                               << "_" << constify(tenum->get_name()) << "_" << constify(name) << ", "
530                               << value << ")." << endl;
531   }
532 
533   f_types_hrl_file_ << endl;
534 }
535 
generate_enum_info(t_enum * tenum)536 void t_erl_generator::generate_enum_info(t_enum* tenum){
537   vector<t_enum_value*> constants = tenum->get_constants();
538   size_t num_constants = constants.size();
539 
540   indent(f_types_file_) << "enum_info(" << atomify(tenum->get_name()) << ") ->\n";
541   indent_up();
542   indent(f_types_file_) << "[\n";
543 
544   for(size_t i=0; i < num_constants; i++) {
545     indent_up();
546     t_enum_value* value = constants.at(i);
547     indent(f_types_file_) << "{" << atomify(value->get_name()) << ", " << value->get_value() << "}";
548 
549     if (i < num_constants - 1) {
550       f_types_file_ << ",\n";
551     }
552     indent_down();
553   }
554   f_types_file_ << "\n";
555   indent(f_types_file_) << "];\n\n";
556   indent_down();
557 }
558 
generate_enum_metadata()559 void t_erl_generator::generate_enum_metadata() {
560   size_t enum_count = v_enums_.size();
561 
562   for(size_t i=0; i < enum_count; i++) {
563     t_enum* tenum = v_enums_.at(i);
564     generate_enum_info(tenum);
565   }
566 
567   indent(f_types_file_) << "enum_info(_) -> erlang:error(function_clause).\n\n";
568 }
569 
570 /**
571  * Generate a constant value
572  */
generate_const(t_const * tconst)573 void t_erl_generator::generate_const(t_const* tconst) {
574   t_type* type = tconst->get_type();
575   string name = tconst->get_name();
576   t_const_value* value = tconst->get_value();
577 
578   // Save the tconst so that function can be emitted in generate_const_functions().
579   v_consts_.push_back(tconst);
580 
581   f_consts_hrl_file_ << "-define(" << constify(make_safe_for_module_name(program_name_)) << "_"
582                      << constify(name) << ", " << render_const_value(type, value) << ")." << endl << endl;
583 }
584 
585 /**
586  * Prints the value of a constant with the given type. Note that type checking
587  * is NOT performed in this function as it is always run beforehand using the
588  * validate_types method in main.cc
589  */
render_const_value(t_type * type,t_const_value * value)590 string t_erl_generator::render_const_value(t_type* type, t_const_value* value) {
591   type = get_true_type(type);
592   std::ostringstream out;
593 
594   if (type->is_base_type()) {
595     t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
596     switch (tbase) {
597     case t_base_type::TYPE_STRING:
598       out << '"' << get_escaped_string(value) << '"';
599       break;
600     case t_base_type::TYPE_BOOL:
601       out << (value->get_integer() > 0 ? "true" : "false");
602       break;
603     case t_base_type::TYPE_I8:
604     case t_base_type::TYPE_I16:
605     case t_base_type::TYPE_I32:
606     case t_base_type::TYPE_I64:
607       out << value->get_integer();
608       break;
609     case t_base_type::TYPE_DOUBLE:
610       if (value->get_type() == t_const_value::CV_INTEGER) {
611         out << "float(" << value->get_integer() << ")";
612       } else {
613         out << emit_double_as_string(value->get_double());
614       }
615       break;
616     default:
617       throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase);
618     }
619   } else if (type->is_enum()) {
620     indent(out) << value->get_integer();
621 
622   } else if (type->is_struct() || type->is_xception()) {
623     out << "#" << type_name(type) << "{";
624     const vector<t_field*>& fields = ((t_struct*)type)->get_members();
625     vector<t_field*>::const_iterator f_iter;
626     const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map();
627     map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter;
628 
629     bool first = true;
630     for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
631       t_type* field_type = nullptr;
632       for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
633         if ((*f_iter)->get_name() == v_iter->first->get_string()) {
634           field_type = (*f_iter)->get_type();
635         }
636       }
637       if (field_type == nullptr) {
638         throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string();
639       }
640 
641       if (first) {
642         first = false;
643       } else {
644         out << ",";
645       }
646       out << v_iter->first->get_string();
647       out << " = ";
648       out << render_const_value(field_type, v_iter->second);
649     }
650     indent_down();
651     indent(out) << "}";
652 
653   } else if (type->is_map()) {
654     t_type* ktype = ((t_map*)type)->get_key_type();
655     t_type* vtype = ((t_map*)type)->get_val_type();
656 
657     if (maps_) {
658       out << "maps:from_list([";
659     } else {
660       out << "dict:from_list([";
661     }
662     map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator i, end = value->get_map().end();
663     for (i = value->get_map().begin(); i != end;) {
664       out << "{" << render_const_value(ktype, i->first) << ","
665           << render_const_value(vtype, i->second) << "}";
666       if (++i != end) {
667         out << ",";
668       }
669     }
670     out << "])";
671   } else if (type->is_set()) {
672     t_type* etype = ((t_set*)type)->get_elem_type();
673     out << "sets:from_list([";
674     vector<t_const_value*>::const_iterator i, end = value->get_list().end();
675     for (i = value->get_list().begin(); i != end;) {
676       out << render_const_value(etype, *i);
677       if (++i != end) {
678         out << ",";
679       }
680     }
681     out << "])";
682   } else if (type->is_list()) {
683     out << "[" << render_const_list_values(type, value) << "]";
684   } else {
685     throw "CANNOT GENERATE CONSTANT FOR TYPE: " + type->get_name();
686   }
687   return out.str();
688 }
689 
render_const_list_values(t_type * type,t_const_value * value)690 string t_erl_generator::render_const_list_values(t_type* type, t_const_value* value) {
691   std::ostringstream out;
692   t_type* etype = ((t_list*)type)->get_elem_type();
693 
694   bool first = true;
695   const vector<t_const_value*>& val = value->get_list();
696   vector<t_const_value*>::const_iterator v_iter;
697   for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
698     if (first) {
699       first = false;
700     } else {
701       out << ",";
702     }
703     out << render_const_value(etype, *v_iter);
704   }
705   return out.str();
706 }
707 
708 
render_default_value(t_field * field)709 string t_erl_generator::render_default_value(t_field* field) {
710   t_type* type = field->get_type();
711   if (type->is_struct() || type->is_xception()) {
712     return "#" + type_name(type) + "{}";
713   } else if (type->is_map()) {
714     if (maps_) {
715       return "#{}";
716     } else {
717       return "dict:new()";
718     }
719   } else if (type->is_set()) {
720     return "sets:new()";
721   } else if (type->is_list()) {
722     return "[]";
723   } else {
724     return "undefined";
725   }
726 }
727 
render_member_type(t_field * field)728 string t_erl_generator::render_member_type(t_field* field) {
729   t_type* type = get_true_type(field->get_type());
730   if (type->is_base_type()) {
731     t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
732     switch (tbase) {
733     case t_base_type::TYPE_STRING:
734       return "string() | binary()";
735     case t_base_type::TYPE_BOOL:
736       return "boolean()";
737     case t_base_type::TYPE_I8:
738     case t_base_type::TYPE_I16:
739     case t_base_type::TYPE_I32:
740     case t_base_type::TYPE_I64:
741       return "integer()";
742     case t_base_type::TYPE_DOUBLE:
743       return "float()";
744     default:
745       throw "compiler error: unsupported base type " + t_base_type::t_base_name(tbase);
746     }
747   } else if (type->is_enum()) {
748     return "integer()";
749   } else if (type->is_struct() || type->is_xception()) {
750     return type_name(type) + "()";
751   } else if (type->is_map()) {
752     if (maps_) {
753       return "map()";
754     } else {
755       return "dict:dict()";
756     }
757   } else if (type->is_set()) {
758       return "sets:set()";
759   } else if (type->is_list()) {
760     return "list()";
761   } else {
762     throw "compiler error: unsupported type " + type->get_name();
763   }
764 }
765 
render_member_requiredness(t_field * field)766 string t_erl_generator::render_member_requiredness(t_field* field) {
767   switch (field->get_req()) {
768   case t_field::T_REQUIRED:
769     return "required";
770   case t_field::T_OPTIONAL:
771     return "optional";
772   default:
773     return "undefined";
774   }
775 }
776 
777 /**
778  * Generates a struct
779  */
generate_struct(t_struct * tstruct)780 void t_erl_generator::generate_struct(t_struct* tstruct) {
781   v_struct_names_.push_back(type_name(tstruct));
782   generate_erl_struct(tstruct, false);
783 }
784 
785 /**
786  * Generates a struct definition for a thrift exception. Basically the same
787  * as a struct but extends the Exception class.
788  *
789  * @param txception The struct definition
790  */
generate_xception(t_struct * txception)791 void t_erl_generator::generate_xception(t_struct* txception) {
792   v_exception_names_.push_back(type_name(txception));
793   generate_erl_struct(txception, true);
794 }
795 
796 /**
797  * Generates a struct
798  */
generate_erl_struct(t_struct * tstruct,bool is_exception)799 void t_erl_generator::generate_erl_struct(t_struct* tstruct, bool is_exception) {
800   (void)is_exception;
801   generate_erl_struct_definition(f_types_hrl_file_, tstruct);
802   generate_erl_struct_info(f_info_, tstruct);
803   generate_erl_extended_struct_info(f_info_ext_, tstruct);
804 }
805 
806 /**
807  * Generates a struct definition for a thrift data type.
808  *
809  * @param tstruct The struct definition
810  */
generate_erl_struct_definition(ostream & out,t_struct * tstruct)811 void t_erl_generator::generate_erl_struct_definition(ostream& out, t_struct* tstruct) {
812   indent(out) << "%% struct " << type_name(tstruct) << endl << endl;
813 
814   std::stringstream buf;
815   buf << indent() << "-record(" << type_name(tstruct) << ", {";
816   string field_indent(buf.str().size(), ' ');
817 
818   const vector<t_field*>& members = tstruct->get_members();
819   for (vector<t_field*>::const_iterator m_iter = members.begin(); m_iter != members.end();) {
820     generate_erl_struct_member(buf, *m_iter);
821     if (++m_iter != members.end()) {
822       buf << "," << endl << field_indent;
823     }
824   }
825   buf << "}).";
826 
827   out << buf.str() << endl;
828   out << "-type " + type_name(tstruct) << "() :: #" + type_name(tstruct) + "{}." << endl << endl;
829 }
830 
831 /**
832  * Generates the record field definition
833  */
834 
generate_erl_struct_member(ostream & out,t_field * tmember)835 void t_erl_generator::generate_erl_struct_member(ostream& out, t_field* tmember) {
836   out << atomify(tmember->get_name());
837   if (has_default_value(tmember))
838     out << " = " << render_member_value(tmember);
839   out << " :: " << render_member_type(tmember);
840   if (tmember->get_req() != t_field::T_REQUIRED)
841     out << " | 'undefined'";
842 }
843 
has_default_value(t_field * field)844 bool t_erl_generator::has_default_value(t_field* field) {
845   t_type* type = field->get_type();
846   if (!field->get_value()) {
847     if (field->get_req() == t_field::T_REQUIRED) {
848       if (type->is_struct() || type->is_xception() || type->is_map() || type->is_set()
849           || type->is_list()) {
850         return true;
851       } else {
852         return false;
853       }
854     } else {
855       return false;
856     }
857   } else {
858     return true;
859   }
860 }
861 
render_member_value(t_field * field)862 string t_erl_generator::render_member_value(t_field* field) {
863   if (!field->get_value()) {
864     return render_default_value(field);
865   } else {
866     return render_const_value(field->get_type(), field->get_value());
867   }
868 }
869 
870 /**
871  * Generates the read method for a struct
872  */
generate_erl_struct_info(ostream & out,t_struct * tstruct)873 void t_erl_generator::generate_erl_struct_info(ostream& out, t_struct* tstruct) {
874   indent(out) << "struct_info(" << type_name(tstruct) << ") ->" << endl;
875   indent_up();
876   out << indent() << render_type_term(tstruct, true) << ";" << endl;
877   indent_down();
878   out << endl;
879 }
880 
generate_erl_extended_struct_info(ostream & out,t_struct * tstruct)881 void t_erl_generator::generate_erl_extended_struct_info(ostream& out, t_struct* tstruct) {
882   indent(out) << "struct_info_ext(" << type_name(tstruct) << ") ->" << endl;
883   indent_up();
884   out << indent() << render_type_term(tstruct, true, true) << ";" << endl;
885   indent_down();
886   out << endl;
887 }
888 
889 /**
890  * Generates a thrift service.
891  *
892  * @param tservice The service definition
893  */
generate_service(t_service * tservice)894 void t_erl_generator::generate_service(t_service* tservice) {
895   service_name_ = make_safe_for_module_name(service_name_);
896 
897   string f_service_hrl_name = get_out_dir() + service_name_ + "_thrift.hrl";
898   string f_service_name = get_out_dir() + service_name_ + "_thrift.erl";
899   f_service_file_.open(f_service_name.c_str());
900   f_service_hrl_.open(f_service_hrl_name.c_str());
901 
902   // Reset service text aggregating stream streams
903   f_service_.str("");
904   export_lines_.str("");
905   export_lines_first_ = true;
906 
907   hrl_header(f_service_hrl_, service_name_);
908 
909   if (tservice->get_extends() != nullptr) {
910     f_service_hrl_ << "-include(\""
911                    << make_safe_for_module_name(tservice->get_extends()->get_name())
912                    << "_thrift.hrl\"). % inherit " << endl;
913   }
914 
915   f_service_hrl_ << "-include(\"" << make_safe_for_module_name(program_name_) << "_types.hrl\")."
916                  << endl << endl;
917 
918   // Generate the three main parts of the service (well, two for now in PHP)
919   generate_service_helpers(tservice); // cpiro: New Erlang Order
920 
921   generate_service_interface(tservice);
922 
923   generate_service_metadata(tservice);
924 
925   // indent_down();
926 
927   f_service_file_ << erl_autogen_comment() << endl << "-module(" << service_name_ << "_thrift)."
928                   << endl << "-behaviour(thrift_service)." << endl << endl << erl_imports() << endl;
929 
930   f_service_file_ << "-include(\"" << make_safe_for_module_name(tservice->get_name())
931                   << "_thrift.hrl\")." << endl << endl;
932 
933   f_service_file_ << "-export([" << export_lines_.str() << "])." << endl << endl;
934 
935   f_service_file_ << f_service_.str();
936 
937   hrl_footer(f_service_hrl_, f_service_name);
938 
939   // Close service file
940   f_service_file_.close();
941   f_service_hrl_.close();
942 }
943 
generate_service_metadata(t_service * tservice)944 void t_erl_generator::generate_service_metadata(t_service* tservice) {
945   export_string("function_names", 0);
946   vector<t_function*> functions = tservice->get_functions();
947   size_t num_functions = functions.size();
948 
949   indent(f_service_) << "function_names() -> " << endl;
950   indent_up();
951   indent(f_service_) << "[";
952 
953   for (size_t i=0; i < num_functions; i++) {
954     t_function* current = functions.at(i);
955     f_service_ << atomify(current->get_name());
956     if (i < num_functions - 1) {
957       f_service_ << ", ";
958     }
959   }
960 
961   f_service_ << "].\n\n";
962   indent_down();
963 }
964 
965 /**
966  * Generates helper functions for a service.
967  *
968  * @param tservice The service to generate a header definition for
969  */
generate_service_helpers(t_service * tservice)970 void t_erl_generator::generate_service_helpers(t_service* tservice) {
971   vector<t_function*> functions = tservice->get_functions();
972   vector<t_function*>::iterator f_iter;
973 
974   //  indent(f_service_) <<
975   //  "% HELPER FUNCTIONS AND STRUCTURES" << endl << endl;
976 
977   export_string("struct_info", 1);
978 
979   for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
980     generate_erl_function_helpers(*f_iter);
981   }
982   f_service_ << "struct_info(_) -> erlang:error(function_clause)." << endl;
983 }
984 
985 /**
986  * Generates a struct and helpers for a function.
987  *
988  * @param tfunction The function
989  */
generate_erl_function_helpers(t_function * tfunction)990 void t_erl_generator::generate_erl_function_helpers(t_function* tfunction) {
991   (void)tfunction;
992 }
993 
994 /**
995  * Generates a service interface definition.
996  *
997  * @param tservice The service to generate a header definition for
998  */
generate_service_interface(t_service * tservice)999 void t_erl_generator::generate_service_interface(t_service* tservice) {
1000 
1001   export_string("function_info", 2);
1002 
1003   vector<t_function*> functions = tservice->get_functions();
1004   vector<t_function*>::iterator f_iter;
1005   f_service_ << "%%% interface" << endl;
1006   for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
1007     f_service_ << indent() << "% " << function_signature(*f_iter) << endl;
1008 
1009     generate_function_info(tservice, *f_iter);
1010   }
1011 
1012   // Inheritance - pass unknown functions to base class
1013   if (tservice->get_extends() != nullptr) {
1014     indent(f_service_) << "function_info(Function, InfoType) ->" << endl;
1015     indent_up();
1016     indent(f_service_) << make_safe_for_module_name(tservice->get_extends()->get_name())
1017                        << "_thrift:function_info(Function, InfoType)." << endl;
1018     indent_down();
1019   } else {
1020     // return function_clause error for non-existent functions
1021     indent(f_service_) << "function_info(_Func, _Info) -> erlang:error(function_clause)." << endl;
1022   }
1023 
1024   indent(f_service_) << endl;
1025 }
1026 
1027 /**
1028  * Generates a function_info(FunctionName, params_type) and
1029  * function_info(FunctionName, reply_type)
1030  */
generate_function_info(t_service * tservice,t_function * tfunction)1031 void t_erl_generator::generate_function_info(t_service* tservice, t_function* tfunction) {
1032   (void)tservice;
1033   string name_atom = atomify(tfunction->get_name());
1034 
1035   t_struct* xs = tfunction->get_xceptions();
1036   t_struct* arg_struct = tfunction->get_arglist();
1037 
1038   // function_info(Function, params_type):
1039   indent(f_service_) << "function_info(" << name_atom << ", params_type) ->" << endl;
1040   indent_up();
1041 
1042   indent(f_service_) << render_type_term(arg_struct, true) << ";" << endl;
1043 
1044   indent_down();
1045 
1046   // function_info(Function, reply_type):
1047   indent(f_service_) << "function_info(" << name_atom << ", reply_type) ->" << endl;
1048   indent_up();
1049 
1050   if (!tfunction->get_returntype()->is_void())
1051     indent(f_service_) << render_type_term(tfunction->get_returntype(), false) << ";" << endl;
1052   else if (tfunction->is_oneway())
1053     indent(f_service_) << "oneway_void;" << endl;
1054   else
1055     indent(f_service_) << "{struct, []}"
1056                        << ";" << endl;
1057   indent_down();
1058 
1059   // function_info(Function, exceptions):
1060   indent(f_service_) << "function_info(" << name_atom << ", exceptions) ->" << endl;
1061   indent_up();
1062   indent(f_service_) << render_type_term(xs, true) << ";" << endl;
1063   indent_down();
1064 }
1065 
1066 /**
1067  * Renders a function signature of the form 'type name(args)'
1068  *
1069  * @param tfunction Function definition
1070  * @return String of rendered function definition
1071  */
function_signature(t_function * tfunction,string prefix)1072 string t_erl_generator::function_signature(t_function* tfunction, string prefix) {
1073   return prefix + tfunction->get_name() + "(This"
1074          + capitalize(argument_list(tfunction->get_arglist())) + ")";
1075 }
1076 
1077 /**
1078  * Add a function to the exports list
1079  */
export_string(string name,int num)1080 void t_erl_generator::export_string(string name, int num) {
1081   if (export_lines_first_) {
1082     export_lines_first_ = false;
1083   } else {
1084     export_lines_ << ", ";
1085   }
1086   export_lines_ << name << "/" << num;
1087 }
1088 
export_types_string(string name,int num)1089 void t_erl_generator::export_types_string(string name, int num) {
1090   if (export_types_lines_first_) {
1091     export_types_lines_first_ = false;
1092   } else {
1093     export_types_lines_ << ", ";
1094   }
1095   export_types_lines_ << name << "/" << num;
1096 }
1097 
export_function(t_function * tfunction,string prefix)1098 void t_erl_generator::export_function(t_function* tfunction, string prefix) {
1099   t_struct::members_type::size_type num = tfunction->get_arglist()->get_members().size();
1100   if (num > static_cast<t_struct::members_type::size_type>(std::numeric_limits<int>().max())) {
1101     throw "integer overflow in t_erl_generator::export_function, name " + tfunction->get_name();
1102   }
1103   export_string(prefix + tfunction->get_name(),
1104                 1 // This
1105                 + static_cast<int>(num));
1106 }
1107 
1108 /**
1109  * Renders a field list
1110  */
argument_list(t_struct * tstruct)1111 string t_erl_generator::argument_list(t_struct* tstruct) {
1112   string result = "";
1113 
1114   const vector<t_field*>& fields = tstruct->get_members();
1115   vector<t_field*>::const_iterator f_iter;
1116   bool first = true;
1117   for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
1118     if (first) {
1119       first = false;
1120       result += ", "; // initial comma to compensate for initial This
1121     } else {
1122       result += ", ";
1123     }
1124     result += capitalize((*f_iter)->get_name());
1125   }
1126   return result;
1127 }
1128 
type_name(t_type * ttype)1129 string t_erl_generator::type_name(t_type* ttype) {
1130   string prefix = ttype->get_program()->get_namespace("erl");
1131   size_t prefix_length = prefix.length();
1132   if (prefix_length > 0 && prefix[prefix_length - 1] != '_') {
1133     size_t delimiter_length = delimiter_.length();
1134     if (delimiter_length > 0 && delimiter_length < prefix_length) {
1135         bool not_match = prefix.compare(prefix_length - delimiter_length, prefix_length, delimiter_) != 0;
1136         if (not_match) {
1137             prefix += delimiter_;
1138         }
1139     }
1140   }
1141 
1142   string name = ttype->get_name();
1143 
1144   return atomify(prefix + name);
1145 }
1146 
1147 /**
1148  * Converts the parse type to a Erlang "type" (macro for int constants)
1149  */
type_to_enum(t_type * type)1150 string t_erl_generator::type_to_enum(t_type* type) {
1151   type = get_true_type(type);
1152 
1153   if (type->is_base_type()) {
1154     t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
1155     switch (tbase) {
1156     case t_base_type::TYPE_VOID:
1157       throw "NO T_VOID CONSTRUCT";
1158     case t_base_type::TYPE_STRING:
1159       return "?tType_STRING";
1160     case t_base_type::TYPE_BOOL:
1161       return "?tType_BOOL";
1162     case t_base_type::TYPE_I8:
1163       return "?tType_I8";
1164     case t_base_type::TYPE_I16:
1165       return "?tType_I16";
1166     case t_base_type::TYPE_I32:
1167       return "?tType_I32";
1168     case t_base_type::TYPE_I64:
1169       return "?tType_I64";
1170     case t_base_type::TYPE_DOUBLE:
1171       return "?tType_DOUBLE";
1172     default:
1173       break;
1174     }
1175   } else if (type->is_enum()) {
1176     return "?tType_I32";
1177   } else if (type->is_struct() || type->is_xception()) {
1178     return "?tType_STRUCT";
1179   } else if (type->is_map()) {
1180     return "?tType_MAP";
1181   } else if (type->is_set()) {
1182     return "?tType_SET";
1183   } else if (type->is_list()) {
1184     return "?tType_LIST";
1185   }
1186 
1187   throw "INVALID TYPE IN type_to_enum: " + type->get_name();
1188 }
1189 
1190 /**
1191  * Generate an Erlang term which represents a thrift type
1192  */
render_type_term(t_type * type,bool expand_structs,bool extended_info)1193 std::string t_erl_generator::render_type_term(t_type* type,
1194                                               bool expand_structs,
1195                                               bool extended_info) {
1196   type = get_true_type(type);
1197 
1198   if (type->is_base_type()) {
1199     t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
1200     switch (tbase) {
1201     case t_base_type::TYPE_VOID:
1202       throw "NO T_VOID CONSTRUCT";
1203     case t_base_type::TYPE_STRING:
1204       return "string";
1205     case t_base_type::TYPE_BOOL:
1206       return "bool";
1207     case t_base_type::TYPE_I8:
1208       return "byte";
1209     case t_base_type::TYPE_I16:
1210       return "i16";
1211     case t_base_type::TYPE_I32:
1212       return "i32";
1213     case t_base_type::TYPE_I64:
1214       return "i64";
1215     case t_base_type::TYPE_DOUBLE:
1216       return "double";
1217     default:
1218       break;
1219     }
1220   } else if (type->is_enum()) {
1221     return "i32";
1222   } else if (type->is_struct() || type->is_xception()) {
1223     if (expand_structs) {
1224 
1225       std::stringstream buf;
1226       buf << "{struct, [";
1227       string field_indent(buf.str().size(), ' ');
1228 
1229       t_struct::members_type const& fields = static_cast<t_struct*>(type)->get_members();
1230       t_struct::members_type::const_iterator i, end = fields.end();
1231       for (i = fields.begin(); i != end;) {
1232         t_struct::members_type::value_type member = *i;
1233         int32_t key = member->get_key();
1234         string type = render_type_term(member->get_type(), false, false); // recursive call
1235 
1236         if (!extended_info) {
1237           // Convert to format: {struct, [{Fid, Type}|...]}
1238           buf << "{" << key << ", " << type << "}";
1239         } else {
1240           // Convert to format: {struct, [{Fid, Req, Type, Name, Def}|...]}
1241           string name = member->get_name();
1242           string value = render_member_value(member);
1243           string requiredness = render_member_requiredness(member);
1244           buf << "{" << key << ", " << requiredness << ", " << type << ", " << atomify(name) << ", "
1245               << value << "}";
1246         }
1247 
1248         if (++i != end) {
1249           buf << "," << endl << field_indent;
1250         }
1251       }
1252 
1253       buf << "]}" << endl;
1254       return buf.str();
1255     } else {
1256       return "{struct, {" + atomify(type_module(type)) + ", " + type_name(type) + "}}";
1257     }
1258   } else if (type->is_map()) {
1259     // {map, KeyType, ValType}
1260     t_type* key_type = ((t_map*)type)->get_key_type();
1261     t_type* val_type = ((t_map*)type)->get_val_type();
1262 
1263     return "{map, " + render_type_term(key_type, false) + ", " + render_type_term(val_type, false)
1264            + "}";
1265 
1266   } else if (type->is_set()) {
1267     t_type* elem_type = ((t_set*)type)->get_elem_type();
1268 
1269     return "{set, " + render_type_term(elem_type, false) + "}";
1270 
1271   } else if (type->is_list()) {
1272     t_type* elem_type = ((t_list*)type)->get_elem_type();
1273 
1274     return "{list, " + render_type_term(elem_type, false) + "}";
1275   }
1276 
1277   throw "INVALID TYPE IN type_to_enum: " + type->get_name();
1278 }
1279 
type_module(t_type * ttype)1280 std::string t_erl_generator::type_module(t_type* ttype) {
1281   return make_safe_for_module_name(ttype->get_program()->get_name()) + "_types";
1282 }
1283 
display_name() const1284 std::string t_erl_generator::display_name() const {
1285   return "Erlang";
1286 }
1287 
1288 
1289 THRIFT_REGISTER_GENERATOR(
1290     erl,
1291     "Erlang",
1292     "    legacynames:     Output files retain naming conventions of Thrift 0.9.1 and earlier.\n"
1293     "    delimiter=       Delimiter between namespace prefix and record name. Default is '.'.\n"
1294     "    app_prefix=      Application prefix for generated Erlang files.\n"
1295     "    maps:            Generate maps instead of dicts.\n")
1296