react-native-protobuf
Version:
由于 protobuf.js 无法在 React Native 中直接使用,简化了下适应 React Native
1,537 lines (1,403 loc) • 208 kB
JavaScript
/*
Copyright 2013 Daniel Wirtz <dcode@dcode.io>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @license protobuf.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
* Released under the Apache License, Version 2.0
* see: https://github.com/dcodeIO/protobuf.js for details
*/
(function(global, factory) {
/* AMD */ if (typeof define === 'function' && define["amd"])
define(["bytebuffer"], factory);
/* CommonJS */ else if (typeof require === "function" && typeof module === "object" && module && module["exports"])
module["exports"] = factory(require("bytebuffer"), true);
/* Global */ else
(global["dcodeIO"] = global["dcodeIO"] || {})["ProtoBuf"] = factory(global["dcodeIO"]["ByteBuffer"]);
})(this, function(ByteBuffer, isCommonJS) {
"use strict";
/**
* The ProtoBuf namespace.
* @exports ProtoBuf
* @namespace
* @expose
*/
var ProtoBuf = {};
/**
* @type {!function(new: ByteBuffer, ...[*])}
* @expose
*/
ProtoBuf.ByteBuffer = ByteBuffer;
/**
* @type {?function(new: Long, ...[*])}
* @expose
*/
ProtoBuf.Long = ByteBuffer.Long || null;
/**
* ProtoBuf.js version.
* @type {string}
* @const
* @expose
*/
ProtoBuf.VERSION = "5.0.1";
/**
* Wire types.
* @type {Object.<string,number>}
* @const
* @expose
*/
ProtoBuf.WIRE_TYPES = {};
/**
* Varint wire type.
* @type {number}
* @expose
*/
ProtoBuf.WIRE_TYPES.VARINT = 0;
/**
* Fixed 64 bits wire type.
* @type {number}
* @const
* @expose
*/
ProtoBuf.WIRE_TYPES.BITS64 = 1;
/**
* Length delimited wire type.
* @type {number}
* @const
* @expose
*/
ProtoBuf.WIRE_TYPES.LDELIM = 2;
/**
* Start group wire type.
* @type {number}
* @const
* @expose
*/
ProtoBuf.WIRE_TYPES.STARTGROUP = 3;
/**
* End group wire type.
* @type {number}
* @const
* @expose
*/
ProtoBuf.WIRE_TYPES.ENDGROUP = 4;
/**
* Fixed 32 bits wire type.
* @type {number}
* @const
* @expose
*/
ProtoBuf.WIRE_TYPES.BITS32 = 5;
/**
* Packable wire types.
* @type {!Array.<number>}
* @const
* @expose
*/
ProtoBuf.PACKABLE_WIRE_TYPES = [
ProtoBuf.WIRE_TYPES.VARINT,
ProtoBuf.WIRE_TYPES.BITS64,
ProtoBuf.WIRE_TYPES.BITS32
];
/**
* Types.
* @dict
* @type {!Object.<string,{name: string, wireType: number, defaultValue: *}>}
* @const
* @expose
*/
ProtoBuf.TYPES = {
// According to the protobuf spec.
"int32": {
name: "int32",
wireType: ProtoBuf.WIRE_TYPES.VARINT,
defaultValue: 0
},
"uint32": {
name: "uint32",
wireType: ProtoBuf.WIRE_TYPES.VARINT,
defaultValue: 0
},
"sint32": {
name: "sint32",
wireType: ProtoBuf.WIRE_TYPES.VARINT,
defaultValue: 0
},
"int64": {
name: "int64",
wireType: ProtoBuf.WIRE_TYPES.VARINT,
defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined
},
"uint64": {
name: "uint64",
wireType: ProtoBuf.WIRE_TYPES.VARINT,
defaultValue: ProtoBuf.Long ? ProtoBuf.Long.UZERO : undefined
},
"sint64": {
name: "sint64",
wireType: ProtoBuf.WIRE_TYPES.VARINT,
defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined
},
"bool": {
name: "bool",
wireType: ProtoBuf.WIRE_TYPES.VARINT,
defaultValue: false
},
"double": {
name: "double",
wireType: ProtoBuf.WIRE_TYPES.BITS64,
defaultValue: 0
},
"string": {
name: "string",
wireType: ProtoBuf.WIRE_TYPES.LDELIM,
defaultValue: ""
},
"bytes": {
name: "bytes",
wireType: ProtoBuf.WIRE_TYPES.LDELIM,
defaultValue: null // overridden in the code, must be a unique instance
},
"fixed32": {
name: "fixed32",
wireType: ProtoBuf.WIRE_TYPES.BITS32,
defaultValue: 0
},
"sfixed32": {
name: "sfixed32",
wireType: ProtoBuf.WIRE_TYPES.BITS32,
defaultValue: 0
},
"fixed64": {
name: "fixed64",
wireType: ProtoBuf.WIRE_TYPES.BITS64,
defaultValue: ProtoBuf.Long ? ProtoBuf.Long.UZERO : undefined
},
"sfixed64": {
name: "sfixed64",
wireType: ProtoBuf.WIRE_TYPES.BITS64,
defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined
},
"float": {
name: "float",
wireType: ProtoBuf.WIRE_TYPES.BITS32,
defaultValue: 0
},
"enum": {
name: "enum",
wireType: ProtoBuf.WIRE_TYPES.VARINT,
defaultValue: 0
},
"message": {
name: "message",
wireType: ProtoBuf.WIRE_TYPES.LDELIM,
defaultValue: null
},
"group": {
name: "group",
wireType: ProtoBuf.WIRE_TYPES.STARTGROUP,
defaultValue: null
}
};
/**
* Valid map key types.
* @type {!Array.<!Object.<string,{name: string, wireType: number, defaultValue: *}>>}
* @const
* @expose
*/
ProtoBuf.MAP_KEY_TYPES = [
ProtoBuf.TYPES["int32"],
ProtoBuf.TYPES["sint32"],
ProtoBuf.TYPES["sfixed32"],
ProtoBuf.TYPES["uint32"],
ProtoBuf.TYPES["fixed32"],
ProtoBuf.TYPES["int64"],
ProtoBuf.TYPES["sint64"],
ProtoBuf.TYPES["sfixed64"],
ProtoBuf.TYPES["uint64"],
ProtoBuf.TYPES["fixed64"],
ProtoBuf.TYPES["bool"],
ProtoBuf.TYPES["string"],
ProtoBuf.TYPES["bytes"]
];
/**
* Minimum field id.
* @type {number}
* @const
* @expose
*/
ProtoBuf.ID_MIN = 1;
/**
* Maximum field id.
* @type {number}
* @const
* @expose
*/
ProtoBuf.ID_MAX = 0x1FFFFFFF;
/**
* If set to `true`, field names will be converted from underscore notation to camel case. Defaults to `false`.
* Must be set prior to parsing.
* @type {boolean}
* @expose
*/
ProtoBuf.convertFieldsToCamelCase = false;
/**
* By default, messages are populated with (setX, set_x) accessors for each field. This can be disabled by
* setting this to `false` prior to building messages.
* @type {boolean}
* @expose
*/
ProtoBuf.populateAccessors = true;
/**
* By default, messages are populated with default values if a field is not present on the wire. To disable
* this behavior, set this setting to `false`.
* @type {boolean}
* @expose
*/
ProtoBuf.populateDefaults = true;
/**
* @alias ProtoBuf.Util
* @expose
*/
ProtoBuf.Util = (function() {
"use strict";
/**
* ProtoBuf utilities.
* @exports ProtoBuf.Util
* @namespace
*/
var Util = {};
/**
* Flag if running in node or not.
* @type {boolean}
* @const
* @expose
*/
Util.IS_NODE = !!(
typeof process === 'object' && process+'' === '[object process]' && !process['browser']
);
/**
* Constructs a XMLHttpRequest object.
* @return {XMLHttpRequest}
* @throws {Error} If XMLHttpRequest is not supported
* @expose
*/
Util.XHR = function() {
// No dependencies please, ref: http://www.quirksmode.org/js/xmlhttp.html
var XMLHttpFactories = [
function () {return new XMLHttpRequest()},
function () {return new ActiveXObject("Msxml2.XMLHTTP")},
function () {return new ActiveXObject("Msxml3.XMLHTTP")},
function () {return new ActiveXObject("Microsoft.XMLHTTP")}
];
/** @type {?XMLHttpRequest} */
var xhr = null;
for (var i=0;i<XMLHttpFactories.length;i++) {
try { xhr = XMLHttpFactories[i](); }
catch (e) { continue; }
break;
}
if (!xhr)
throw Error("XMLHttpRequest is not supported");
return xhr;
};
/**
* Fetches a resource.
* @param {string} path Resource path
* @param {function(?string)=} callback Callback receiving the resource's contents. If omitted the resource will
* be fetched synchronously. If the request failed, contents will be null.
* @return {?string|undefined} Resource contents if callback is omitted (null if the request failed), else undefined.
* @expose
*/
Util.fetch = function(path, callback) {
if (callback)
callback();
};
/**
* Converts a string to camel case.
* @param {string} str
* @returns {string}
* @expose
*/
Util.toCamelCase = function(str) {
return str.replace(/_([a-zA-Z])/g, function ($0, $1) {
return $1.toUpperCase();
});
};
return Util;
})();
/**
* Language expressions.
* @type {!Object.<string,!RegExp>}
* @expose
*/
ProtoBuf.Lang = {
// Characters always ending a statement
DELIM: /[\s\{\}=;:\[\],'"\(\)<>]/g,
// Field rules
RULE: /^(?:required|optional|repeated|map)$/,
// Field types
TYPE: /^(?:double|float|int32|uint32|sint32|int64|uint64|sint64|fixed32|sfixed32|fixed64|sfixed64|bool|string|bytes)$/,
// Names
NAME: /^[a-zA-Z_][a-zA-Z_0-9]*$/,
// Type definitions
TYPEDEF: /^[a-zA-Z][a-zA-Z_0-9]*$/,
// Type references
TYPEREF: /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)+$/,
// Fully qualified type references
FQTYPEREF: /^(?:\.[a-zA-Z][a-zA-Z_0-9]*)+$/,
// All numbers
NUMBER: /^-?(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+|([0-9]*(\.[0-9]*)?([Ee][+-]?[0-9]+)?)|inf|nan)$/,
// Decimal numbers
NUMBER_DEC: /^(?:[1-9][0-9]*|0)$/,
// Hexadecimal numbers
NUMBER_HEX: /^0[xX][0-9a-fA-F]+$/,
// Octal numbers
NUMBER_OCT: /^0[0-7]+$/,
// Floating point numbers
NUMBER_FLT: /^([0-9]*(\.[0-9]*)?([Ee][+-]?[0-9]+)?|inf|nan)$/,
// Booleans
BOOL: /^(?:true|false)$/i,
// Id numbers
ID: /^(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+)$/,
// Negative id numbers (enum values)
NEGID: /^\-?(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+)$/,
// Whitespaces
WHITESPACE: /\s/,
// All strings
STRING: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")|(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g,
// Double quoted strings
STRING_DQ: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")/g,
// Single quoted strings
STRING_SQ: /(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g
};
/**
* @alias ProtoBuf.DotProto
* @expose
*/
ProtoBuf.DotProto = (function(ProtoBuf, Lang) {
"use strict";
/**
* Utilities to parse .proto files.
* @exports ProtoBuf.DotProto
* @namespace
*/
var DotProto = {};
/**
* Constructs a new Tokenizer.
* @exports ProtoBuf.DotProto.Tokenizer
* @class prototype tokenizer
* @param {string} proto Proto to tokenize
* @constructor
*/
var Tokenizer = function(proto) {
/**
* Source to parse.
* @type {string}
* @expose
*/
this.source = proto+"";
/**
* Current index.
* @type {number}
* @expose
*/
this.index = 0;
/**
* Current line.
* @type {number}
* @expose
*/
this.line = 1;
/**
* Token stack.
* @type {!Array.<string>}
* @expose
*/
this.stack = [];
/**
* Opening character of the current string read, if any.
* @type {?string}
* @private
*/
this._stringOpen = null;
};
/**
* @alias ProtoBuf.DotProto.Tokenizer.prototype
* @inner
*/
var TokenizerPrototype = Tokenizer.prototype;
/**
* Reads a string beginning at the current index.
* @return {string}
* @private
*/
TokenizerPrototype._readString = function() {
var re = this._stringOpen === '"'
? Lang.STRING_DQ
: Lang.STRING_SQ;
re.lastIndex = this.index - 1; // Include the open quote
var match = re.exec(this.source);
if (!match)
throw Error("unterminated string");
this.index = re.lastIndex;
this.stack.push(this._stringOpen);
this._stringOpen = null;
return match[1];
};
/**
* Gets the next token and advances by one.
* @return {?string} Token or `null` on EOF
* @expose
*/
TokenizerPrototype.next = function() {
if (this.stack.length > 0)
return this.stack.shift();
if (this.index >= this.source.length)
return null;
if (this._stringOpen !== null)
return this._readString();
var repeat,
prev,
next;
do {
repeat = false;
// Strip white spaces
while (Lang.WHITESPACE.test(next = this.source.charAt(this.index))) {
if (next === '\n')
++this.line;
if (++this.index === this.source.length)
return null;
}
// Strip comments
if (this.source.charAt(this.index) === '/') {
++this.index;
if (this.source.charAt(this.index) === '/') { // Line
while (this.source.charAt(++this.index) !== '\n')
if (this.index == this.source.length)
return null;
++this.index;
++this.line;
repeat = true;
} else if ((next = this.source.charAt(this.index)) === '*') { /* Block */
do {
if (next === '\n')
++this.line;
if (++this.index === this.source.length)
return null;
prev = next;
next = this.source.charAt(this.index);
} while (prev !== '*' || next !== '/');
++this.index;
repeat = true;
} else
return '/';
}
} while (repeat);
if (this.index === this.source.length)
return null;
// Read the next token
var end = this.index;
Lang.DELIM.lastIndex = 0;
var delim = Lang.DELIM.test(this.source.charAt(end++));
if (!delim)
while(end < this.source.length && !Lang.DELIM.test(this.source.charAt(end)))
++end;
var token = this.source.substring(this.index, this.index = end);
if (token === '"' || token === "'")
this._stringOpen = token;
return token;
};
/**
* Peeks for the next token.
* @return {?string} Token or `null` on EOF
* @expose
*/
TokenizerPrototype.peek = function() {
if (this.stack.length === 0) {
var token = this.next();
if (token === null)
return null;
this.stack.push(token);
}
return this.stack[0];
};
/**
* Skips a specific token and throws if it differs.
* @param {string} expected Expected token
* @throws {Error} If the actual token differs
*/
TokenizerPrototype.skip = function(expected) {
var actual = this.next();
if (actual !== expected)
throw Error("illegal '"+actual+"', '"+expected+"' expected");
};
/**
* Omits an optional token.
* @param {string} expected Expected optional token
* @returns {boolean} `true` if the token exists
*/
TokenizerPrototype.omit = function(expected) {
if (this.peek() === expected) {
this.next();
return true;
}
return false;
};
/**
* Returns a string representation of this object.
* @return {string} String representation as of "Tokenizer(index/length)"
* @expose
*/
TokenizerPrototype.toString = function() {
return "Tokenizer ("+this.index+"/"+this.source.length+" at line "+this.line+")";
};
/**
* @alias ProtoBuf.DotProto.Tokenizer
* @expose
*/
DotProto.Tokenizer = Tokenizer;
/**
* Constructs a new Parser.
* @exports ProtoBuf.DotProto.Parser
* @class prototype parser
* @param {string} source Source
* @constructor
*/
var Parser = function(source) {
/**
* Tokenizer.
* @type {!ProtoBuf.DotProto.Tokenizer}
* @expose
*/
this.tn = new Tokenizer(source);
/**
* Whether parsing proto3 or not.
* @type {boolean}
*/
this.proto3 = false;
};
/**
* @alias ProtoBuf.DotProto.Parser.prototype
* @inner
*/
var ParserPrototype = Parser.prototype;
/**
* Parses the source.
* @returns {!Object}
* @throws {Error} If the source cannot be parsed
* @expose
*/
ParserPrototype.parse = function() {
var topLevel = {
"name": "[ROOT]", // temporary
"package": null,
"messages": [],
"enums": [],
"imports": [],
"options": {},
"services": []
// "syntax": undefined
};
var token,
head = true,
weak;
try {
while (token = this.tn.next()) {
switch (token) {
case 'package':
if (!head || topLevel["package"] !== null)
throw Error("unexpected 'package'");
token = this.tn.next();
if (!Lang.TYPEREF.test(token))
throw Error("illegal package name: " + token);
this.tn.skip(";");
topLevel["package"] = token;
break;
case 'import':
if (!head)
throw Error("unexpected 'import'");
token = this.tn.peek();
if (token === "public" || (weak = token === "weak")) // token ignored
this.tn.next();
token = this._readString();
this.tn.skip(";");
if (!weak) // import ignored
topLevel["imports"].push(token);
break;
case 'syntax':
if (!head)
throw Error("unexpected 'syntax'");
this.tn.skip("=");
if ((topLevel["syntax"] = this._readString()) === "proto3")
this.proto3 = true;
this.tn.skip(";");
break;
case 'message':
this._parseMessage(topLevel, null);
head = false;
break;
case 'enum':
this._parseEnum(topLevel);
head = false;
break;
case 'option':
this._parseOption(topLevel);
break;
case 'service':
this._parseService(topLevel);
break;
case 'extend':
this._parseExtend(topLevel);
break;
default:
throw Error("unexpected '" + token + "'");
}
}
} catch (e) {
e.message = "Parse error at line "+this.tn.line+": " + e.message;
throw e;
}
delete topLevel["name"];
return topLevel;
};
/**
* Parses the specified source.
* @returns {!Object}
* @throws {Error} If the source cannot be parsed
* @expose
*/
Parser.parse = function(source) {
return new Parser(source).parse();
};
// ----- Conversion ------
/**
* Converts a numerical string to an id.
* @param {string} value
* @param {boolean=} mayBeNegative
* @returns {number}
* @inner
*/
function mkId(value, mayBeNegative) {
var id = -1,
sign = 1;
if (value.charAt(0) == '-') {
sign = -1;
value = value.substring(1);
}
if (Lang.NUMBER_DEC.test(value))
id = parseInt(value);
else if (Lang.NUMBER_HEX.test(value))
id = parseInt(value.substring(2), 16);
else if (Lang.NUMBER_OCT.test(value))
id = parseInt(value.substring(1), 8);
else
throw Error("illegal id value: " + (sign < 0 ? '-' : '') + value);
id = (sign*id)|0; // Force to 32bit
if (!mayBeNegative && id < 0)
throw Error("illegal id value: " + (sign < 0 ? '-' : '') + value);
return id;
}
/**
* Converts a numerical string to a number.
* @param {string} val
* @returns {number}
* @inner
*/
function mkNumber(val) {
var sign = 1;
if (val.charAt(0) == '-') {
sign = -1;
val = val.substring(1);
}
if (Lang.NUMBER_DEC.test(val))
return sign * parseInt(val, 10);
else if (Lang.NUMBER_HEX.test(val))
return sign * parseInt(val.substring(2), 16);
else if (Lang.NUMBER_OCT.test(val))
return sign * parseInt(val.substring(1), 8);
else if (val === 'inf')
return sign * Infinity;
else if (val === 'nan')
return NaN;
else if (Lang.NUMBER_FLT.test(val))
return sign * parseFloat(val);
throw Error("illegal number value: " + (sign < 0 ? '-' : '') + val);
}
// ----- Reading ------
/**
* Reads a string.
* @returns {string}
* @private
*/
ParserPrototype._readString = function() {
var value = "",
token,
delim;
do {
delim = this.tn.next();
if (delim !== "'" && delim !== '"')
throw Error("illegal string delimiter: "+delim);
value += this.tn.next();
this.tn.skip(delim);
token = this.tn.peek();
} while (token === '"' || token === '"'); // multi line?
return value;
};
/**
* Reads a value.
* @param {boolean=} mayBeTypeRef
* @returns {number|boolean|string}
* @private
*/
ParserPrototype._readValue = function(mayBeTypeRef) {
var token = this.tn.peek(),
value;
if (token === '"' || token === "'")
return this._readString();
this.tn.next();
if (Lang.NUMBER.test(token))
return mkNumber(token);
if (Lang.BOOL.test(token))
return (token.toLowerCase() === 'true');
if (mayBeTypeRef && Lang.TYPEREF.test(token))
return token;
throw Error("illegal value: "+token);
};
// ----- Parsing constructs -----
/**
* Parses a namespace option.
* @param {!Object} parent Parent definition
* @param {boolean=} isList
* @private
*/
ParserPrototype._parseOption = function(parent, isList) {
var token = this.tn.next(),
custom = false;
if (token === '(') {
custom = true;
token = this.tn.next();
}
if (!Lang.TYPEREF.test(token))
// we can allow options of the form google.protobuf.* since they will just get ignored anyways
// if (!/google\.protobuf\./.test(token)) // FIXME: Why should that not be a valid typeref?
throw Error("illegal option name: "+token);
var name = token;
if (custom) { // (my_method_option).foo, (my_method_option), some_method_option, (foo.my_option).bar
this.tn.skip(')');
name = '('+name+')';
token = this.tn.peek();
if (Lang.FQTYPEREF.test(token)) {
name += token;
this.tn.next();
}
}
this.tn.skip('=');
this._parseOptionValue(parent, name);
if (!isList)
this.tn.skip(";");
};
/**
* Sets an option on the specified options object.
* @param {!Object.<string,*>} options
* @param {string} name
* @param {string|number|boolean} value
* @inner
*/
function setOption(options, name, value) {
if (typeof options[name] === 'undefined')
options[name] = value;
else {
if (!Array.isArray(options[name]))
options[name] = [ options[name] ];
options[name].push(value);
}
}
/**
* Parses an option value.
* @param {!Object} parent
* @param {string} name
* @private
*/
ParserPrototype._parseOptionValue = function(parent, name) {
var token = this.tn.peek();
if (token !== '{') { // Plain value
setOption(parent["options"], name, this._readValue(true));
} else { // Aggregate options
this.tn.skip("{");
while ((token = this.tn.next()) !== '}') {
if (!Lang.NAME.test(token))
throw Error("illegal option name: " + name + "." + token);
if (this.tn.omit(":"))
setOption(parent["options"], name + "." + token, this._readValue(true));
else
this._parseOptionValue(parent, name + "." + token);
}
}
};
/**
* Parses a service definition.
* @param {!Object} parent Parent definition
* @private
*/
ParserPrototype._parseService = function(parent) {
var token = this.tn.next();
if (!Lang.NAME.test(token))
throw Error("illegal service name at line "+this.tn.line+": "+token);
var name = token;
var svc = {
"name": name,
"rpc": {},
"options": {}
};
this.tn.skip("{");
while ((token = this.tn.next()) !== '}') {
if (token === "option")
this._parseOption(svc);
else if (token === 'rpc')
this._parseServiceRPC(svc);
else
throw Error("illegal service token: "+token);
}
this.tn.omit(";");
parent["services"].push(svc);
};
/**
* Parses a RPC service definition of the form ['rpc', name, (request), 'returns', (response)].
* @param {!Object} svc Service definition
* @private
*/
ParserPrototype._parseServiceRPC = function(svc) {
var type = "rpc",
token = this.tn.next();
if (!Lang.NAME.test(token))
throw Error("illegal rpc service method name: "+token);
var name = token;
var method = {
"request": null,
"response": null,
"request_stream": false,
"response_stream": false,
"options": {}
};
this.tn.skip("(");
token = this.tn.next();
if (token.toLowerCase() === "stream") {
method["request_stream"] = true;
token = this.tn.next();
}
if (!Lang.TYPEREF.test(token))
throw Error("illegal rpc service request type: "+token);
method["request"] = token;
this.tn.skip(")");
token = this.tn.next();
if (token.toLowerCase() !== "returns")
throw Error("illegal rpc service request type delimiter: "+token);
this.tn.skip("(");
token = this.tn.next();
if (token.toLowerCase() === "stream") {
method["response_stream"] = true;
token = this.tn.next();
}
method["response"] = token;
this.tn.skip(")");
token = this.tn.peek();
if (token === '{') {
this.tn.next();
while ((token = this.tn.next()) !== '}') {
if (token === 'option')
this._parseOption(method);
else
throw Error("illegal rpc service token: " + token);
}
this.tn.omit(";");
} else
this.tn.skip(";");
if (typeof svc[type] === 'undefined')
svc[type] = {};
svc[type][name] = method;
};
/**
* Parses a message definition.
* @param {!Object} parent Parent definition
* @param {!Object=} fld Field definition if this is a group
* @returns {!Object}
* @private
*/
ParserPrototype._parseMessage = function(parent, fld) {
var isGroup = !!fld,
token = this.tn.next();
var msg = {
"name": "",
"fields": [],
"enums": [],
"messages": [],
"options": {},
"services": [],
"oneofs": {}
// "extensions": undefined
};
if (!Lang.NAME.test(token))
throw Error("illegal "+(isGroup ? "group" : "message")+" name: "+token);
msg["name"] = token;
if (isGroup) {
this.tn.skip("=");
fld["id"] = mkId(this.tn.next());
msg["isGroup"] = true;
}
token = this.tn.peek();
if (token === '[' && fld)
this._parseFieldOptions(fld);
this.tn.skip("{");
while ((token = this.tn.next()) !== '}') {
if (Lang.RULE.test(token))
this._parseMessageField(msg, token);
else if (token === "oneof")
this._parseMessageOneOf(msg);
else if (token === "enum")
this._parseEnum(msg);
else if (token === "message")
this._parseMessage(msg);
else if (token === "option")
this._parseOption(msg);
else if (token === "service")
this._parseService(msg);
else if (token === "extensions")
msg["extensions"] = this._parseExtensionRanges();
else if (token === "reserved")
this._parseIgnored(); // TODO
else if (token === "extend")
this._parseExtend(msg);
else if (Lang.TYPEREF.test(token)) {
if (!this.proto3)
throw Error("illegal field rule: "+token);
this._parseMessageField(msg, "optional", token);
} else
throw Error("illegal message token: "+token);
}
this.tn.omit(";");
parent["messages"].push(msg);
return msg;
};
/**
* Parses an ignored statement.
* @private
*/
ParserPrototype._parseIgnored = function() {
while (this.tn.peek() !== ';')
this.tn.next();
this.tn.skip(";");
};
/**
* Parses a message field.
* @param {!Object} msg Message definition
* @param {string} rule Field rule
* @param {string=} type Field type if already known (never known for maps)
* @returns {!Object} Field descriptor
* @private
*/
ParserPrototype._parseMessageField = function(msg, rule, type) {
if (!Lang.RULE.test(rule))
throw Error("illegal message field rule: "+rule);
var fld = {
"rule": rule,
"type": "",
"name": "",
"options": {},
"id": 0
};
var token;
if (rule === "map") {
if (type)
throw Error("illegal type: " + type);
this.tn.skip('<');
token = this.tn.next();
if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token))
throw Error("illegal message field type: " + token);
fld["keytype"] = token;
this.tn.skip(',');
token = this.tn.next();
if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token))
throw Error("illegal message field: " + token);
fld["type"] = token;
this.tn.skip('>');
token = this.tn.next();
if (!Lang.NAME.test(token))
throw Error("illegal message field name: " + token);
fld["name"] = token;
this.tn.skip("=");
fld["id"] = mkId(this.tn.next());
token = this.tn.peek();
if (token === '[')
this._parseFieldOptions(fld);
this.tn.skip(";");
} else {
type = typeof type !== 'undefined' ? type : this.tn.next();
if (type === "group") {
// "A [legacy] group simply combines a nested message type and a field into a single declaration. In your
// code, you can treat this message just as if it had a Result type field called result (the latter name is
// converted to lower-case so that it does not conflict with the former)."
var grp = this._parseMessage(msg, fld);
if (!/^[A-Z]/.test(grp["name"]))
throw Error('illegal group name: '+grp["name"]);
fld["type"] = grp["name"];
fld["name"] = grp["name"].toLowerCase();
this.tn.omit(";");
} else {
if (!Lang.TYPE.test(type) && !Lang.TYPEREF.test(type))
throw Error("illegal message field type: " + type);
fld["type"] = type;
token = this.tn.next();
if (!Lang.NAME.test(token))
throw Error("illegal message field name: " + token);
fld["name"] = token;
this.tn.skip("=");
fld["id"] = mkId(this.tn.next());
token = this.tn.peek();
if (token === "[")
this._parseFieldOptions(fld);
this.tn.skip(";");
}
}
msg["fields"].push(fld);
return fld;
};
/**
* Parses a message oneof.
* @param {!Object} msg Message definition
* @private
*/
ParserPrototype._parseMessageOneOf = function(msg) {
var token = this.tn.next();
if (!Lang.NAME.test(token))
throw Error("illegal oneof name: "+token);
var name = token,
fld;
var fields = [];
this.tn.skip("{");
while ((token = this.tn.next()) !== "}") {
fld = this._parseMessageField(msg, "optional", token);
fld["oneof"] = name;
fields.push(fld["id"]);
}
this.tn.omit(";");
msg["oneofs"][name] = fields;
};
/**
* Parses a set of field option definitions.
* @param {!Object} fld Field definition
* @private
*/
ParserPrototype._parseFieldOptions = function(fld) {
this.tn.skip("[");
var token,
first = true;
while ((token = this.tn.peek()) !== ']') {
if (!first)
this.tn.skip(",");
this._parseOption(fld, true);
first = false;
}
this.tn.next();
};
/**
* Parses an enum.
* @param {!Object} msg Message definition
* @private
*/
ParserPrototype._parseEnum = function(msg) {
var enm = {
"name": "",
"values": [],
"options": {}
};
var token = this.tn.next();
if (!Lang.NAME.test(token))
throw Error("illegal name: "+token);
enm["name"] = token;
this.tn.skip("{");
while ((token = this.tn.next()) !== '}') {
if (token === "option")
this._parseOption(enm);
else {
if (!Lang.NAME.test(token))
throw Error("illegal name: "+token);
this.tn.skip("=");
var val = {
"name": token,
"id": mkId(this.tn.next(), true)
};
token = this.tn.peek();
if (token === "[")
this._parseFieldOptions({ "options": {} });
this.tn.skip(";");
enm["values"].push(val);
}
}
this.tn.omit(";");
msg["enums"].push(enm);
};
/**
* Parses extension / reserved ranges.
* @returns {!Array.<!Array.<number>>}
* @private
*/
ParserPrototype._parseExtensionRanges = function() {
var ranges = [];
var token,
range,
value;
do {
range = [];
while (true) {
token = this.tn.next();
switch (token) {
case "min":
value = ProtoBuf.ID_MIN;
break;
case "max":
value = ProtoBuf.ID_MAX;
break;
default:
value = mkNumber(token);
break;
}
range.push(value);
if (range.length === 2)
break;
if (this.tn.peek() !== "to") {
range.push(value);
break;
}
this.tn.next();
}
ranges.push(range);
} while (this.tn.omit(","));
this.tn.skip(";");
return ranges;
};
/**
* Parses an extend block.
* @param {!Object} parent Parent object
* @private
*/
ParserPrototype._parseExtend = function(parent) {
var token = this.tn.next();
if (!Lang.TYPEREF.test(token))
throw Error("illegal extend reference: "+token);
var ext = {
"ref": token,
"fields": []
};
this.tn.skip("{");
while ((token = this.tn.next()) !== '}') {
if (Lang.RULE.test(token))
this._parseMessageField(ext, token);
else if (Lang.TYPEREF.test(token)) {
if (!this.proto3)
throw Error("illegal field rule: "+token);
this._parseMessageField(ext, "optional", token);
} else
throw Error("illegal extend token: "+token);
}
this.tn.omit(";");
parent["messages"].push(ext);
return ext;
};
// ----- General -----
/**
* Returns a string representation of this parser.
* @returns {string}
*/
ParserPrototype.toString = function() {
return "Parser at line "+this.tn.line;
};
/**
* @alias ProtoBuf.DotProto.Parser
* @expose
*/
DotProto.Parser = Parser;
return DotProto;
})(ProtoBuf, ProtoBuf.Lang);
/**
* @alias ProtoBuf.Reflect
* @expose
*/
ProtoBuf.Reflect = (function(ProtoBuf) {
"use strict";
/**
* Reflection types.
* @exports ProtoBuf.Reflect
* @namespace
*/
var Reflect = {};
/**
* Constructs a Reflect base class.
* @exports ProtoBuf.Reflect.T
* @constructor
* @abstract
* @param {!ProtoBuf.Builder} builder Builder reference
* @param {?ProtoBuf.Reflect.T} parent Parent object
* @param {string} name Object name
*/
var T = function(builder, parent, name) {
/**
* Builder reference.
* @type {!ProtoBuf.Builder}
* @expose
*/
this.builder = builder;
/**
* Parent object.
* @type {?ProtoBuf.Reflect.T}
* @expose
*/
this.parent = parent;
/**
* Object name in namespace.
* @type {string}
* @expose
*/
this.name = name;
/**
* Fully qualified class name
* @type {string}
* @expose
*/
this.className;
};
/**
* @alias ProtoBuf.Reflect.T.prototype
* @inner
*/
var TPrototype = T.prototype;
/**
* Returns the fully qualified name of this object.
* @returns {string} Fully qualified name as of ".PATH.TO.THIS"
* @expose
*/
TPrototype.fqn = function() {
var name = this.name,
ptr = this;
do {
ptr = ptr.parent;
if (ptr == null)
break;
name = ptr.name+"."+name;
} while (true);
return name;
};
/**
* Returns a string representation of this Reflect object (its fully qualified name).
* @param {boolean=} includeClass Set to true to include the class name. Defaults to false.
* @return String representation
* @expose
*/
TPrototype.toString = function(includeClass) {
return (includeClass ? this.className + " " : "") + this.fqn();
};
/**
* Builds this type.
* @throws {Error} If this type cannot be built directly
* @expose
*/
TPrototype.build = function() {
throw Error(this.toString(true)+" cannot be built directly");
};
/**
* @alias ProtoBuf.Reflect.T
* @expose
*/
Reflect.T = T;
/**
* Constructs a new Namespace.
* @exports ProtoBuf.Reflect.Namespace
* @param {!ProtoBuf.Builder} builder Builder reference
* @param {?ProtoBuf.Reflect.Namespace} parent Namespace parent
* @param {string} name Namespace name
* @param {Object.<string,*>=} options Namespace options
* @param {string?} syntax The syntax level of this definition (e.g., proto3)
* @constructor
* @extends ProtoBuf.Reflect.T
*/
var Namespace = function(builder, parent, name, options, syntax) {
T.call(this, builder, parent, name);
/**
* @override
*/
this.className = "Namespace";
/**
* Children inside the namespace.
* @type {!Array.<ProtoBuf.Reflect.T>}
*/
this.children = [];
/**
* Options.
* @type {!Object.<string, *>}
*/
this.options = options || {};
/**
* Syntax level (e.g., proto2 or proto3).
* @type {!string}
*/
this.syntax = syntax || "proto2";
};
/**
* @alias ProtoBuf.Reflect.Namespace.prototype
* @inner
*/
var NamespacePrototype = Namespace.prototype = Object.create(T.prototype);
/**
* Returns an array of the namespace's children.
* @param {ProtoBuf.Reflect.T=} type Filter type (returns instances of this type only). Defaults to null (all children).
* @return {Array.<ProtoBuf.Reflect.T>}
* @expose
*/
NamespacePrototype.getChildren = function(type) {
type = type || null;
if (type == null)
return this.children.slice();
var children = [];
for (var i=0, k=this.children.length; i<k; ++i)
if (this.children[i] instanceof type)
children.push(this.children[i]);
return children;
};
/**
* Adds a child to the namespace.
* @param {ProtoBuf.Reflect.T} child Child
* @throws {Error} If the child cannot be added (duplicate)
* @expose
*/
NamespacePrototype.addChild = function(child) {
var other;
if (other = this.getChild(child.name)) {
// Try to revert camelcase transformation on collision
if (other instanceof Message.Field && other.name !== other.originalName && this.getChild(other.originalName) === null)
other.name = other.originalName; // Revert previous first (effectively keeps both originals)
else if (child instanceof Message.Field && child.name !== child.originalName && this.getChild(child.originalName) === null)
child.name = child.originalName;
else
throw Error("Duplicate name in namespace "+this.toString(true)+": "+child.name);
}
this.children.push(child);
};
/**
* Gets a child by its name or id.
* @param {string|number} nameOrId Child name or id
* @return {?ProtoBuf.Reflect.T} The child or null if not found
* @expose
*/
NamespacePrototype.getChild = function(nameOrId) {
var key = typeof nameOrId === 'number' ? 'id' : 'name';
for (var i=0, k=this.children.length; i<k; ++i)
if (this.children[i][key] === nameOrId)
ret