From 2f5afceeaa2050ce3cca477288729c30622a1d38 Mon Sep 17 00:00:00 2001 From: Christopher Piro Date: Fri, 29 Jun 2007 07:17:33 +0000 Subject: [PATCH] [thrift] preliminary Erlang support (initial import) Summary: * missing {list,map,set}s, inheritance is spotty * loose source code, plus everything is one process (application / gen_server behavior is forthcoming) * codegen is a mess, need t_fp_generator Test Plan: * codegen invoked without -erl generates identical code for test/ * calculatorHandler plus 'thrift -erl -r tutorial.thrift' more or less works Revert Plan: ok git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665146 13f79535-47bb-0310-9956-ffa450edef68 --- compiler/cpp/Makefile.am | 3 +- compiler/cpp/src/generate/t_erl_generator.cc | 1643 +++++++++++++++++ compiler/cpp/src/generate/t_erl_generator.h | 174 ++ compiler/cpp/src/main.cc | 14 +- .../src-loose/protocol/tBinaryProtocol.erl | 232 +++ .../src-loose/protocol/tBinaryProtocol.hrl | 4 + lib/erl/src-loose/protocol/tProtocol.erl | 69 + lib/erl/src-loose/protocol/tProtocol.hrl | 1 + .../src-loose/protocol/tProtocolException.erl | 9 + .../src-loose/protocol/tProtocolException.hrl | 8 + lib/erl/src-loose/server/tServer.erl | 62 + lib/erl/src-loose/server/tServer.hrl | 1 + lib/erl/src-loose/tApplicationException.erl | 64 + lib/erl/src-loose/tApplicationException.hrl | 9 + lib/erl/src-loose/tException.erl | 7 + lib/erl/src-loose/tException.hrl | 1 + lib/erl/src-loose/thrift.hrl | 36 + .../transport/tBufferedTransport.erl | 34 + .../transport/tBufferedTransport.hrl | 1 + lib/erl/src-loose/transport/tServerSocket.erl | 44 + lib/erl/src-loose/transport/tServerSocket.hrl | 1 + lib/erl/src-loose/transport/tSocket.erl | 96 + lib/erl/src-loose/transport/tSocket.hrl | 1 + lib/erl/src-loose/transport/tTransport.erl | 4 + .../transport/tTransportException.erl | 15 + .../transport/tTransportException.hrl | 7 + lib/erl/thrift | 1 + tutorial/erl/ErlClient.rb | 49 + tutorial/erl/calculatorHandler.erl | 68 + 29 files changed, 2656 insertions(+), 2 deletions(-) create mode 100644 compiler/cpp/src/generate/t_erl_generator.cc create mode 100644 compiler/cpp/src/generate/t_erl_generator.h create mode 100644 lib/erl/src-loose/protocol/tBinaryProtocol.erl create mode 100644 lib/erl/src-loose/protocol/tBinaryProtocol.hrl create mode 100644 lib/erl/src-loose/protocol/tProtocol.erl create mode 100644 lib/erl/src-loose/protocol/tProtocol.hrl create mode 100644 lib/erl/src-loose/protocol/tProtocolException.erl create mode 100644 lib/erl/src-loose/protocol/tProtocolException.hrl create mode 100644 lib/erl/src-loose/server/tServer.erl create mode 100644 lib/erl/src-loose/server/tServer.hrl create mode 100644 lib/erl/src-loose/tApplicationException.erl create mode 100644 lib/erl/src-loose/tApplicationException.hrl create mode 100644 lib/erl/src-loose/tException.erl create mode 100644 lib/erl/src-loose/tException.hrl create mode 100644 lib/erl/src-loose/thrift.hrl create mode 100644 lib/erl/src-loose/transport/tBufferedTransport.erl create mode 100644 lib/erl/src-loose/transport/tBufferedTransport.hrl create mode 100644 lib/erl/src-loose/transport/tServerSocket.erl create mode 100644 lib/erl/src-loose/transport/tServerSocket.hrl create mode 100644 lib/erl/src-loose/transport/tSocket.erl create mode 100644 lib/erl/src-loose/transport/tSocket.hrl create mode 100644 lib/erl/src-loose/transport/tTransport.erl create mode 100644 lib/erl/src-loose/transport/tTransportException.erl create mode 100644 lib/erl/src-loose/transport/tTransportException.hrl create mode 120000 lib/erl/thrift create mode 100644 tutorial/erl/ErlClient.rb create mode 100644 tutorial/erl/calculatorHandler.erl diff --git a/compiler/cpp/Makefile.am b/compiler/cpp/Makefile.am index d4c56fd6..4c7d15f5 100644 --- a/compiler/cpp/Makefile.am +++ b/compiler/cpp/Makefile.am @@ -14,7 +14,8 @@ thrift_SOURCES = src/thrifty.yy \ src/generate/t_py_generator.cc \ src/generate/t_rb_generator.cc \ src/generate/t_xsd_generator.cc \ - src/generate/t_perl_generator.cc + src/generate/t_perl_generator.cc \ + src/generate/t_erl_generator.cc thrift_CXXFLAGS = -Wall -Isrc thrift_LDFLAGS = -Wall diff --git a/compiler/cpp/src/generate/t_erl_generator.cc b/compiler/cpp/src/generate/t_erl_generator.cc new file mode 100644 index 00000000..ff2e2872 --- /dev/null +++ b/compiler/cpp/src/generate/t_erl_generator.cc @@ -0,0 +1,1643 @@ +// Copyright (c) 2006- Facebook +// Distributed under the Thrift Software License +// +// See accompanying file LICENSE or visit the Thrift site at: +// http://developers.facebook.com/thrift/ + +#include +#include +#include +#include +#include "t_erl_generator.h" +using namespace std; + + +void hrl_header(std::ostream& out, std::string name); +void hrl_footer(std::ostream& out, std::string name); + + +/** + * UI for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_erl_generator::init_generator() { + // Make output directory + mkdir(T_ERL_DIR, S_IREAD | S_IWRITE | S_IEXEC); + + export_types_lines_first_ = export_lines_first_ = true; + + // Make output file + string f_types_name = string(T_ERL_DIR)+"/"+program_name_+"_types.erl"; + string f_types_hrl_name = string(T_ERL_DIR)+"/"+program_name_+"_types.hrl"; + f_types_file_.open(f_types_name.c_str()); + f_types_hrl_file_.open(f_types_hrl_name.c_str()); + + hrl_header(f_types_hrl_file_, program_name_ + "_types"); + + string f_consts_name = string(T_ERL_DIR)+"/"+program_name_+"_constants.hrl"; + f_consts_.open(f_consts_name.c_str()); + + // Print header + f_types_file_ << + erl_autogen_comment() << endl << + "-module(" << uncapitalize(program_name_) << "_types)." << endl << + erl_imports() << endl; + + f_types_file_ << + "-include(\"" << uncapitalize(program_name_) << "_types.hrl\")." << endl << + endl; + + f_types_hrl_file_ << render_includes() << endl; + + f_consts_ << + erl_autogen_comment() << endl << + erl_imports() << endl << + "-include(\"" << program_name_ << "_types.hrl\"). % init_generator" << endl << + endl; +} + +void hrl_header(ostream& out, string name) { + out << "-ifndef(_" << name << "_included)." << endl << + "-define(_" << name << "_included, yeah)." << endl; +} + +void hrl_footer(ostream& out, string name) { + out << "-endif." << endl; +} + +/** + * Renders all the imports necessary for including another Thrift program + */ +string t_erl_generator::render_includes() { + const vector& includes = program_->get_includes(); + string result = ""; + for (size_t i = 0; i < includes.size(); ++i) { + result += "-include(\"" + includes[i]->get_name() + "_types.hrl\"). % render_includes\n"; + } + if (includes.size() > 0) { + result += "\n"; + } + return result; +} + +/** + * Autogen'd comment + */ +string t_erl_generator::erl_autogen_comment() { + return + std::string("%\n") + + "% Autogenerated by Thrift\n" + + "%\n" + + "% DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + + "%\n"; +} + +/** + * Prints standard thrift imports + */ +string t_erl_generator::erl_imports() { + return + string("-include(\"thrift/protocol/tProtocol.hrl\").\n") + + "-include(\"thrift/thrift.hrl\")."; +} + +/** + * Closes the type files + */ +void t_erl_generator::close_generator() { + // Close types file + f_types_file_ << "-export([" << export_types_lines_.str() << "])." << endl; + f_types_file_ << f_types_.str(); + + hrl_footer(f_types_hrl_file_, string("BOGUS")); + + f_types_file_.close(); + f_types_hrl_file_.close(); + f_consts_.close(); +} + +/** + * Generates a typedef. This is not done in Ruby, types are all implicit. + * + * @param ttypedef The type definition + */ +void t_erl_generator::generate_typedef(t_typedef* ttypedef) {} + +/** + * Generates code for an enumerated type. Done using a class to scope + * the values. + * + * @param tenum The enumeration + */ +void t_erl_generator::generate_enum(t_enum* tenum) { + //f_types_ << "module " << tenum->get_name() << endl; + //indent_up(); + + vector constants = tenum->get_constants(); + vector::iterator c_iter; + int value = -1; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + if ((*c_iter)->has_value()) { + value = (*c_iter)->get_value(); + } else { + ++value; + } + + // Ruby class constants have to be capitalized... omg i am so on the fence + // about languages strictly enforcing capitalization why can't we just all + // agree and play nice. + string name = capitalize((*c_iter)->get_name()); + + f_types_hrl_file_ << + indent() << "-define(" << name << ", " << value << ")."<< endl; + } + + // indent_down(); + f_types_hrl_file_ << endl; +} + +/** + * Generate a constant value + */ +void t_erl_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = tconst->get_name(); + t_const_value* value = tconst->get_value(); + + name[0] = toupper(name[0]); + + f_consts_ << "-define(" << name << ", " << render_const_value(type, value) << ")." << endl << endl; +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +string t_erl_generator::render_const_value(t_type* type, t_const_value* value) { + std::ostringstream out; + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << "\"" << value->get_string() << "\""; + break; + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "true" : "false"); + break; + case t_base_type::TYPE_BYTE: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + out << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + tbase; + } + } else if (type->is_enum()) { + indent(out) << value->get_integer(); + } else if (type->is_struct() || type->is_xception()) { // TODO + out << type->get_name() << "({" << endl; + indent_up(); + const vector& fields = ((t_struct*)type)->get_members(); + vector::const_iterator f_iter; + const map& val = value->get_map(); + map::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + out << indent(); + out << render_const_value(g_type_string, v_iter->first); + out << " => "; + out << render_const_value(field_type, v_iter->second); + out << "," << endl; + } + indent_down(); + indent(out) << "})"; + } else if (type->is_map()) { // TODO + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + out << "{" << endl; + indent_up(); + const map& val = value->get_map(); + map::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << indent(); + out << render_const_value(ktype, v_iter->first); + out << " => "; + out << render_const_value(vtype, v_iter->second); + out << "," << endl; + } + indent_down(); + indent(out) << "}"; + } else if (type->is_list() || type->is_set()) { // TODO + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + if (type->is_set()) { + out << "{"; + } else { + out << "[" << endl; + } + indent_up(); + const vector& val = value->get_list(); + vector::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << indent(); + out << render_const_value(etype, *v_iter); + if (type->is_set()) { + out << " => true"; + } + out << "," << endl; + } + indent_down(); + if (type->is_set()) { + indent(out) << "}"; + } else { + indent(out) << "]"; + } + } + return out.str(); +} + +/** + * Generates a ruby struct + */ +void t_erl_generator::generate_struct(t_struct* tstruct) { + generate_erl_struct(tstruct, false); +} + +/** + * Generates a struct definition for a thrift exception. Basically the same + * as a struct but extends the Exception class. + * + * @param txception The struct definition + */ +void t_erl_generator::generate_xception(t_struct* txception) { + generate_erl_struct(txception, true); +} + +/** + * Generates a ruby struct + */ +void t_erl_generator::generate_erl_struct(t_struct* tstruct, + bool is_exception) { + generate_erl_struct_definition(f_types_, f_types_hrl_file_, tstruct, is_exception); +} + +/** + * Generates a struct definition for a thrift data type. This is nothing in PHP + * where the objects are all just associative arrays (unless of course we + * decide to start using objects for them...) + * + * @param tstruct The struct definition + */ +void t_erl_generator::generate_erl_struct_definition(ostream& out, + ostream& hrl_out, + t_struct* tstruct, + bool is_exception, + bool is_result) { + const vector& members = tstruct->get_members(); + vector::const_iterator m_iter; + + // indent_up(); // indent removeme + + indent(out) << "%% struct " << type_name(tstruct) << endl; + + if (is_exception) { // TODO + out << "% RUBY( < StandardError) % TODO"; + } + out << endl; + // indent_up(); + +/// out << endl; + if (members.size() > 0) { + indent(out) << "% -record(" << uncapitalize(type_name(tstruct)) << ", {"; + indent(hrl_out) << "-record(" << uncapitalize(type_name(tstruct)) << ", {"; + + bool first = true; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (first) { + first = false; + } else { + out << ", "; + hrl_out << ", "; + } + out << (*m_iter)->get_name(); + hrl_out << (*m_iter)->get_name(); + } + out << "})." << endl; + hrl_out << "})." << endl; + } else { // no members; explicit comment + indent(out) << "% -record(" << uncapitalize(type_name(tstruct)) << ", {}). % empty struct" << endl; + indent(hrl_out) << "-record(" << uncapitalize(type_name(tstruct)) << ", {}). % empty struct" << endl; + } + + // no constructors : use #myStruct{} and #myStruct{field=Val} + +/// out << +/// indent() << "" << uncapitalize(type_name(tstruct)) << "(d=nil)" << endl; +/// indent_up(); +/// +/// if (members.size() > 0) { +/// for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { +/// if ((*m_iter)->get_value() != NULL) { +/// indent(out) << declare_field(*m_iter) << endl; +/// } +/// } +/// indent(out) << +/// "if (d != nil)" << endl; +/// indent_up(); +/// for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { +/// out << +/// indent() << "if (d.has_key?('" << (*m_iter)->get_name() << "'))" << endl << +/// indent() << " @" << (*m_iter)->get_name() << " = d['" << (*m_iter)->get_name() << "']" << endl << +/// indent() << "end" << endl; +/// } +/// indent_down(); +/// indent(out) << "end" << endl; +/// } +/// +/// indent_down(); +/// indent(out) << "end" << endl; +/// +/// out << endl; +/// + + out << endl; + hrl_out << endl; + + + generate_erl_struct_reader(out, tstruct); + generate_erl_struct_writer(out, tstruct); + + //indent_down(); + // indent(out) << "end" << endl << endl; +} + +/** + * Generates the read method for a struct + */ +void t_erl_generator::generate_erl_struct_reader(ostream& out, + t_struct* tstruct) { + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + + string name = uncapitalize(type_name(tstruct)) + "_read"; + + if(out == f_types_) { // TODO(cpiro): omfg please ftlog do this right + export_types_string(name, 1); + } else { + export_string(name, 1); + } + + indent(out) << name << "(Iprot) ->" << endl; + indent_up(); + + out << + indent() << "?M0(Iprot, readStructBegin)," << endl << + indent() << "Str = " << uncapitalize(type_name(tstruct)) << "_read_loop(Iprot, "; + + // if (fields.size() > 0) { // cpiro: sensible default for non-empty structs + out << "#" << uncapitalize(type_name(tstruct)) << "{}"; + // } else { + // out << "empty_struct"; + // } + + out << ")," << endl << + indent() << "?M0(Iprot, readStructEnd)," << endl << + indent() << "Str." << endl; + + indent_down(); + + indent(out) << + "" << uncapitalize(type_name(tstruct)) << "_read_loop(Iprot, Str) ->" << endl; + indent_up(); + + // Read beginning field marker + indent(out) << + "{ Fname, Ftype, Fid } = ?M0(Iprot, readFieldBegin)," << endl << + indent() << "Fid, Fname, % suppress unused warnings" << endl; + + // Check for field STOP marker and break + indent(out) << "if" << endl; + indent_up(); + indent(out) << "Ftype == ?tType_STOP ->" << endl << + indent() << " Str;" << endl; + + // indent() << "true ->" << endl; + + // if (fields.size() > 0) { + // indent(out) << + // "end" << endl; + // } + + // Switch statement on the field we are reading + //bool first = true; + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + // if (first) { + // first = false; + // out << + // indent() << "if "; + // } else { + // out << + // indent() << "elsif "; + // } + out << indent() << "(Fid == " << (*f_iter)->get_key() << ") and (Ftype == " + << type_to_enum((*f_iter)->get_type()) << ") ->" << endl; + + indent_up(); + generate_deserialize_field(out, *f_iter, "Val"); + + out << indent() << "?M0(Iprot, readFieldEnd)," << endl + << indent() << uncapitalize(type_name(tstruct)) << "_read_loop(Iprot, " + << "Str#" << uncapitalize(type_name(tstruct)) + << "{" << (*f_iter)->get_name() + << "=Val});" << endl; + indent_down(); + } + + // In the default case we skip the field + out << + indent() << "true -> " << endl << + indent() << " ?M1(Iprot, skip, Ftype)," << endl << + indent() << " ?M0(Iprot, readFieldEnd)," << endl << + indent() << " " << uncapitalize(type_name(tstruct)) << "_read_loop(Iprot, Str)" << endl; + indent_down(); + indent(out) << "end." << endl; + + // Read field end marker + // indent(out) << + //"iprot.readFieldEnd()" <get_name(); + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + + string fname = uncapitalize(type_name(tstruct)) + "_write"; + + if(out == f_types_) { // TODO(cpiro): omfg please ftlog do this right + export_types_string(fname, 2); + } else { + export_string(fname, 2); + } + + indent(out) << fname << "(Str, Oprot) -> %xy" << endl; + indent_up(); + + indent(out) << + "Str, % suppress unused warnings" << endl << + indent() << "?M1(Oprot, writeStructBegin, \"" << name << "\")," << endl; + + + string prefix = string("Str#") + uncapitalize(type_name(tstruct)) + "."; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + // Write field header + indent(out) << + "if " << prefix << (*f_iter)->get_name() << " /= undefined ->" << endl; + indent_up(); + indent(out) << + "?M3(Oprot, writeFieldBegin, " << + "\"" << (*f_iter)->get_name() << "\", " << + type_to_enum((*f_iter)->get_type()) << ", " << + (*f_iter)->get_key() << ")," << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, prefix); + + // Write field closer + indent(out) << + "?M0(Oprot, writeFieldEnd);" << endl << + indent() << "true -> ok" << endl; + + indent_down(); + out << " end," << endl; + } + + // Write the struct map + out << + indent() << "?M0(Oprot, writeFieldStop)," << endl << + indent() << "?M0(Oprot, writeStructEnd)," << endl << + indent() << "ok." << endl; + + indent_down(); + + out << endl; +} + +/** + * Generates a thrift service. + * + * @param tservice The service definition + */ +void t_erl_generator::generate_service(t_service* tservice) { + string f_service_hrl_name = string(T_ERL_DIR)+"/"+uncapitalize(service_name_)+".hrl"; + string f_service_name = string(T_ERL_DIR)+"/"+uncapitalize(service_name_)+".erl"; + f_service_file_.open(f_service_name.c_str()); + f_service_hrl_.open(f_service_hrl_name.c_str()); + + hrl_header(f_service_hrl_, uncapitalize(service_name_)); + + if (tservice->get_extends() != NULL) { + f_service_hrl_ << "-include(\"" << + uncapitalize(tservice->get_extends()->get_name()) << ".hrl\"). % inherit " << endl; + } + + f_service_hrl_ << + "-include(\"" << uncapitalize(program_name_) << "_types.hrl\")." << endl << + endl; + + // Generate the three main parts of the service (well, two for now in PHP) + generate_service_helpers(tservice); // cpiro: New Erlang Order + + generate_service_interface(tservice); + generate_service_client(tservice); + generate_service_server(tservice); + + // indent_down(); + + f_service_file_ << + erl_autogen_comment() << endl << + "-module(" << uncapitalize(service_name_) << ")." << endl << endl << + erl_imports() << endl; + + f_service_file_ << "-include(\"" << uncapitalize(tservice->get_name()) << ".hrl\")." << endl << endl; + + f_service_file_ << "-export([" << export_lines_.str() << "])."; + + f_service_file_ << f_service_.str(); + + hrl_footer(f_service_hrl_, f_service_name); + + // Close service file + f_service_file_.close(); + f_service_hrl_.close(); +} + +/** + * Generates helper functions for a service. + * + * @param tservice The service to generate a header definition for + */ +void t_erl_generator::generate_service_helpers(t_service* tservice) { + vector functions = tservice->get_functions(); + vector::iterator f_iter; + + indent(f_service_) << + "% HELPER FUNCTIONS AND STRUCTURES" << endl << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_erl_struct_definition(f_service_, f_service_hrl_, ts, false); + generate_erl_function_helpers(*f_iter); + } +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_erl_generator::generate_erl_function_helpers(t_function* tfunction) { + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + t_struct* xs = tfunction->get_xceptions(); + const vector& fields = xs->get_members(); + vector::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + generate_erl_struct_definition(f_service_, f_service_hrl_, &result, false, true); +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_erl_generator::generate_service_interface(t_service* tservice) { + // f_service_ << + // indent() << "module Iface" << endl; + // indent_up(); + + // if (tservice->get_extends() != NULL) { + // string extends = type_name(tservice->get_extends()); + // indent(f_service_) << "include " << extends << "::Iface" << endl; + // } + + vector functions = tservice->get_functions(); + vector::iterator f_iter; + f_service_ << "%%% interface" << endl; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << + indent() << "% " << function_signature(*f_iter) << endl; + } + // indent_down(); + indent(f_service_) << endl << endl; +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_erl_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + // if (tservice->get_extends() != NULL) { + // extends = type_name(tservice->get_extends()); + // extends_client = " < " + extends + "::Client "; + // } + + // indent(f_service_) << + // "class Client" << extends_client << endl; + // indent_up(); + + // indent(f_service_) << + // "include Iface" << endl << endl; + + // Constructor function + export_string("new", 2); + export_string("new", 1); + + f_service_ << + indent() << "new(Iprot, Oprot) -> % generate_service_client" << endl << + indent() << " #"<" << endl << + indent() << " #"< functions = tservice->get_functions(); + vector::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* arg_struct = (*f_iter)->get_arglist(); + const vector& fields = arg_struct->get_members(); + vector::const_iterator fld_iter; + string funname = (*f_iter)->get_name(); + + export_function(*f_iter); + + // Open function + indent(f_service_) << + function_signature(*f_iter) << " -> % generate_service_client (call)" << endl; + + indent_up(); + + indent(f_service_) << + "send_" << funname << "(This"; + + //bool first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + // if (first) { + // first = false; + // } else { + f_service_ << ", "; + // } + f_service_ << capitalize((*fld_iter)->get_name()); + } + f_service_ << ")," << endl; + + if (!(*f_iter)->is_async()) { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << ""; + } + f_service_ << + "recv_" << funname << "(This), " << endl; + } + + indent(f_service_) << "ok." << endl; + indent_down(); + f_service_ << endl; + + export_function(*f_iter, "send_"); + + indent(f_service_) << + "send_" << function_signature(*f_iter) << " -> % generate_service_client (send)" << endl; + indent_up(); + + std::string argsname = capitalize((*f_iter)->get_name() + "_args"); + + // Serialize the request header + f_service_ << + indent() << "Oprot = ?ATTR(oprot)," << endl << + indent() << "?M3(Oprot, writeMessageBegin, \"" << (*f_iter)->get_name() << "\", ?tMessageType_CALL, ?ATTR(seqid))," << endl << + indent() << "Args = #" << (*f_iter)->get_name() << "_args{"; + + bool first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << (first ? first = false, "" : ", ") + << (*fld_iter)->get_name() + << "=" << capitalize((*fld_iter)->get_name()); + } + f_service_ << "}," << endl; + + indent(f_service_) << (*f_iter)->get_name() << "_args_write(Args, Oprot)," << endl; + + // Write to the stream + f_service_ << + indent() << "?M0(Oprot, writeMessageEnd)," << endl << + indent() << "%% side-effect: not gonna happen" << endl << + indent() << "%% ?M0(?M0(Oprot, trans), flush)," << endl << + indent() << "ok." << endl; + + indent_down(); + + if (!(*f_iter)->is_async()) { + std::string resultname = uncapitalize((*f_iter)->get_name() + "_result"); + t_struct noargs(program_); + + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs); + + export_function(&recv_function, ""); + + // Open function + f_service_ << + endl << + indent() << function_signature(&recv_function) << " -> % generate_service_client (recv)" << endl; + indent_up(); + + // TODO(mcslee): Validate message reply here, seq ids etc. + // TODO(cpiro): actually raise an Erlang exception? + + f_service_ << + indent() << "Iprot = ?ATTR(iprot)," << endl << + indent() << "{ Fname, Mtype, Rseqid } = ?M0(Iprot, readMessageBegin)," << endl << + indent() << "Fname, Rseqid, % suppress unused warnings" << endl << + indent() << "if" << endl << + indent() << " Mtype == ?tMessageType_EXCEPTION ->" << endl << + indent() << " X = tApplicationException:new()," << endl << + indent() << " ?M1(X, read, Iprot), " << endl << + indent() << " ?M0(Iprot, readMessageEnd), " << endl << + indent() << " {error, X};" << endl << + indent() << " true ->" << endl << + indent() << " Result = " << resultname << "_read(Iprot)," << endl << + indent() << " ?M0(Iprot, readMessageEnd)," << endl << + indent() << " if % time to figure out retval" << endl; + + // indent() << " {ok, Success} % result.success casing? Success even for void?" << endl << + // indent() << "end" << endl; + + // WATCH cpiro + // Careful, only return _result if not a void function + + std::string result = "Result#"+resultname+"."; + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << + indent() << " " << result << "success /= nil ->" << endl << + indent() << " {ok, " << result << "success};" << endl; + } + + t_struct* xs = (*f_iter)->get_xceptions(); // TODO(cpiro) + const std::vector& xceptions = xs->get_members(); + vector::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << + indent() << " " << result << (*x_iter)->get_name() << " /= nil -> " << endl << + indent() << " {error, " << result << (*x_iter)->get_name() << "};" << endl; + } + + // Careful, only return _result if not a void function + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << + indent() << " Result, % suppress unused warnings" << endl << + indent() << " true -> {ok, nil}" << endl << + indent() << " end" << endl; + } else { + f_service_ << + indent() << " true -> " << endl << + indent() << " {error, tApplicationException:new(?tApplicationException_MISSING_RESULT, \"" << (*f_iter)->get_name() << " failed: unknown result\")}" << endl << + indent() << " end" << endl; + } + + // Close function + indent(f_service_) << "end." << endl << endl; + indent_down(); + } + } + + indent_down(); + indent(f_service_) << endl; +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_erl_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector functions = tservice->get_functions(); + vector::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_processor = " RUBY(< " + extends + "::Processor) "; // TODO(cpiro) + } + + // Generate the header portion + indent(f_service_) << + "%% processor" << extends_processor << endl; + + indent_up(); + + // f_service_ << + // indent() << "include Iface" << endl << + // indent() << "include TProcessor" << endl << + // endl; + + /* + indent(f_service_) << + "def initialize(handler)" << endl; + indent_up(); + if (extends.empty()) { + f_service_ << + indent() << "@handler = handler" << endl << + indent() << "@processMap = {}" << endl; + } else { + f_service_ << + indent() << "super(handler)" << endl; + } + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << + indent() << "@processMap['" << (*f_iter)->get_name() << "'] = method(:process_" << (*f_iter)->get_name() << ")" << endl; + } + indent_down(); + indent(f_service_) << "end" << endl << endl; +*/ + indent(f_service_) << + "% initialize wuz here" << endl << endl; + + export_string("process", 3); + + // Generate the server implementation + indent(f_service_) << + "process(HandlerModule, Iprot, Oprot) ->" << endl; + indent_up(); + + f_service_ << + indent() << "{ Name, Type, Seqid } = ?M0(Iprot, readMessageBegin)," << endl << + indent() << "Type, % suppress unused warnings" << endl; + + // TODO(mcslee): validate message + + // HOT: dictionary function lookup + f_service_ << + // try to dispatch to one of our process_* + indent() << "case Name of" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << + indent() << " \"" << (*f_iter)->get_name() << "\" -> process_" << (*f_iter)->get_name() << "(HandlerModule, Seqid, Iprot, Oprot);" << endl; + } + + indent(f_service_) << " %% TODO(cpiro): pass to super" << endl; + indent(f_service_) << " _ -> % unknown function" << endl << + indent() << " ?M1(Iprot, skip, ?tType_STRUCT)," << endl << + indent() << " ?M0(Iprot, readMessageEnd)," << endl << + indent() << " X = tApplicationException:new(?tApplicationException_UNKNOWN_METHOD, \"Unknown function \" ++ Name)," << endl << + indent() << " ?M3(Oprot, writeMessageBegin, Name, ?tMessageType_EXCEPTION, Seqid)," << endl << + indent() << " ?M1(X, write, Oprot)," << endl << + indent() << " ?M0(Oprot, writeMessageEnd)," << endl << + indent() << " %% side-effect: not gonna happen" << endl << + indent() << " %% ?M0(?M0(Oprot, trans), flush)," << endl << + indent() << " {error, X} % what's the retval in this case?" << endl << + indent() << "end." << endl; + // indent() << "ok. % this one?" << endl << endl; + /* + indent() << "if (@processMap.has_key?(name))" << endl << + indent() << " @processMap[name].call(seqid, iprot, oprot)" << endl << + indent() << "else" << endl << + indent() << " iprot.skip(TType::STRUCT)" << endl << + indent() << " iprot.readMessageEnd()" << endl << + indent() << " x = TApplicationException.new(TApplicationException::UNKNOWN_METHOD, 'Unknown function '+name)" << endl << + indent() << " oprot.writeMessageBegin(name, TMessageType::EXCEPTION, seqid)" << endl << + indent() << " x.write(oprot)" << endl << + indent() << " oprot.writeMessageEnd()" << endl << + indent() << " oprot.trans.flush()" << endl << + indent() << " return" << endl << + indent() << "end" << endl; + */ + + // Read end of args field, the T_STOP, and the struct close + // f_service_ << + // indent() << "return true" << endl; + + indent_down(); + // indent(f_service_) << "end" << endl << endl; + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + indent_down(); + indent(f_service_) << endl << endl; +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_erl_generator::generate_process_function(t_service* tservice, + t_function* tfunction) { // TODO(cpiro) + + string name = "process_" + tfunction->get_name(); + + export_string(name, 4); + + // Open function + indent(f_service_) << + name << + "(HandlerModule, Seqid, Iprot, Oprot) ->" << endl; + indent_up(); + + string argsname = tfunction->get_name() + "_args"; + string resultname = tfunction->get_name() + "_result"; + + f_service_ << + indent() << "Args = " << argsname << "_read(Iprot)," << endl << + indent() << "Args, Seqid, Oprot, % suppress unused warnings" << endl << + indent() << "?M0(Iprot, readMessageEnd)," << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector& xceptions = xs->get_members(); + vector::const_iterator x_iter; + + // Declare result for non async function + if (!tfunction->is_async()) { + } + + + // Try block for a function with exceptions + // if (xceptions.size() > 0) { // TODO(cpiro) + // f_service_ << + // indent() << "try" << endl; + // indent_up(); + // } + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector& fields = arg_struct->get_members(); + vector::const_iterator f_iter; + + f_service_ << indent(); + f_service_ << + "Retval = HandlerModule:" << tfunction->get_name() << "("; + + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "Args#" << tfunction->get_name() << "_args." << (*f_iter)->get_name(); + } + f_service_ << ")," << endl; + + indent(f_service_) << "Result = case Retval of" << endl; + indent_up(); + indent(f_service_) << "{ok, Value} ->" << endl; + indent_up(); + indent(f_service_) << "Value, % suppress unused warnings" << endl; + + if (!tfunction->is_async() && !tfunction->get_returntype()->is_void()) { + indent(f_service_) << "#" << resultname << "{success=Value};" << endl; + } else{ + indent(f_service_) << "#" << resultname << "{};" << endl; + } + indent_down(); + + if (!tfunction->is_async() && xceptions.size() > 0) { + indent(f_service_) << "{error, E} ->" << endl; + indent_up(); + indent(f_service_) << "if" << endl; + + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << ""; + // indent() << "{error, ?" << (*x_iter)->get_type()->get_name() << "} -> " << "% TODO investigate this" << endl; + + //"" << (*x_iter)->get_type()->get_name() << "} -> " << "% TODO investigate this" << endl; + + if (!tfunction->is_async()) { + indent_up(); + f_service_ << + indent() << "is_record(E, " << uncapitalize((*x_iter)->get_type()->get_name()) << ") -> #" << + resultname << "{" << (*x_iter)->get_name() << " = E};" << endl; + // RUBY(Result.oops = + indent_down(); + } + indent_up(); + indent(f_service_) << "true -> throw(cpiro_if_nomatch) % TODO(cpiro): spirit foul" << endl; + indent_down(); + } + indent(f_service_) << "end;" << endl; + indent_down(); + } + indent(f_service_) << "dummy -> throw(cpiro_case_nomatch) % TODO(cpiro): gross" << endl; + indent_down(); + indent(f_service_) << "end," << endl; + + //indent_down(); + + if (tfunction->is_async()) { + indent(f_service_) << "% async" << endl; + } else { + f_service_ << + indent() << "?M3(Oprot, writeMessageBegin, \"" << tfunction->get_name() << "\", ?tMessageType_REPLY, Seqid)," << endl << + indent() << tfunction->get_name() << "_result_write(Result, Oprot)," << endl << + indent() << "?M0(Oprot, writeMessageEnd)," << endl << + indent() << "%% side-effect: not gonna happen" << endl << + indent() << "%% ?M0(?M0(Oprot, trans), flush)," << endl; + } + + indent(f_service_) << "Result." << endl << endl; + + // Close function + indent_down(); + // indent(f_service_); +} + +/** + * Deserializes a field of any type. + */ +void t_erl_generator::generate_deserialize_field(ostream &out, + t_field* tfield, + string prefix, + bool inclass) { + t_type* type = tfield->get_type(); + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + + prefix + tfield->get_name(); + } + + string name = prefix; //+ tfield->get_name(); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, + (t_struct*)type, + name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << + // name << " = iprot."; + name << " = ?M0(Iprot, "; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + + name; + break; + case t_base_type::TYPE_STRING: + out << "readString"; + break; + case t_base_type::TYPE_BOOL: + out << "readBool"; + break; + case t_base_type::TYPE_BYTE: + out << "readByte"; + break; + case t_base_type::TYPE_I16: + out << "readI16"; + break; + case t_base_type::TYPE_I32: + out << "readI32"; + break; + case t_base_type::TYPE_I64: + out << "readI64"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble"; + break; + default: + throw "compiler error: no PHP name for base type " + tbase; + } + } else if (type->is_enum()) { + out << "readI32"; + } + out << "), % generate_deserialize_field" << endl; + + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), type->get_name().c_str()); + } +} + +/** + * Generates an unserializer for a struct, calling read() + */ + void t_erl_generator::generate_deserialize_struct(ostream &out, // TODO + t_struct* tstruct, + string prefix) { + out << + // indent() << prefix << " = " << type_name(tstruct) << ":new()" << endl << + // indent() << prefix << ".read(iprot)" << endl; + indent() << prefix << " = " << (tstruct->get_program())->get_name() << "_types:" << uncapitalize(type_name(tstruct)) << "_read(Iprot), % generate_deserialize_struct" << endl; +} + +/** + * Serialize a container by writing out the header followed by + * data and then a footer. + */ +void t_erl_generator::generate_deserialize_container(ostream &out, // TODO + t_type* ttype, + string prefix) { + string size = tmp("_size"); + string ktype = tmp("_ktype"); + string vtype = tmp("_vtype"); + string etype = tmp("_etype"); + + t_field fsize(g_type_i32, size); + t_field fktype(g_type_byte, ktype); + t_field fvtype(g_type_byte, vtype); + t_field fetype(g_type_byte, etype); + + // Declare variables, read header + if (ttype->is_map()) { + out << + indent() << prefix << " = {}" << endl << + indent() << "(" << ktype << ", " << vtype << ", " << size << " ) = iprot.readMapBegin() " << endl; + } else if (ttype->is_set()) { + out << + indent() << prefix << " = {}" << endl << + indent() << "(" << etype << ", " << size << ") = iprot.readSetBegin()" << endl; + } else if (ttype->is_list()) { + out << + indent() << prefix << " = []" << endl << + indent() << "(" << etype << ", " << size << ") = iprot.readListBegin()" << endl; + } + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << + "for " << i << " in (1.." << size << ")" << endl; + + indent_up(); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + indent_down(); + indent(out) << "end" << endl; + + // Read container end + if (ttype->is_map()) { + indent(out) << "iprot.readMapEnd()" << endl; + } else if (ttype->is_set()) { + indent(out) << "iprot.readSetEnd()" << endl; + } else if (ttype->is_list()) { + indent(out) << "iprot.readListEnd()" << endl; + } +} + + +/** + * Generates code to deserialize a map + */ +void t_erl_generator::generate_deserialize_map_element(ostream &out, // TODO + t_map* tmap, + string prefix) { + string key = tmp("_key"); + string val = tmp("_val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << + prefix << "[" << key << "] = " << val << endl; +} + +/** + * Write a set element + */ +void t_erl_generator::generate_deserialize_set_element(ostream &out, // TODO + t_set* tset, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + generate_deserialize_field(out, &felem); + + indent(out) << + prefix << "[" << elem << "] = true" << endl; +} + +/** + * Write a list element + */ +void t_erl_generator::generate_deserialize_list_element(ostream &out, + t_list* tlist, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + generate_deserialize_field(out, &felem); + + indent(out) << + prefix << ".push(" << elem << ")" << endl; +} + + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_erl_generator::generate_serialize_field(ostream &out, + t_field* tfield, + string prefix) { + t_type* type = tfield->get_type(); + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + + prefix + tfield->get_name(); + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, + (t_struct*)type, + prefix + tfield->get_name()); + } else if (type->is_container()) { + generate_serialize_container(out, + type, + prefix + tfield->get_name()); + } else if (type->is_base_type() || type->is_enum()) { + + string name = prefix + tfield->get_name(); + + indent(out) << + "?M1(Oprot, "; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw + "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + out << "writeString, " << name << "),"; + break; + case t_base_type::TYPE_BOOL: + out << "writeBool, " << name << "),"; + break; + case t_base_type::TYPE_BYTE: + out << "writeByte, " << name << "),"; + break; + case t_base_type::TYPE_I16: + out << "writeI16, " << name << "),"; + break; + case t_base_type::TYPE_I32: + out << "writeI32, " << name << "),"; + break; + case t_base_type::TYPE_I64: + out << "writeI64, " << name << "),"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble, " << name << "),"; + break; + default: + throw "compiler error: no PHP name for base type " + tbase; + } + } else if (type->is_enum()) { + out << "writeI32, " << name << "),"; + } + out << " % generate_serialize_field" << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type->get_name().c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_erl_generator::generate_serialize_struct(ostream &out, + t_struct* tstruct, + string prefix) { + indent(out) << tstruct->get_program()->get_name() << "_types:" << uncapitalize(tstruct->get_name()) << "_write(" << prefix << ", Oprot), % generate_serialize_struct" << endl; + // indent(out) << + // "?M1(" << prefix << ", write, Oprot), % generate_serialize_struct" << endl; +} + +void t_erl_generator::generate_serialize_container(ostream &out, + t_type* ttype, + string prefix) { + if (ttype->is_map()) { + indent(out) << + "?M3(Oprot, writeMapBegin, " << + type_to_enum(((t_map*)ttype)->get_key_type()) << ", " << + type_to_enum(((t_map*)ttype)->get_val_type()) << ", length(" << + prefix << ")), % generate_serialize_container" << endl; + } else if (ttype->is_set()) { + indent(out) << + "?M2(Oprot, writeSetBegin, " << + type_to_enum(((t_set*)ttype)->get_elem_type()) << ", length(" << + prefix << ")), % generate_serialize_container" << endl; + } else if (ttype->is_list()) { + indent(out) << + "?M2(Oprot, writeListBegin, " << + type_to_enum(((t_list*)ttype)->get_elem_type()) << ", length(" << + prefix << ")), % generate_serialize_container" << endl; + } + + if (ttype->is_map()) { // TODO + string kiter = tmp("kiter"); + string viter = tmp("viter"); + indent(out) << + prefix << ".each do |" << kiter << ", " << viter << "|" << endl; + indent_up(); + generate_serialize_map_element(out, (t_map*)ttype, kiter, viter); + indent_down(); + indent(out) << "end" << endl; + } else if (ttype->is_set()) { + string iter = tmp("iter"); + string t = tmp("true"); + indent(out) << + prefix << ".each do |" << iter << ", " << t << "|" << endl; + indent_up(); + generate_serialize_set_element(out, (t_set*)ttype, iter); + indent_down(); + indent(out) << "end" << endl; + } else if (ttype->is_list()) { + string iter = tmp("iter"); + indent(out) << + prefix << ".each do |" << iter << "|" << endl; + indent_up(); + generate_serialize_list_element(out, (t_list*)ttype, iter); + indent_down(); + indent(out) << "end" << endl; + } + + if (ttype->is_map()) { + indent(out) << + "?M0(Oprot, writeMapEnd), % generate_serialize_container" << endl; + } else if (ttype->is_set()) { + indent(out) << + "?M0(Oprot, writeSetEnd), % generate_serialize_container" << endl; + } else if (ttype->is_list()) { + indent(out) << + "?M0(Oprot, writeListEnd), % generate_serialize_container" << endl; + } +} + +/** + * Serializes the members of a map. + * + */ +void t_erl_generator::generate_serialize_map_element(ostream &out, // TODO + t_map* tmap, + string kiter, + string viter) { + t_field kfield(tmap->get_key_type(), kiter); + generate_serialize_field(out, &kfield, ""); + + t_field vfield(tmap->get_val_type(), viter); + generate_serialize_field(out, &vfield, ""); +} + +/** + * Serializes the members of a set. + */ +void t_erl_generator::generate_serialize_set_element(ostream &out, // TODO + t_set* tset, + string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Serializes the members of a list. + */ +void t_erl_generator::generate_serialize_list_element(ostream &out, // TODO + t_list* tlist, + string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Declares a field, which may include initialization as necessary. + * + * @param ttype The type + */ +string t_erl_generator::declare_field(t_field* tfield) { // TODO + string result = "@" + tfield->get_name(); + t_type* type = tfield->get_type(); + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + if (tfield->get_value() != NULL) { + result += " = " + render_const_value(type, tfield->get_value()); + } else { + result += " = nil"; + } + return result; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_erl_generator::function_signature(t_function* tfunction, + string prefix) { + // TODO(mcslee): Nitpicky, no ',' if argument_list is empty + // WATCH cpiro: do we really hardcode This into them all? + return + prefix + tfunction->get_name() + + "(This" + capitalize(argument_list(tfunction->get_arglist())) + ")"; +} + +void t_erl_generator::export_string(string name, int num) { + if(export_lines_first_) { + export_lines_first_ = false; + } else { + export_lines_ << ", "; + } + export_lines_ << name << "/" << num; +} + +void t_erl_generator::export_types_function(t_function* tfunction, + string prefix) { + + export_types_string(prefix + tfunction->get_name(), + 1 // This + + ((tfunction->get_arglist())->get_members()).size() + ); +} +void t_erl_generator::export_types_string(string name, int num) { + if(export_types_lines_first_) { + export_types_lines_first_ = false; + } else { + export_types_lines_ << ", "; + } + export_types_lines_ << name << "/" << num; +} + +void t_erl_generator::export_function(t_function* tfunction, + string prefix) { + + export_string(prefix + tfunction->get_name(), + 1 // This + + ((tfunction->get_arglist())->get_members()).size() + ); +} + + +/** + * Renders a field list + */ +string t_erl_generator::argument_list(t_struct* tstruct) { // TODO? + string result = ""; + + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + result += ", "; // WATCH cpiro: initial comma to compensate for initial This + } else { + result += ", "; + } + result += capitalize((*f_iter)->get_name()); + } + return result; +} + +string t_erl_generator::type_name(t_type* ttype) { // TODO + string prefix = ""; + t_program* program = ttype->get_program(); + if (program != NULL && program != program_) { + if (!ttype->is_service()) { + prefix = program->get_name() + "_types."; + } + } + + string name = ttype->get_name(); + if (ttype->is_struct() || ttype->is_xception()) { + name = capitalize(ttype->get_name()); + } + + return prefix + name; +} + +/** + * Converts the parse type to a Ruby tyoe + */ +string t_erl_generator::type_to_enum(t_type* type) { + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "?tType_STRING"; + case t_base_type::TYPE_BOOL: + return "?tType_BOOL"; + case t_base_type::TYPE_BYTE: + return "?tType_BYTE"; + case t_base_type::TYPE_I16: + return "?tType_I16"; + case t_base_type::TYPE_I32: + return "?tType_I32"; + case t_base_type::TYPE_I64: + return "?tType_I64"; + case t_base_type::TYPE_DOUBLE: + return "?tType_DOUBLE"; + } + } else if (type->is_enum()) { + return "?tType_I32"; + } else if (type->is_struct() || type->is_xception()) { + return "?tType_STRUCT"; + } else if (type->is_map()) { + return "?tType_MAP"; + } else if (type->is_set()) { + return "?tType_SET"; + } else if (type->is_list()) { + return "?tType_LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +// LocalWords: Iprot diff --git a/compiler/cpp/src/generate/t_erl_generator.h b/compiler/cpp/src/generate/t_erl_generator.h new file mode 100644 index 00000000..d54514ed --- /dev/null +++ b/compiler/cpp/src/generate/t_erl_generator.h @@ -0,0 +1,174 @@ +#ifndef T_ERL_GENERATOR_H +#define T_ERL_GENERATOR_H + +#include +#include +#include +#include + +#include "t_oop_generator.h" + +#define T_ERL_DIR "gen-erl" + +/** + * Erlang code generator. + * + * @author + */ +class t_erl_generator : public t_oop_generator { + public: + t_erl_generator(t_program* program) : + t_oop_generator(program) {} + + /** + * Init and close methods + */ + + void init_generator(); + void close_generator(); + + /** + * Program-level generation functions + */ + + void generate_typedef (t_typedef* ttypedef); + void generate_enum (t_enum* tenum); + void generate_const (t_const* tconst); + void generate_struct (t_struct* tstruct); + void generate_xception (t_struct* txception); + void generate_service (t_service* tservice); + + std::string render_const_value(t_type* type, t_const_value* value); + + /** + * Struct generation code + */ + + void generate_erl_struct(t_struct* tstruct, bool is_exception); + void generate_erl_struct_definition(std::ostream& out, std::ostream& hrl_out, t_struct* tstruct, bool is_xception=false, bool is_result=false); + void generate_erl_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_erl_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_erl_function_helpers(t_function* tfunction); + + /** + * Service-level generation functions + */ + + void generate_service_helpers (t_service* tservice); + void generate_service_interface (t_service* tservice); + void generate_service_client (t_service* tservice); + void generate_service_server (t_service* tservice); + void generate_process_function (t_service* tservice, t_function* tfunction); + + /** + * Serialization constructs + */ + + void generate_deserialize_field (std::ostream &out, + t_field* tfield, + std::string prefix="", + bool inclass=false); + + void generate_deserialize_struct (std::ostream &out, + t_struct* tstruct, + std::string prefix=""); + + void generate_deserialize_container (std::ostream &out, + t_type* ttype, + std::string prefix=""); + + void generate_deserialize_set_element (std::ostream &out, + t_set* tset, + std::string prefix=""); + + void generate_deserialize_map_element (std::ostream &out, + t_map* tmap, + std::string prefix=""); + + void generate_deserialize_list_element (std::ostream &out, + t_list* tlist, + std::string prefix=""); + + void generate_serialize_field (std::ostream &out, + t_field* tfield, + std::string prefix=""); + + void generate_serialize_struct (std::ostream &out, + t_struct* tstruct, + std::string prefix=""); + + void generate_serialize_container (std::ostream &out, + t_type* ttype, + std::string prefix=""); + + void generate_serialize_map_element (std::ostream &out, + t_map* tmap, + std::string kiter, + std::string viter); + + void generate_serialize_set_element (std::ostream &out, + t_set* tmap, + std::string iter); + + void generate_serialize_list_element (std::ostream &out, + t_list* tlist, + std::string iter); + + /** + * Helper rendering functions + */ + + std::string erl_autogen_comment(); + std::string erl_imports(); + std::string render_includes(); + std::string declare_field(t_field* tfield); + std::string type_name(t_type* ttype); + std::string function_signature(t_function* tfunction, std::string prefix=""); + + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + + std::string capitalize(std::string in) { + in[0] = toupper(in[0]); + return in; + } + + std::string uncapitalize(std::string in) { + in[0] = tolower(in[0]); + return in; + } + + private: + + bool export_lines_first_; + std::ostringstream export_lines_; + + bool export_types_lines_first_; + std::ostringstream export_types_lines_; + + // f_types_ + // f_consts_ + // f_service_ + + /** + * File streams + */ + + void export_function(t_function* tfunction, std::string prefix=""); + void export_string(std::string name, int num); + + void export_types_function(t_function* tfunction, std::string prefix=""); + void export_types_string(std::string name, int num); + + std::ostringstream f_types_; + std::ofstream f_types_file_; + std::ofstream f_types_hrl_file_; + + std::ofstream f_consts_; + std::ostringstream f_service_; + std::ofstream f_service_file_; + std::ofstream f_service_hrl_; + +}; + +#endif diff --git a/compiler/cpp/src/main.cc b/compiler/cpp/src/main.cc index c62c925b..41305d51 100644 --- a/compiler/cpp/src/main.cc +++ b/compiler/cpp/src/main.cc @@ -35,6 +35,7 @@ #include "generate/t_rb_generator.h" #include "generate/t_xsd_generator.h" #include "generate/t_perl_generator.h" +#include "generate/t_erl_generator.h" using namespace std; @@ -124,6 +125,7 @@ bool gen_xsd = false; bool gen_php = false; bool gen_phpi = false; bool gen_perl = false; +bool gen_erl = false; bool gen_recurse = false; /** @@ -301,6 +303,7 @@ void usage() { fprintf(stderr, " -rb Generate Ruby output files\n"); fprintf(stderr, " -xsd Generate XSD output files\n"); fprintf(stderr, " -perl Generate Perl output files\n"); + fprintf(stderr, " -erl Generate Erlang output files\n"); fprintf(stderr, " -I dir Add a directory to the list of directories \n"); fprintf(stderr, " searched for include directives\n"); fprintf(stderr, " -nowarn Suppress all compiler warnings (BAD!)\n"); @@ -563,6 +566,13 @@ void generate(t_program* program) { delete perl; } + if (gen_erl) { + pverbose("Generating Erlang\n"); + t_erl_generator* erl = new t_erl_generator(program); + erl->generate_program(); + delete erl; + } + } catch (string s) { printf("Error: %s\n", s.c_str()); } catch (const char* exc) { @@ -625,6 +635,8 @@ int main(int argc, char** argv) { gen_xsd = true; } else if (strcmp(arg, "-perl") == 0) { gen_perl = true; + } else if (strcmp(arg, "-erl") == 0) { + gen_erl = true; } else if (strcmp(arg, "-I") == 0) { // An argument of "-I\ asdf" is invalid and has unknown results arg = argv[++i]; @@ -645,7 +657,7 @@ int main(int argc, char** argv) { } // You gotta generate something! - if (!gen_cpp && !gen_java && !gen_php && !gen_phpi && !gen_py && !gen_rb && !gen_xsd && !gen_perl) { + if (!gen_cpp && !gen_java && !gen_php && !gen_phpi && !gen_py && !gen_rb && !gen_xsd && !gen_perl && !gen_erl) { fprintf(stderr, "!!! No output language(s) specified\n\n"); usage(); } diff --git a/lib/erl/src-loose/protocol/tBinaryProtocol.erl b/lib/erl/src-loose/protocol/tBinaryProtocol.erl new file mode 100644 index 00000000..b82af73d --- /dev/null +++ b/lib/erl/src-loose/protocol/tBinaryProtocol.erl @@ -0,0 +1,232 @@ +-module(tBinaryProtocol). + +-include("thrift/thrift.hrl"). +-include("thrift/protocol/tBinaryProtocol.hrl"). +-include("thrift/protocol/tProtocolException.hrl"). + +-export([ + new/1, + trans/1, + skip/2, + + writeMessageBegin/4, writeMessageEnd/1, + writeStructBegin/2, writeStructEnd/1, + writeFieldBegin/4, writeFieldEnd/1, writeFieldStop/1, + writeMapBegin/4, writeMapEnd/1, + writeListBegin/3, writeListEnd/1, + writeSetBegin/3, writeSetEnd/1, + + writeBool/2, writeByte/2, writeI16/2, writeI32/2, + writeI64/2, writeDouble/2, writeString/2, + + readMessageBegin/1, readMessageEnd/1, + readStructBegin/1, readStructEnd/1, + readFieldBegin/1, readFieldEnd/1, + readMapBegin/1, readMapEnd/1, + readListBegin/1, readListEnd/1, + readSetBegin/1, readSetEnd/1, + + readBool/1, readByte/1, readI16/1, readI32/1, + readI64/1, readDouble/1, readString/1 +]). + +new(Trans) -> + #tBinaryProtocol{trans=Trans}. + +trans(This) -> % accessor + ?ATTR(trans). + +skip(This, Type) -> + tProtocol:skip(This, Type). + +writeMessageBegin(This, Name, Type, Seqid) -> + writeI32(This, ?VERSION_1 bor Type), + writeString(This, Name), + writeI32(This, Seqid). + +writeMessageEnd(This) -> + This, % suppress unused warnings + ok. + +writeStructBegin(This, Name) -> + This, Name, % suppress unused warnings + ok. + +writeStructEnd(This) -> + This, % suppress unused warnings + ok. + +writeFieldBegin(This, Name, Type, Id) -> + Name, + writeByte(This, Type), + writeI16(This, Id). + +writeFieldEnd(This) -> + This, % suppress unused warnings + ok. + +writeFieldStop(This) -> + writeByte(This, ?tType_STOP). + +writeMapBegin(This, Ktype, Vtype, Size) -> + writeByte(This, Ktype), + writeByte(This, Vtype), + writeI32(This, Size). + +writeMapEnd(This) -> + This, % suppress unused warnings + ok. + +writeListBegin(This, Etype, Size) -> + writeByte(This, Etype), + writeI32(This, Size). + +writeListEnd(This) -> + This, % suppress unused warnings + ok. + +writeSetBegin(This, Etype, Size) -> + writeByte(This, Etype), + writeI32(This, Size). + +writeSetEnd(This) -> + This, % suppress unused warnings + ok. + +% + +writeBool(This, Bool) -> + if Bool -> % true + writeByte(This, 1); + true -> % false + writeByte(This, 0) + end. + +writeByte(This, Byte) -> + Trans = This#tBinaryProtocol.trans, + ?M1(Trans, write, binary_to_list(<>)). + +writeI16(This, I16) -> + Trans = This#tBinaryProtocol.trans, + ?M1(Trans, write, binary_to_list(<>)). + +writeI32(This, I32) -> + Trans = This#tBinaryProtocol.trans, + ?M1(Trans, write, binary_to_list(<>)). + +writeI64(This, I64) -> + Trans = This#tBinaryProtocol.trans, + ?M1(Trans, write, binary_to_list(<>)). + +writeDouble(This, Double) -> + Trans = This#tBinaryProtocol.trans, + ?M1(Trans, write, binary_to_list(<>)). + +writeString(This, Str) -> + Trans = This#tBinaryProtocol.trans, + writeI32(This, length(Str)), + ?M1(Trans, write, Str). + +% + +readMessageBegin(This) -> + Version = readI32(This), + if + (Version band ?VERSION_MASK) /= ?VERSION_1 -> + throw(tProtocolException:new(?tProtocolException_BAD_VERSION, + "Missing version identifier")); + true -> ok + end, + Type = Version band 16#000000ff, + Name = readString(This), + Seqid = readI32(This), + { Name, Type, Seqid }. + +readMessageEnd(This) -> + This, % suppress unused warnings + ok. + +readStructBegin(This) -> + This, % suppress unused warnings + ok. + +readStructEnd(This) -> + This, % suppress unused warnings + ok. + +readFieldBegin(This) -> + Type = readByte(This), + if Type == ?tType_STOP -> + { nil, Type, 0 }; % WATCH + true -> + Id = readI16(This), + { nil, Type, Id } + end. + +readFieldEnd(This) -> + This, % suppress unused warnings + ok. + +readMapBegin(This) -> + Ktype = readByte(This), + Vtype = readByte(This), + Size = readI32(This), + { Ktype, Vtype, Size }. + +readMapEnd(This) -> + This, % suppress unused warnings + ok. + +readListBegin(This) -> + Etype = readByte(This), + Size = readI32(This), + { Etype, Size }. + +readListEnd(This) -> + This, % suppress unused warnings + ok. + +readSetBegin(This) -> + Etype = readByte(This), + Size = readI32(This), + { Etype, Size }. + +readSetEnd(This) -> + This, % suppress unused warnings + ok. + +% WATCH everything ... who knows what of this will work + +readBool(This) -> + Byte = readByte(This), + (Byte /= 0). + +readByte(This) -> + Trans = This#tBinaryProtocol.trans, + <> = ?M1(Trans, readAll, 1), + Val. + +readI16(This) -> + Trans = This#tBinaryProtocol.trans, + <> = ?M1(Trans, readAll, 2), + Val. + +readI32(This) -> + Trans = This#tBinaryProtocol.trans, + <> = ?M1(Trans, readAll, 4), + Val. + +readI64(This) -> + Trans = This#tBinaryProtocol.trans, + <> = ?M1(Trans, readAll, 8), + Val. + +readDouble(This) -> + Trans = This#tBinaryProtocol.trans, + <> = ?M1(Trans, readAll, 8), + Val. + +readString(This) -> + Trans = This#tBinaryProtocol.trans, + Sz = readI32(This), + binary_to_list(?M1(Trans, readAll, Sz)). diff --git a/lib/erl/src-loose/protocol/tBinaryProtocol.hrl b/lib/erl/src-loose/protocol/tBinaryProtocol.hrl new file mode 100644 index 00000000..cfd48367 --- /dev/null +++ b/lib/erl/src-loose/protocol/tBinaryProtocol.hrl @@ -0,0 +1,4 @@ +-define(VERSION_MASK, 16#FFFF0000). +-define(VERSION_1, 16#80010000). +-record(tBinaryProtocol, {trans}). + diff --git a/lib/erl/src-loose/protocol/tProtocol.erl b/lib/erl/src-loose/protocol/tProtocol.erl new file mode 100644 index 00000000..a36ca113 --- /dev/null +++ b/lib/erl/src-loose/protocol/tProtocol.erl @@ -0,0 +1,69 @@ +-module(tProtocol). + +-include("thrift/thrift.hrl"). +-include("thrift/protocol/tProtocol.hrl"). + +-export([new/1, skip/2]). + +skip_struct_loop(This) -> + { Name, Type, Id } = ?M0(This, readFieldBegin), + Name, Id, % suppress unused warnings + if + Type == ?tType_STOP -> + ok; + true -> + skip(This, Type), + ?M0(This, readFieldEnd), + + %% this is here in original tprotocol.rb, but i think it's a bug + % ?M0(This, readStructEnd), + skip_struct_loop(This) + end. + +skip_map_repeat(This, Ktype, Vtype, Times) -> + skip(This, Ktype), + skip(This, Vtype), + skip_map_repeat(This, Ktype, Vtype, Times-1). + +skip_set_repeat(This, Etype, Times) -> + skip(This, Etype), + skip_set_repeat(This, Etype, Times-1). + +new(Trans) -> + #tProtocol{trans=Trans}. + +skip(This, Type) -> + case Type of + ?tType_STOP -> nil; % WATCH + ?tType_BOOL -> ?M0(This, readBool); + ?tType_BYTE -> ?M0(This, readByte); + ?tType_I16 -> ?M0(This, readI16); + ?tType_I32 -> ?M0(This, readI32); + ?tType_I64 -> ?M0(This, readI64); + ?tType_DOUBLE -> ?M0(This, readDouble); + ?tType_STRING -> ?M0(This, readString); + + ?tType_STRUCT -> + ?M0(This, readStructBegin), + skip_struct_loop(This), + + %% this isn't here in the original tprotocol.rb, but i think it's a bug + ?M0(This, readStructEnd); + + ?tType_MAP -> + {Ktype, Vtype, Size} = ?M0(This, readMapBegin), + skip_map_repeat(This, Ktype, Vtype, Size), + ?M0(This, readMapEnd); + + ?tType_SET -> + {Etype, Size} = ?M0(This, readSetBegin), + skip_set_repeat(This, Etype, Size), + ?M0(This, readSetEnd); + + ?tType_LIST -> + {Etype, Size} = ?M0(This, readListBegin), + skip_set_repeat(This, Etype, Size), % [sic] skipping same as for SET + ?M0(This, readListEnd) + end. + + diff --git a/lib/erl/src-loose/protocol/tProtocol.hrl b/lib/erl/src-loose/protocol/tProtocol.hrl new file mode 100644 index 00000000..43851240 --- /dev/null +++ b/lib/erl/src-loose/protocol/tProtocol.hrl @@ -0,0 +1 @@ +-record(tProtocol, {trans}). diff --git a/lib/erl/src-loose/protocol/tProtocolException.erl b/lib/erl/src-loose/protocol/tProtocolException.erl new file mode 100644 index 00000000..b9aa48a6 --- /dev/null +++ b/lib/erl/src-loose/protocol/tProtocolException.erl @@ -0,0 +1,9 @@ +-module(tProtocolException). +-include("tProtocolException.hrl"). +-export([new/2, new/1, new/0]). + +new(Type, Message) -> + #tProtocolException{type=Type, message=Message}. + +new(Type) -> new(Type, nil). +new() -> new(?tProtocolException_UNKNOWN, nil). diff --git a/lib/erl/src-loose/protocol/tProtocolException.hrl b/lib/erl/src-loose/protocol/tProtocolException.hrl new file mode 100644 index 00000000..96bf3acb --- /dev/null +++ b/lib/erl/src-loose/protocol/tProtocolException.hrl @@ -0,0 +1,8 @@ +-record(tProtocolException, {message, type}). + +-define(tProtocolException_UNKNOWN, 0). +-define(tProtocolException_INVALID_DATA, 1). +-define(tProtocolException_NEGATIVE_SIZE, 2). +-define(tProtocolException_SIZE_LIMIT, 3). +-define(tProtocolException_BAD_VERSION, 4). + diff --git a/lib/erl/src-loose/server/tServer.erl b/lib/erl/src-loose/server/tServer.erl new file mode 100644 index 00000000..0c3182e0 --- /dev/null +++ b/lib/erl/src-loose/server/tServer.erl @@ -0,0 +1,62 @@ +-module(tServer). + +-include("thrift/thrift.hrl"). +-include("thrift/protocol/tProtocol.hrl"). +-include("thrift/protocol/tBinaryProtocol.hrl"). +% -include("thrift/transport/tTransport.hrl"). +-include("tServer.hrl"). + +-export([new/3, serve/1]). + +% now processor is the module with process_*, not an object + +new(ProcessorModule, HandlerModule, ServerTransport) -> + #tServer{processorModule=ProcessorModule, + handlerModule=HandlerModule, + serverTransport=ServerTransport}. + +serverTransport(This) -> + This#tServer.serverTransport. + +serve(This) -> + ST1 = ?M0(serverTransport(This), listen_MUTABLE), + This1 = This#tServer{serverTransport=ST1}, + serve_loop(This1). + +processorModule(This) -> + This#tServer.processorModule. + +handlerModule(This) -> + This#tServer.handlerModule. + +serve_loop(This) -> + io:format("~nready.~n", []), + Client = ?M0(serverTransport(This), accept_MUTABLE), + This1 = This#tServer{serverTransport=Client}, + + Trans = Client, % factory + Prot = tBinaryProtocol:new(Trans), + serve_loop_loop(This1, Prot), % giggle loop? + ?M0(Trans, close_MUTABLE), % don't "assign" ... discard + serve_loop(This). + +serve_loop_loop(This, Prot) -> + Next = try + Val = (processorModule(This)):process(handlerModule(This), Prot, Prot), + io:format("request processed: rv=~p~n", [Val]), + loop + catch + %% TODO(cpiro) case when is_record(...) to pick out our exception + %% records vs. normal erlang throws + {tTransportException,_,_} -> + io:format("tTransportException (normal-ish?)~n", []), + close; + E -> + io:format("EXCEPTION: ~p~n", [E]), + close + end, + case Next of + loop -> serve_loop_loop(This, Prot); + close -> ok + end. + diff --git a/lib/erl/src-loose/server/tServer.hrl b/lib/erl/src-loose/server/tServer.hrl new file mode 100644 index 00000000..23da0290 --- /dev/null +++ b/lib/erl/src-loose/server/tServer.hrl @@ -0,0 +1 @@ +-record(tServer, {processorModule, handlerModule, serverTransport}). diff --git a/lib/erl/src-loose/tApplicationException.erl b/lib/erl/src-loose/tApplicationException.erl new file mode 100644 index 00000000..c6453a39 --- /dev/null +++ b/lib/erl/src-loose/tApplicationException.erl @@ -0,0 +1,64 @@ +-module(tApplicationException). + +-include("thrift.hrl"). +% -include("tApplicationException.hrl"). + +-export([new/0, new/1, new/2, read/2, write/2]). + +new(Type, Message) -> + #tApplicationException{type=Type, message=Message}. + +new() -> new(?tApplicationException_UNKNOWN, nil). % WATCH +new(Type) -> new(Type, nil). % WATCH + +read(This, Iprot) -> + ?M0(Iprot, readStructBegin), + read_while_loop(This, Iprot), + ?M0(Iprot, readStructEnd), + This. + +read_while_loop(This, Iprot) -> + {_, Ftype, Fid} = ?M0(Iprot, readFieldBegin), % field = {fname, ftype, fid} + + if + Ftype == ?tType_STOP -> + This; + (Fid == 1) and (Ftype == ?tType_STRING) -> + This1 = This#tApplicationException{message=?M0(Iprot, readString)}, + ?M0(Iprot, readFieldEnd), + read_while_loop(This1, Iprot); + + Fid == 1 -> + ?M0(Iprot, skip), + ?M0(Iprot, readFieldEnd), + read_while_loop(This, Iprot); + + (Fid == 2) and (Ftype == ?tType_I32) -> + This1 = This#tApplicationException{type=?M0(Iprot, readI32)}, + ?M0(Iprot, readFieldEnd), + read_while_loop(This1, Iprot); + + true -> + ?M0(Iprot, skip), + ?M0(Iprot, readFieldEnd), + read_while_loop(This, Iprot) + end. + +write(This, Oprot) -> + ?M1(Oprot, writeStructBegin, "tApplicationException"), + Message = This#tApplicationException.message, + Type = This#tApplicationException.type, + if Message /= undefined -> + ?M3(Oprot, writeFieldBegin, "message", ?tType_STRING, 1), + ?M1(Oprot, writeString, Message), + ?M0(Oprot, writeFieldEnd); + true -> ok + end, + if Type /= undefined -> + ?M3(Oprot, writeFieldBegin, "type", ?tType_I32, 2), + ?M1(Oprot, writeI32, Type), + ?M0(Oprot, writeFieldEnd); + true -> ok + end, + ?M0(Oprot, writeFieldStop), + ?M0(Oprot, writeStructEnd). diff --git a/lib/erl/src-loose/tApplicationException.hrl b/lib/erl/src-loose/tApplicationException.hrl new file mode 100644 index 00000000..ab1f4012 --- /dev/null +++ b/lib/erl/src-loose/tApplicationException.hrl @@ -0,0 +1,9 @@ +% TApplicationException +-define(tApplicationException_UNKNOWN, 0). +-define(tApplicationException_UNKNOWN_METHOD, 1). +-define(tApplicationException_INVALID_MESSAGE_TYPE, 2). +-define(tApplicationException_WRONG_METHOD_NAME, 3). +-define(tApplicationException_BAD_SEQUENCE_ID, 4). +-define(tApplicationException_MISSING_RESULT, 5). + +-record(tApplicationException, {message, type=?tApplicationException_UNKNOWN}). diff --git a/lib/erl/src-loose/tException.erl b/lib/erl/src-loose/tException.erl new file mode 100644 index 00000000..3a4a9e86 --- /dev/null +++ b/lib/erl/src-loose/tException.erl @@ -0,0 +1,7 @@ +-module(tException). +-include("tException.hrl"). +-export([new/0]). + +new() -> + #tException{}. + diff --git a/lib/erl/src-loose/tException.hrl b/lib/erl/src-loose/tException.hrl new file mode 100644 index 00000000..6b105519 --- /dev/null +++ b/lib/erl/src-loose/tException.hrl @@ -0,0 +1 @@ +-record(tException, {message}). diff --git a/lib/erl/src-loose/thrift.hrl b/lib/erl/src-loose/thrift.hrl new file mode 100644 index 00000000..845d61c8 --- /dev/null +++ b/lib/erl/src-loose/thrift.hrl @@ -0,0 +1,36 @@ +-define(CLASS(Obj), element(1,Obj)). + +-define(M0(Obj, Method), ((?CLASS(Obj)):Method(Obj))). +-define(M1(Obj, Method, Arg1), ((?CLASS(Obj)):Method(Obj, Arg1))). +-define(M2(Obj, Method, Arg1, Arg2), ((?CLASS(Obj)):Method(Obj, Arg1, Arg2))). +-define(M3(Obj, Method, Arg1, Arg2, Arg3), ((?CLASS(Obj)):Method(Obj, Arg1, Arg2, Arg3))). +-define(M4(Obj, Method, Arg1, Arg2, Arg3, Arg4), ((?CLASS(Obj)):Method(Obj, Arg1, Arg2, Arg3, Arg4))). +-define(M5(Obj, Method, Arg1, Arg2, Arg3, Arg4, Arg5), ((?CLASS(Obj)):Method(Obj, Arg1, Arg2, Arg3, Arg4, Arg5))). +-define(M6(Obj, Method, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6), ((?CLASS(Obj)):Method(Obj, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6))). + +-define(ATTR(X), This#?MODULE.X). + +%% TType +-define(tType_STOP, 0). +-define(tType_VOID, 1). +-define(tType_BOOL, 2). +-define(tType_BYTE, 3). +-define(tType_DOUBLE, 4). +-define(tType_I16, 6). +-define(tType_I32, 8). +-define(tType_I64, 10). +-define(tType_STRING, 11). +-define(tType_STRUCT, 12). +-define(tType_MAP, 13). +-define(tType_SET, 14). +-define(tType_LIST, 15). + +% tmessagetype +-define(tMessageType_CALL, 1). +-define(tMessageType_REPLY, 2). +-define(tMessageType_EXCEPTION, 3). + +% TProcessor +% ? + +-include("thrift/tApplicationException.hrl"). diff --git a/lib/erl/src-loose/transport/tBufferedTransport.erl b/lib/erl/src-loose/transport/tBufferedTransport.erl new file mode 100644 index 00000000..1cc809de --- /dev/null +++ b/lib/erl/src-loose/transport/tBufferedTransport.erl @@ -0,0 +1,34 @@ +-module(tBufferedTransport). + +-include("thrift/thrift.hrl"). +-include("thrift/transport/tBufferedTransport.hrl"). + +-export([new/1, isOpen/1, open/1, close/1, read/2, write_MUTABLE/2, flush_MUTABLE/1]). + +new(Transport) -> + #tBufferedTransport{transport=Transport, wbuf=""}. + +transport(This) -> % local accessor + This#tBufferedTransport.transport. + +isOpen(This) -> + ?M0(transport(This), isOpen). + +open(This) -> + ?M0(transport(This), open). + +close(This) -> + ?M0(transport(This), close). + +read(This, Sz) -> + ?M1(transport(This), read, Sz). + +write_MUTABLE(This, Buf) -> % be sure to rebind This to the retval + Wbuf = This#tBufferedTransport.wbuf, + This#tBufferedTransport{wbuf=Wbuf++Buf}. % TODO: ++ efficiency? + +flush_MUTABLE(This) -> % be sure to rebind This to the retval + Wbuf = This#tBufferedTransport.wbuf, + ?M1(transport(This), write, Wbuf), + ?M0(transport(This), flush), + This#tBufferedTransport{wbuf=""}. % TODO: ++ efficiency? diff --git a/lib/erl/src-loose/transport/tBufferedTransport.hrl b/lib/erl/src-loose/transport/tBufferedTransport.hrl new file mode 100644 index 00000000..d8d71e1b --- /dev/null +++ b/lib/erl/src-loose/transport/tBufferedTransport.hrl @@ -0,0 +1 @@ +-record(tBufferedTransport, {transport, wbuf}). diff --git a/lib/erl/src-loose/transport/tServerSocket.erl b/lib/erl/src-loose/transport/tServerSocket.erl new file mode 100644 index 00000000..239af6ef --- /dev/null +++ b/lib/erl/src-loose/transport/tServerSocket.erl @@ -0,0 +1,44 @@ +-module(tServerSocket). +-include("tServerSocket.hrl"). + +-export([new/1, listen_MUTABLE/1, accept_MUTABLE/1, close/1]). + +new(Port) -> + #tServerSocket{port=Port, handle=nil}. + +listen_MUTABLE(This) -> + Port = This#tServerSocket.port, + Options = [binary, {packet, 0}, {active, false}], % was [] + + case gen_tcp:listen(Port, Options) of + {ok, ListenSocket} -> + This#tServerSocket{handle=ListenSocket} + % {error, _} -> + % TODO: no error handling in Ruby version? + end. + +accept_MUTABLE(This) -> + if + This#tServerSocket.handle /= nil -> + case gen_tcp:accept(This#tServerSocket.handle) of + {ok, Socket} -> + tSocket:setHandle_MUTABLE( tSocket:new(), Socket ) + % {error, _} -> + % TODO: no error handling in Ruby version? + end; + true -> + nil + end. + +close(This) -> + if + This#tServerSocket.handle /= nil -> + case gen_tcp:close(This#tServerSocket.handle) of + ok -> + ok + % {error, _} -> + % TODO: no error handling in Ruby version? + end; + true -> + ok + end. diff --git a/lib/erl/src-loose/transport/tServerSocket.hrl b/lib/erl/src-loose/transport/tServerSocket.hrl new file mode 100644 index 00000000..34ed3208 --- /dev/null +++ b/lib/erl/src-loose/transport/tServerSocket.hrl @@ -0,0 +1 @@ +-record(tServerSocket, {port, handle}). diff --git a/lib/erl/src-loose/transport/tSocket.erl b/lib/erl/src-loose/transport/tSocket.erl new file mode 100644 index 00000000..850c3b92 --- /dev/null +++ b/lib/erl/src-loose/transport/tSocket.erl @@ -0,0 +1,96 @@ +-module(tSocket). + +-include("thrift/thrift.hrl"). +-include("thrift/transport/tTransportException.hrl"). +% -include("thrift/transport/tTransport.hrl"). +-include("thrift/transport/tSocket.hrl"). + +-export([new/0, new/1, new/2, setHandle_MUTABLE/2, open_MUTABLE/1, isOpen/1, write/2, read/2, close_MUTABLE/1, readAll/2]). + +new(Host, Port) -> + #tSocket{host=Host, port=Port, handle=nil}. % WATCH + +new() -> new("localhost", 9090). +new(Host) -> new(Host, 9090). + +setHandle_MUTABLE(This, Handle) -> + This#tSocket{handle=Handle}. + +open_MUTABLE(This) -> + Host = This#tSocket.host, + Port = This#tSocket.port, + Options = [], + + case gen_tcp:connect(Host, Port, Options) of + {error, _} -> + throw(tTransportException:new( + ?tTransportException_NOT_OPEN, + "Could not connect to " ++ Host ++ ":" ++ Port) + ), + {error, This}; % cpiro not reached? + {ok, Socket} -> + {ok, This#tSocket{handle=Socket}} + end. + +handle(This) -> + This#tSocket.handle. + +isOpen(This) -> + handle(This) /= nil. + +write(This, Str) -> + Val = gen_tcp:send(handle(This), Str), + + %% io:format("WRITE |~p|(~p)~n", [Str,Val]), + + case Val of + {error, _} -> + throw(tTransportException:new(?tTransportException_NOT_OPEN, "in write")); + ok -> + ok + end. + +read(This, Sz) -> + case gen_tcp:recv(handle(This), Sz) of + {ok, []} -> + { Host, Port } = { This#tSocket.host, This#tSocket.port }, + throw(tTransportException:new(?tTransportException_UNKNOWN, "TSocket: Could not read " ++ Sz ++ "bytes from " ++ Host ++ ":" ++ Port)); + {ok, Data} -> + Data; + {error, Error} -> + io:format("in tSocket:read/2: gen_tcp:recv(~p, ~p) => {error, ~p}~n", + [handle(This), Sz, Error]), + throw(tTransportException:new(?tTransportException_NOT_OPEN, "in tSocket:read/2: gen_tcp:recv")) + end. + +close_MUTABLE(This) -> + if + This#tSocket.handle == nil -> + This; + true -> + gen_tcp:close(handle(This)), + This#tSocket{handle=nil} + end. + +readAll(This, Sz) -> + readAll_loop(This, Sz, "", 0). + +readAll_loop(This, Sz, Buff, Have) -> + if + Have < Sz -> + Chunk = ?M1(This, read, Sz - Have), + + %% man gen_tcp: + %% exactly Length bytes are returned, or an error; + %% possibly discarding less than Length bytes of data when + %% the socket gets closed from the other side. + + %% io:format("READ |~p|~n", [Chunk]), + + Have1 = Have + (Sz-Have), % length(Chunk) + Buff1 = Buff ++ Chunk, % TODO: ++ efficiency? + readAll_loop(This, Sz, Buff1, Have1); + true -> + Buff + end. + diff --git a/lib/erl/src-loose/transport/tSocket.hrl b/lib/erl/src-loose/transport/tSocket.hrl new file mode 100644 index 00000000..dc1cc20e --- /dev/null +++ b/lib/erl/src-loose/transport/tSocket.hrl @@ -0,0 +1 @@ +-record(tSocket, {host, port, handle}). diff --git a/lib/erl/src-loose/transport/tTransport.erl b/lib/erl/src-loose/transport/tTransport.erl new file mode 100644 index 00000000..91b7228e --- /dev/null +++ b/lib/erl/src-loose/transport/tTransport.erl @@ -0,0 +1,4 @@ +-module(tTransport). + +-include("thrift/transport/tTransportException.hrl"). + diff --git a/lib/erl/src-loose/transport/tTransportException.erl b/lib/erl/src-loose/transport/tTransportException.erl new file mode 100644 index 00000000..b31bb203 --- /dev/null +++ b/lib/erl/src-loose/transport/tTransportException.erl @@ -0,0 +1,15 @@ +-module(tTransportException). + +-include("thrift/thrift.hrl"). +-include("thrift/transport/tTransportException.hrl"). + +-export([new/0, new/1, new/2, message/1]). + +new(Type, Message) -> + #tTransportException{type = Type, message = Message}. + +new() -> new(?tTransportException_UNKNOWN, nil). % WATCH +new(Type) -> new(Type, nil). % WATCH + +message(This) -> + ?ATTR(message). diff --git a/lib/erl/src-loose/transport/tTransportException.hrl b/lib/erl/src-loose/transport/tTransportException.hrl new file mode 100644 index 00000000..fa8554b0 --- /dev/null +++ b/lib/erl/src-loose/transport/tTransportException.hrl @@ -0,0 +1,7 @@ +-define(tTransportException_UNKNOWN, 0). +-define(tTransportException_NOT_OPEN, 1). +-define(tTransportException_ALREADY_OPEN, 2). +-define(tTransportException_TIMED_OUT, 3). +-define(tTransportException_END_OF_FILE, 4). + +-record(tTransportException, {type, message}). diff --git a/lib/erl/thrift b/lib/erl/thrift new file mode 120000 index 00000000..1ab4c5cd --- /dev/null +++ b/lib/erl/thrift @@ -0,0 +1 @@ +src-loose \ No newline at end of file diff --git a/tutorial/erl/ErlClient.rb b/tutorial/erl/ErlClient.rb new file mode 100644 index 00000000..bcf13009 --- /dev/null +++ b/tutorial/erl/ErlClient.rb @@ -0,0 +1,49 @@ +#!/usr/bin/env ruby + +$:.push('../gen-rb') + +require 'thrift/transport/tsocket' +require 'thrift/protocol/tbinaryprotocol' + +require 'Calculator' + +begin + + transport = TBufferedTransport.new(TSocket.new('localhost', 9090)) + protocol = TBinaryProtocol.new(transport) + client = Calculator::Client.new(protocol) + + transport.open() + + client.ping() + print "ping()\n" + + sum = client.add(1,1) + print "1+1=", sum, "\n" + + work = Work.new() + + begin + work.op = Operation::DIVIDE + work.num1 = 1 + work.num2 = 0 + quot = client.calculate(1, work) + puts "Whoa, we can divide by 0 now?" + rescue InvalidOperation => io + print "InvalidOperation: ", io.why, "\n" + end + + work.op = Operation::SUBTRACT + work.num1 = 15 + work.num2 = 10 + diff = client.calculate(1, work) + print "15-10=", diff, "\n" + + log = client.getStruct(1) + print "Log: ", log.value, "\n" + + transport.close() + +rescue TException => tx + print 'TException: ', tx.message, "\n" +end diff --git a/tutorial/erl/calculatorHandler.erl b/tutorial/erl/calculatorHandler.erl new file mode 100644 index 00000000..5d420192 --- /dev/null +++ b/tutorial/erl/calculatorHandler.erl @@ -0,0 +1,68 @@ +-module(calculatorHandler). + +-include("thrift/thrift.hrl"). +-include("thrift/transport/tSocket.hrl"). +-include("thrift/protocol/tBinaryProtocol.hrl"). +-include("thrift/server/tServer.hrl"). +-include("thrift/transport/tServerSocket.hrl"). + +-include("gen-erl/calculator.hrl"). +%-include("gen-erl/shared_types.hrl"). + +%-include("gen-erl/tutorial_types.hrl"). % TODO(cpiro): o rly? + +-export([start/0, ping/0, add/2, calculate/2, getStruct/1, zip/0]). + +%%% def initialize() +%%% @log = {} +%%% end + +% TODO: voids take only ok as return? + +ping() -> + io:format("ping()~n",[]), + {ok, nil}. + +add(N1, N2) -> + io:format("add(~p,~p)~n",[N1,N2]), + {ok, N1+N2}. + +calculate(Logid, Work) -> + { Op, Num1, Num2 } = { Work#work.op, Work#work.num1, Work#work.num2 }, + io:format("calculate(~p, {~p,~p,~p})~n", [Logid, Op, Num1, Num2]), + case Op of + ?ADD -> {ok, Num1 + Num2}; + ?SUBTRACT -> {ok, Num1 - Num2}; + ?MULTIPLY -> {ok, Num1 * Num2}; + ?DIVIDE -> + if Num2 == 0 -> {error, #invalidOperation{what=Op, why="Cannot divide by 0"}}; + true -> {ok, Num1 / Num2} + end; + true -> + {error, #invalidOperation{what=Op, why="Invalid operation"}} + end. + +getStruct(Key) -> + io:format("getStruct(~p)~n", [Key]), + {ok, get(Key)}. + +zip() -> + io:format("zip~n"). + +start() -> + Transport = tServerSocket:new(9090), + Server = tServer:new(calculator, ?MODULE, Transport), + io:format("Starting the server...~n", []), + ?M0(Server, serve), + io:format("done.~n", []), % won't ever reach, rookie beotch + ok. + +%%% handler = CalculatorHandler.new() +%%% processor = Calculator::Processor.new(handler) +%%% transport = TServerSocket.new(9090) +%%% transportFactory = TBufferedTransportFactory.new() +%%% server = TSimpleServer.new(processor, transport, transportFactory) +%%% +%%% puts "Starting the server..." +%%% server.serve() +%%% puts "done." -- 2.17.1