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