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