UNPKG

react-native-protobuf

Version:

由于 protobuf.js 无法在 React Native 中直接使用,简化了下适应 React Native

1,537 lines (1,403 loc) 208 kB
/* 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