Skip to content

Commit

Permalink
Static pbjs target progress, now generates usable CommonJS code, see #…
Browse files Browse the repository at this point in the history
  • Loading branch information
dcodeIO committed Dec 1, 2016
1 parent 00f3574 commit ad5abe7
Show file tree
Hide file tree
Showing 7 changed files with 758 additions and 89 deletions.
226 changes: 204 additions & 22 deletions cli/targets/static.js
Expand Up @@ -2,31 +2,31 @@ module.exports = static_target;

static_target.private = true;

// This file contains the beginnings of static code generation.
// It doesn't generate anything useful, yet, but can be used as a starting point.
// Currently, this file contains initial static code for CommonJS modules.

// TBD:
// - Generate a single file or scaffold an entire project directory? Both?
// - Targets: ES5, ES6, TypeScript? CommonJS? AMD?
// - Is there a need for a minimal runtime composed only of Reader/Writer/minimal util?
// - What about generating comments and typescript definitions for non-ts targets?
// - What about generating typescript definitions for non-ts targets?

var protobuf = require("../..");

var Type = protobuf.Type,
Service = protobuf.Service,
Enum = protobuf.Enum,
Namespace = protobuf.Namespace,
codegen = protobuf.util.codegen;
encoder = protobuf.encoder,
decoder = protobuf.decoder,
verifier = protobuf.verifier,
util = protobuf.util;

var out = [];
var indent = 0;

function static_target(root, options, callback) {
tree = {};
try {
out.push("var protobuf = require(\"protobufjs\");");
out.push("var root = exports;");
buildNamespace("root", root);
buildNamespace("module.exports", root);
callback(null, out.join('\n'));
} catch (err) {
callback(err);
Expand All @@ -35,33 +35,215 @@ function static_target(root, options, callback) {
}
}

function push(line) {
if (line === "")
return out.push("");
var ind = "";
for (var i = 0; i < indent; ++i)
ind += " ";
out.push(ind + line);
}

function pushComment(lines) {
push("/**");
lines.forEach(function(line, i) {
push(" * " + line);
});
push(" */");
}

function name(name) {
if (!name)
return "$root";
return name;
}

function buildNamespace(ref, ns) {
if (!ns)
return;
if (ns.name === "") { // root
push(name(ref) + " = (function() {");
++indent;
push('"use strict";');
push("");
push("// Minimal static codegen runtime");
push("var $runtime = require(\"protobufjs/runtime\");")
push("");
push("// Lazily resolved type references");
push("var $lazyTypes = [];");
} else {
push("");
push("/** @alias " + ns.fullName.substring(1) + " */");
push(name(ref) + "." + name(ns.name) + " = (function() {");
++indent;
}

if (ns instanceof Type) {
buildType(undefined, ns);
} else if (ns instanceof Service)
buildService(undefined, ns);
else {
push("");
push("/** @alias " + (ns.name && ns.fullName.substring(1) || "exports") + " */");
push("var " + name(ns.name) + " = {};");
}

ns.nestedArray.forEach(function(nested) {
if (nested instanceof Type)
buildType(ref, nested);
else if (nested instanceof Service)
buildService(ref, nested);
else if (nested instanceof Enum)
buildEnum(ref, nested);
if (nested instanceof Enum)
buildEnum(ns.name, nested);
else if (nested instanceof Namespace)
buildNamespace(ref, nested);
buildNamespace(ns.name, nested);
});
push("");
if (ns.name === "") // root
push("return $runtime.resolve($root, $lazyTypes);");
else
push("return " + name(ns.name) + ";");
--indent;
push("})();");
}

function buildFunction(type, functionName, gen, scope) {
var lines = gen.str(functionName)
.replace("(this.getCtor())", " $root" + type.fullName)
.split(/\n/g);
push(name(type.name) + "." + functionName + " = (function() {");
++indent;
push("/* eslint-disable */");
Object.keys(scope).forEach(function(key) {
push("var " + key + " = " + scope[key] + ";");
});
push("var types; $lazyTypes.push(types = [" + type.fieldsArray.map(function(field) {
return field.resolve().resolvedType
? JSON.stringify(field.resolvedType.fullName.substring(1))
: "null";
}).join(',') + "]);");
push("return " + lines[0]);
lines.slice(1).forEach(function(line) {
if (line === '\t"use strict"')
return;
var prev = indent;
var i = 0;
while (line.charAt(i++) === "\t")
++indent;
push(line.trim());
indent = prev;
});
push("/* eslint-enable */");
--indent;
push("})();");
}

function buildType(ref, type) {
out.push("");
out.push(ref + "." + type.name + " = function " + type.name + "() {};"); // currently just an empty function
buildNamespace(ref + "." + type.name, type);
var fullName = type.fullName.substring(1);

push("");
pushComment([
"Constructs a new " + type.name + ".",
"@exports " + fullName,
"@constructor",
"@param {Object} [properties] Properties to set"
]);
push("function " + name(type.name) + "(properties) {");
++indent;
push("if (properties) {");
++indent;
push("var keys = Object.keys(properties);");
push("for (var i = 0; i < keys.length; ++i)");
++indent;
push("this[keys[i]] = properties[keys[i]];");
--indent;
--indent;
push("}");
--indent;
push("}");
push("");
type.fieldsArray.forEach(function(field) {
field.resolve();
if (typeof field.defaultValue === 'object' && field.defaultValue)
return;
push(name(type.name) + ".prototype." + name(field.name) + " = " +JSON.stringify(field.defaultValue) + ";");
});

// #encode
push("");
pushComment([
"Encodes the specified " + type.name + ".",
"@function",
"@param {" + fullName + "|Object} message " + type.name + " or plain object to encode",
"@param {Writer} [writer] Writer to encode to",
"@returns {Writer} Writer"
]);
buildFunction(type, "encode", encoder.generate(type), {
Writer : "$runtime.Writer",
util : "$runtime.util"
});

// #encodeDelimited
push("");
pushComment([
"Encodes the specified " + type.name + ", length delimited.",
"@param {" + fullName + "|Object} message " + type.name + " or plain object to encode",
"@param {Writer} [writer] Writer to encode to",
"@returns {Writer} Writer"
]);
push(name(type.name) + ".encodeDelimited = function encodeDelimited(message, writer) {");
++indent;
push("return this.encode(message, writer).ldelim();");
--indent;
push("};");

// #decode
push("");
pushComment([
"Decodes a " + type.name + " from the specified reader or buffer.",
"@function",
"@param {Reader|Uint8Array} readerOrBuffer Reader or buffer to decode from",
"@param {number} [length] Message length if known beforehand",
"@returns {" + fullName + "} " + type.name
]);
buildFunction(type, "decode", decoder.generate(type), {
Reader : "$runtime.Reader",
util : "$runtime.util"
});

// #decodeDelimited
push("");
pushComment([
"Decodes a " + type.name + " from the specified reader or buffer, length delimited.",
"@param {Reader|Uint8Array} readerOrBuffer Reader or buffer to decode from",
"@returns {" + fullName + "} " + type.name
]);
push(name(type.name) + ".decodeDelimited = function decodeDelimited(readerOrBuffer) {");
++indent;
push("readerOrBuffer = readerOrBuffer instanceof Reader ? readerOrBuffer : Reader(readerOrBuffer);");
push("return this.decode(readerOrBuffer, readerOrBuffer.uint32());");
--indent;
push("};");

// #verify
push("");
pushComment([
"Verifies a " + type.name + ".",
"@param {" + fullName + "|Object} message " + type.name + " or plain object to verify",
"@returns {?string} `null` if valid, otherwise the reason why it is not"
]);
buildFunction(type, "verify", verifier.generate(type), {});
}

function buildService(ref, service) {
out.push("");
out.push(ref + "." + service.name + " = {};"); // currently just an empty object
push("");
push(name(ref) + "." + name(service.name) + " = {};"); // currently just an empty object
}

function buildEnum(ref, enm) {
out.push("");
out.push(ref + "." + enm.name + " = " + JSON.stringify(enm.values, null, "\t") + ";");
push("");
push(ref + "." + enm.name + " = {");
++indent;
push("");
Object.keys(enm.values).forEach(function(key) {
push(name(key) + ": " + enm.values[key].toString(10) + ",");
});
--indent;
push("};");
}
71 changes: 71 additions & 0 deletions runtime.js
@@ -0,0 +1,71 @@
"use strict";

/**
* Minimal static code generator runtime.
* @namespace
*/
var runtime = exports;

/**
* @alias Reader
*/
runtime.Reader = require("./src/reader");

/**
* @alias Writer
*/
runtime.Writer = require("./src/writer");

/**
* Runtime utility.
* @memberof runtime
*/
var util = runtime.util = {};

/**
* Converts a number or long to an 8 characters long hash string.
* @param {Long|number} value Value to convert
* @returns {string} Hash
*/
util.longToHash = function longToHash(value) {
return value
? LongBits.from(value).toHash()
: '\0\0\0\0\0\0\0\0';
};

/**
* Tests if two possibly long values are not equal.
* @param {number|Long} a First value
* @param {number|Long} b Second value
* @returns {boolean} `true` if not equal
*/
util.longNeq = function longNeq(a, b) {
return typeof a === 'number'
? typeof b === 'number'
? a !== b
: (a = LongBits.fromNumber(a)).lo !== b.low || a.hi !== b.high
: typeof b === 'number'
? (b = LongBits.fromNumber(b)).lo !== a.low || b.hi !== a.high
: a.low !== b.low || a.high !== b.high;
};

/**
* Resolves lazy type references.
* @param {Object} root Root object
* @param {string[][]} lazyTypes Lazy type references
* @returns {Object} `root`
*/
runtime.resolve = function resolve(root, lazyTypes) {
lazyTypes.forEach(function(types) {
types.forEach(function(path, i) {
if (!path)
return;
path = path.split('.');
var ptr = root;
while (path.length)
ptr = ptr[path.shift()];
types[i] = ptr;
});
});
return root;
};
1 change: 1 addition & 0 deletions src/index.js
Expand Up @@ -33,6 +33,7 @@ protobuf.Reader = require("./reader");
protobuf.BufferReader = protobuf.Reader.BufferReader;
protobuf.encoder = require("./encoder");
protobuf.decoder = require("./decoder");
protobuf.verifier = require("./verifier");

// Reflection
protobuf.ReflectionObject = require("./object");
Expand Down
13 changes: 7 additions & 6 deletions src/type.js
Expand Up @@ -16,7 +16,7 @@ var Enum = require("./enum"),
Writer = require("./writer"),
encoder = require("./encoder"),
decoder = require("./decoder"),
Verifier = require("./verifier"),
verifier = require("./verifier"),
inherits = require("./inherits"),
util = require("./util");

Expand Down Expand Up @@ -387,9 +387,10 @@ TypePrototype.decodeDelimited = function decodeDelimited(readerOrBuffer) {
* @returns {?string} `null` if valid, otherwise the reason why it is not
*/
TypePrototype.verify = function verify(message) {
var verifier = new Verifier(this);
this.verify = codegen.supported
? verifier.generate()
: verifier.verify;
return this.verify(message);
return (this.verify = codegen.supported
? verifier.generate(this).eof(this.getFullName() + "$verify", {
types : this.getFieldsArray().map(function(fld) { return fld.resolvedType; })
})
: verifier.fallback
).call(this, message);
};

0 comments on commit ad5abe7

Please sign in to comment.