UNPKG

@kbrw/node_erlastic

Version:

Interface to make node-erlang communication, integrate a nodejs gen_server into your erlang application

511 lines (455 loc) 12.5 kB
// BERT-NODE // Arnaud Wetzel, Inspired by bert.js of 2009 Rusty Klophaus (@rklophaus) // Rewrite it to // - code and decode from node-js Buffer objects // - handle erlang 17 maps // - binary type is Buffer // // References: http://www.erlang.org/doc/apps/erts/erl_ext_dist.html#8 function BertClass() { this.BERT_START = 131; this.SMALL_ATOM = 115; this.ATOM = 100; this.BINARY = 109; this.SMALL_INTEGER = 97; this.INTEGER = 98; this.SMALL_BIG = 110; this.LARGE_BIG = 111; this.FLOAT = 99; this.STRING = 107; this.LIST = 108; this.SMALL_TUPLE = 104; this.LARGE_TUPLE = 105; this.NIL = 106; this.MAP = 116; this.NEW_FLOAT = 70; this.ZERO = 0; this.ELIXIR = 0; this.ERLANG = 1; this.all_binaries_as_string = false; this.map_key_as_atom = true; this.decode_undefined_values = true; this.convention = this.ELIXIR; this.output_buffer = Buffer.allocUnsafe(10000000); this.output_buffer[0] = this.BERT_START; } function BertAtom(Obj) { this.type = "Atom"; this.value = Obj; this.toString = function () { return Obj; }; } function BertTuple(Arr) { this.type = "Tuple"; this.length = Arr.length; this.value = Arr; for (var i = 0; i < Arr.length; i++) { this[i] = Arr[i]; } this.toString = function () { var i, s = ""; for (i = 0; i < this.length; i++) { if (s !== "") { s += ", "; } s += this[i].toString(); } return "{" + s + "}"; }; } // - INTERFACE - BertClass.prototype.encode = function (Obj,nocopy) { if (nocopy === undefined) var nocopy = false; var tail_buffer = this.encode_inner(Obj,this.output_buffer.slice(1)); if(tail_buffer.length == 0){ throw new Error("Bert encode a too big term, encoding buffer overflow"); } if(!nocopy){ res = Buffer.allocUnsafe(this.output_buffer.length - tail_buffer.length); this.output_buffer.copy(res,0,0,res.length); return res; }else{ return this.output_buffer.slice(0,this.output_buffer.length - tail_buffer.length); } }; BertClass.prototype.decode = function (buffer) { if (buffer[0] !== this.BERT_START) { throw ("Not a valid BERT."); } var Obj = this.decode_inner(buffer.slice(1)); if (Obj.rest.length !== 0) { throw ("Invalid BERT."); } return Obj.value; }; BertClass.prototype.atom = function (Obj) { return new BertAtom(Obj); }; BertClass.prototype.tuple = function () { return new BertTuple(arguments); }; // - ENCODING - BertClass.prototype.encode_inner = function (Obj, buffer) { var func = 'encode_' + typeof(Obj); return this[func](Obj,buffer); }; BertClass.prototype.encode_string = function (Obj, buffer) { if (this.convention === this.ELIXIR){ return this.encode_binary(Buffer.from(Obj), buffer); } else { buffer[0] = this.STRING; buffer.writeUInt16BE(Obj.length,1); var len = buffer.write(Obj,3); return buffer.slice(3+len); } }; BertClass.prototype.encode_boolean = function (Obj, buffer) { if (Obj) { return this.encode_inner(this.atom("true"), buffer); } else { return this.encode_inner(this.atom("false"), buffer); } }; BertClass.prototype.encode_number = function (Obj, buffer) { var s, isInteger = (Obj % 1 === 0); // Handle floats... if (!isInteger) { return this.encode_float(Obj, buffer); } // Small int... if (isInteger && Obj >= 0 && Obj < 256) { buffer[0] = this.SMALL_INTEGER; buffer.writeUInt8(Obj, 1); return buffer.slice(2); } // 4 byte int... if (isInteger && Obj >= -134217728 && Obj <= 134217727) { buffer[0] = this.INTEGER; buffer.writeInt32BE(Obj, 1); return buffer.slice(5); } // Bignum... var num_buffer = Buffer.allocUnsafe(buffer.length); if (Obj < 0) { Obj *= -1; num_buffer[0] = 1; } else { num_buffer[0] = 0; } var offset = 1; while (Obj !== 0) { num_buffer[offset] = Obj % 256; Obj = Math.floor(Obj / 256); offset++; } if (offset < 256) { buffer[0] = this.SMALL_BIG; buffer.writeUInt8(offset - 1, 1); num_buffer.copy(buffer,2,0,offset); return buffer.slice(2 + offset); } else { buffer[0] = this.LARGE_BIG; buffer.writeUInt32BE(offset - 1, 1); num_buffer.copy(buffer,5,0,offset); return buffer.slice(5 + offset); } }; BertClass.prototype.encode_float = function (Obj, buffer) { // float... buffer[0] = this.NEW_FLOAT; buffer.writeDoubleBE(Obj,1); return buffer.slice(9); }; BertClass.prototype.encode_object = function (Obj, buffer) { // Check if it's an atom, binary, or tuple... if (Obj === null){ var undefined_atom = (this.convention === this.ELIXIR) ? "nil" : "undefined"; return this.encode_inner(this.atom(undefined_atom),buffer); } if (Obj instanceof Buffer) { return this.encode_binary(Obj,buffer); } if (Obj instanceof Array) { return this.encode_array(Obj,buffer); } if (Obj.type === "Atom") { return this.encode_atom(Obj,buffer); } if (Obj.type === "Tuple") { return this.encode_tuple(Obj,buffer); } // Treat the object as an associative array... return this.encode_map(Obj,buffer); }; BertClass.prototype.encode_atom = function (Obj, buffer) { buffer[0] = this.ATOM; buffer.writeUInt16BE(Obj.value.length,1); var len = buffer.write(Obj.value,3); return buffer.slice(3+len); }; BertClass.prototype.encode_binary = function (Obj, buffer) { buffer[0] = this.BINARY; buffer.writeUInt32BE(Obj.length,1); Obj.copy(buffer,5); return buffer.slice(5 + Obj.length); }; // undefined is null BertClass.prototype.encode_undefined = function (Obj, buffer) { return this.encode_inner(null,buffer) } BertClass.prototype.encode_tuple = function (Obj, buffer) { var i; if (Obj.length < 256) { buffer[0] = this.SMALL_TUPLE; buffer.writeUInt8(Obj.length,1); buffer = buffer.slice(2); } else { buffer[0] = this.LARGE_TUPLE; buffer.writeUInt32BE(Obj.length,1); buffer = buffer.slice(5); } for (i = 0; i < Obj.length; i++) { buffer = this.encode_inner(Obj[i],buffer); } return buffer; }; BertClass.prototype.encode_array = function (Obj, buffer) { if (Obj.length == 0){ buffer[0] = this.NIL; return buffer.slice(1); } buffer[0] = this.LIST; buffer.writeUInt32BE(Obj.length,1); buffer = buffer.slice(5); var i; for (i = 0; i < Obj.length; i++) { buffer = this.encode_inner(Obj[i],buffer); } buffer[0] = this.NIL; return buffer.slice(1); }; BertClass.prototype.encode_map = function (Obj, buffer) { var keys = Object.keys(Obj); buffer[0] = this.MAP; buffer.writeUInt32BE(keys.length, 1); buffer = buffer.slice(5); var i; for (i = 0; i < keys.length; i++) { key = (this.map_key_as_atom) ? this.atom(keys[i]) : keys[i]; buffer = this.encode_inner(key,buffer); buffer = this.encode_inner(Obj[keys[i]],buffer); } return buffer; }; // - DECODING - BertClass.prototype.decode_inner = function (buffer) { var Type = buffer[0]; buffer = buffer.slice(1); switch (Type) { case this.SMALL_ATOM: return this.decode_atom(buffer, 1); case this.ATOM: return this.decode_atom(buffer, 2); case this.BINARY: return this.decode_binary(buffer); case this.SMALL_INTEGER: return this.decode_integer(buffer, 1, true); case this.INTEGER: return this.decode_integer(buffer, 4); case this.SMALL_BIG: return this.decode_big(buffer, 1); case this.LARGE_BIG: return this.decode_big(buffer, 4); case this.FLOAT: return this.decode_float(buffer); case this.NEW_FLOAT: return this.decode_new_float(buffer); case this.STRING: return this.decode_string(buffer); case this.LIST: return this.decode_list(buffer); case this.SMALL_TUPLE: return this.decode_tuple(buffer, 1); case this.LARGE_TUPLE: return this.decode_large_tuple(buffer, 4); case this.NIL: return this.decode_nil(buffer); case this.MAP: return this.decode_map(buffer); default: throw ("Unexpected BERT type: " + Type); } }; BertClass.prototype.decode_atom = function (buffer, Count) { var Size, Value; Size = this.bytes_to_int(buffer, Count); buffer = buffer.slice(Count); Value = buffer.toString('utf8',0, Size); if (Value === "true") { Value = true; } else if (Value === "false") { Value = false; } else if (this.decode_undefined_values && this.convention === this.ELIXIR && Value === "nil") { Value = null; } else if (this.decode_undefined_values && this.convention === this.ERLANG && Value === "undefined") { Value = null; } else{ Value = this.atom(Value); } return { value: Value, rest: buffer.slice(Size) }; }; BertClass.prototype.decode_binary = function (buffer) { var Size = this.bytes_to_int(buffer, 4); buffer = buffer.slice(4); var bin = Buffer.allocUnsafe(Size); buffer.copy(bin,0,0,Size); return { value: (this.convention === this.ELIXIR && this.all_binaries_as_string) ? bin.toString() : bin, rest: buffer.slice(Size) }; }; BertClass.prototype.decode_integer = function (buffer, Count, unsigned) { return { value: this.bytes_to_int(buffer, Count, unsigned), rest: buffer.slice(Count) }; }; BertClass.prototype.decode_big = function (buffer, Count) { var Size = this.bytes_to_int(buffer, Count); buffer = buffer.slice(Count); var isNegative, i, n, Num = 0; isNegative = (buffer[0] === 1); buffer = buffer.slice(1); for (i = Size - 1; i >= 0; i--) { n = buffer[i]; if (Num === 0) { Num = n; } else { Num = Num * 256 + n; } } if (isNegative) { Num = Num * -1 } return { value : Num, rest: buffer.slice(Size) }; }; BertClass.prototype.decode_float = function (buffer) { var Size = 31; return { value: parseFloat(buffer.toString('utf8',0,Size)), rest: buffer.slice(Size) }; }; BertClass.prototype.decode_new_float = function (buffer) { return { value: buffer.readDoubleBE(0), rest: buffer.slice(8) }; }; BertClass.prototype.decode_string = function (buffer) { var Size = this.bytes_to_int(buffer, 2); buffer = buffer.slice(2); return { value: buffer.toString('utf8',0,Size), rest: buffer.slice(Size) }; }; BertClass.prototype.decode_list = function (buffer) { var Size, i, El, LastChar, Arr = []; Size = this.bytes_to_int(buffer, 4); buffer = buffer.slice(4); for (i = 0; i < Size; i++) { El = this.decode_inner(buffer); Arr.push(El.value); buffer = El.rest; } LastChar = buffer[0]; if (LastChar !== this.NIL) { throw ("List does not end with NIL!"); } buffer = buffer.slice(1); return { value: Arr, rest: buffer }; }; BertClass.prototype.decode_map = function (buffer) { var Size, i, El, Key, Value, Map = {}; Size = this.bytes_to_int(buffer, 4); buffer = buffer.slice(4); for (i = 0; i < Size; i++) { El = this.decode_inner(buffer); Key = El.value; El = this.decode_inner(El.rest); Value = El.value; Map[Key] = Value; buffer = El.rest; } return { value: Map, rest: buffer }; }; BertClass.prototype.decode_tuple = function (buffer, Count) { var Size, i, El, Arr = []; Size = this.bytes_to_int(buffer, Count); buffer = buffer.slice(Count); for (i = 0; i < Size; i++) { El = this.decode_inner(buffer); Arr.push(El.value); buffer = El.rest; } return { value: this.tuple.apply(this,Arr), rest: buffer }; }; BertClass.prototype.decode_nil = function (buffer) { // nil is an empty list return { value: [], rest: buffer }; }; // Read a big-endian encoded integer from the first Length bytes // of the supplied string. BertClass.prototype.bytes_to_int = function (buffer, Length, unsigned) { switch (Length) { case 1: return unsigned ? buffer.readUInt8(0, true) : buffer.readInt8(0, true); case 2: return unsigned ? buffer.readUInt16BE(0, true) : buffer.readInt16BE(0, true); case 4: return unsigned ? buffer.readUInt32BE(0, true) : buffer.readInt32BE(0, true); } }; // - TESTING - // Pretty Print a byte-string in Erlang binary form. BertClass.prototype.pp_bytes = function (Bin) { var i, s = ""; for (i = 0; i < Bin.length; i++) { if (s !== "") { s += ","; } s += "" + Bin[i]; } return "<<" + s + ">>"; }; // Pretty Print a JS object in Erlang term form. BertClass.prototype.pp_term = function (Obj) { return Obj.toString(); }; BertClass.prototype.binary_to_list = function (Str){ var ret = []; for (var i = 0; i < Str.length; i++) ret.push(Str[i]); return ret; }; module.exports = new BertClass();