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