[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
diff --git a/compiler/cpp/Makefile.am b/compiler/cpp/Makefile.am
index d4c56fd..4c7d15f 100644
--- a/compiler/cpp/Makefile.am
+++ b/compiler/cpp/Makefile.am
@@ -14,7 +14,8 @@
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 0000000..ff2e287
--- /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 <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sstream>
+#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<t_program*>& 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<t_enum_value*> constants = tenum->get_constants();
+ vector<t_enum_value*>::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<t_field*>& fields = ((t_struct*)type)->get_members();
+ vector<t_field*>::const_iterator f_iter;
+ const map<t_const_value*, t_const_value*>& val = value->get_map();
+ map<t_const_value*, t_const_value*>::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<t_const_value*, t_const_value*>& val = value->get_map();
+ map<t_const_value*, t_const_value*>::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<t_const_value*>& val = value->get_list();
+ vector<t_const_value*>::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<t_field*>& members = tstruct->get_members();
+ vector<t_field*>::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<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::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()" <<endl;
+
+ // indent_down();
+ // indent(out) << "end" << endl;
+
+ // indent(out) <<
+ //"iprot.readStructEnd()" << endl;
+
+ indent_down();
+ // indent(out) << "end" << endl;
+ out << endl;
+}
+
+void t_erl_generator::generate_erl_struct_writer(ostream& out, // TODO
+ t_struct* tstruct) {
+ string name = tstruct->get_name();
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::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<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::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<t_field*>& fields = xs->get_members();
+ vector<t_field*>::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<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::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() << " #"<<uncapitalize(service_name_)<<"{iprot=Iprot, oprot=Oprot, seqid=0}." << endl <<
+ indent() << "new(Iprot) ->" << endl <<
+ indent() << " #"<<uncapitalize(service_name_)<<"{iprot=Iprot, oprot=Iprot, seqid=0}." << endl << endl;
+
+ // indent(f_service_) << "end" << endl << endl;
+
+ f_service_hrl_ <<
+ indent() << "-record("<< uncapitalize(service_name_) <<", {iprot, oprot, seqid})." << endl << endl;
+
+ // Generate client method implementations
+ vector<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::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<t_field*>& fields = arg_struct->get_members();
+ vector<t_field*>::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<t_field*>& xceptions = xs->get_members();
+ vector<t_field*>::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<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::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<t_field*>& xceptions = xs->get_members();
+ vector<t_field*>::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<t_field*>& fields = arg_struct->get_members();
+ vector<t_field*>::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<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::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 0000000..d54514e
--- /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 <string>
+#include <fstream>
+#include <iostream>
+#include <vector>
+
+#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 c62c925..41305d5 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_php = false;
bool gen_phpi = false;
bool gen_perl = false;
+bool gen_erl = false;
bool gen_recurse = false;
/**
@@ -301,6 +303,7 @@
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 @@
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 @@
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 @@
}
// 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 0000000..b82af73
--- /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(<<Byte:8/big>>)).
+
+writeI16(This, I16) ->
+ Trans = This#tBinaryProtocol.trans,
+ ?M1(Trans, write, binary_to_list(<<I16:16/big>>)).
+
+writeI32(This, I32) ->
+ Trans = This#tBinaryProtocol.trans,
+ ?M1(Trans, write, binary_to_list(<<I32:32/big>>)).
+
+writeI64(This, I64) ->
+ Trans = This#tBinaryProtocol.trans,
+ ?M1(Trans, write, binary_to_list(<<I64:64/big>>)).
+
+writeDouble(This, Double) ->
+ Trans = This#tBinaryProtocol.trans,
+ ?M1(Trans, write, binary_to_list(<<Double:64/big>>)).
+
+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,
+ <<Val:8/integer-signed-big, _/binary>> = ?M1(Trans, readAll, 1),
+ Val.
+
+readI16(This) ->
+ Trans = This#tBinaryProtocol.trans,
+ <<Val:16/integer-signed-big, _/binary>> = ?M1(Trans, readAll, 2),
+ Val.
+
+readI32(This) ->
+ Trans = This#tBinaryProtocol.trans,
+ <<Val:32/integer-signed-big, _/binary>> = ?M1(Trans, readAll, 4),
+ Val.
+
+readI64(This) ->
+ Trans = This#tBinaryProtocol.trans,
+ <<Val:64/integer-signed-big, _/binary>> = ?M1(Trans, readAll, 8),
+ Val.
+
+readDouble(This) ->
+ Trans = This#tBinaryProtocol.trans,
+ <<Val:64/float-signed-big, _/binary>> = ?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 0000000..cfd4836
--- /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 0000000..a36ca11
--- /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 0000000..4385124
--- /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 0000000..b9aa48a
--- /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 0000000..96bf3ac
--- /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 0000000..0c3182e
--- /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 0000000..23da029
--- /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 0000000..c6453a3
--- /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 0000000..ab1f401
--- /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 0000000..3a4a9e8
--- /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 0000000..6b10551
--- /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 0000000..845d61c
--- /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 0000000..1cc809d
--- /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 0000000..d8d71e1
--- /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 0000000..239af6e
--- /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 0000000..34ed320
--- /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 0000000..850c3b9
--- /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 0000000..dc1cc20
--- /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 0000000..91b7228
--- /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 0000000..b31bb20
--- /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 0000000..fa8554b
--- /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 0000000..1ab4c5c
--- /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 0000000..bcf1300
--- /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 0000000..5d42019
--- /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."