1277 lines
41 KiB
JavaScript
1277 lines
41 KiB
JavaScript
"use strict";
|
|
var protobuf = require("../light");
|
|
|
|
var Type = protobuf.Type,
|
|
Enum = protobuf.Enum,
|
|
Field = protobuf.Field,
|
|
Reader = protobuf.Reader,
|
|
types = protobuf.types,
|
|
util = protobuf.util;
|
|
|
|
var textformat = protobuf.textformat = module.exports = {};
|
|
|
|
/**
|
|
* Maximum recursion depth for formatting length-delimited unknown fields.
|
|
* @name unknownRecursionLimit
|
|
* @type {number}
|
|
*/
|
|
textformat.unknownRecursionLimit = 10;
|
|
|
|
var punct = {
|
|
"{": true,
|
|
"}": true,
|
|
"<": true,
|
|
">": true,
|
|
":": true,
|
|
"[": true,
|
|
"]": true,
|
|
",": true,
|
|
";": true,
|
|
"-": true
|
|
};
|
|
|
|
var identRe = /^[A-Za-z_][A-Za-z0-9_]*$/,
|
|
numberStartRe = /[0-9]/,
|
|
wordCharRe = /[A-Za-z0-9_]/,
|
|
hexRe = /^[0-9A-Fa-f]$/,
|
|
octRe = /^[0-7]$/,
|
|
floatRe = /^(?:[0-9]+\.[0-9]*(?:[eE][+-]?[0-9]+)?[fF]?|\.[0-9]+(?:[eE][+-]?[0-9]+)?[fF]?|[0-9]+[eE][+-]?[0-9]+[fF]?|[0-9]+[fF])/,
|
|
hexIntRe = /^0[xX][0-9A-Fa-f]+/,
|
|
octIntRe = /^0[0-7]+/,
|
|
decIntRe = /^(?:0|[1-9][0-9]*)/;
|
|
|
|
/**
|
|
* Parses protobuf text format using the specified reflected message type.
|
|
* @param {Type} type Reflected message type
|
|
* @param {string} text Text format input
|
|
* @returns {Message<{}>} Message instance
|
|
* @private
|
|
*/
|
|
function parseText(type, text) {
|
|
if (!(type instanceof Type))
|
|
throw TypeError("type must be a Type");
|
|
type.root.resolveAll();
|
|
var parser = new Parser(String(text));
|
|
var object = parser.parseMessage(type, null, 0);
|
|
parser.expectEnd();
|
|
var err = verifyTextMessage(type, object),
|
|
message;
|
|
if (err)
|
|
throw Error(err);
|
|
message = type.create(object);
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Text format options.
|
|
* @interface ITextFormatOptions
|
|
* @property {boolean} [unknowns=false] Also includes and formats unknown fields.
|
|
*/
|
|
|
|
/**
|
|
* Parses a message from protobuf text format using the specified reflected type.
|
|
* @function fromText
|
|
* @name fromText
|
|
* @param {$protobuf.Type} type Reflected message type
|
|
* @param {string} text Text format input
|
|
* @returns {$protobuf.Message<{}>} Message instance
|
|
*/
|
|
textformat.fromText = function fromText(type, text) {
|
|
return parseText(type, text);
|
|
};
|
|
|
|
/**
|
|
* Formats a message as protobuf text format using the specified reflected type.
|
|
* @function toText
|
|
* @name toText
|
|
* @param {$protobuf.Type} type Reflected message type
|
|
* @param {$protobuf.Message<{}>|Object.<string,*>} message Message instance or plain object
|
|
* @param {ITextFormatOptions} [options] Text format options
|
|
* @returns {string} Text format output
|
|
*/
|
|
textformat.toText = function toText(type, message, options) {
|
|
if (!(type instanceof Type))
|
|
throw TypeError("type must be a Type");
|
|
type.root.resolveAll();
|
|
var lines = [];
|
|
writeMessage(type, message, lines, 0, options || {}, 0);
|
|
return lines.join("\n");
|
|
};
|
|
|
|
/**
|
|
* Installs reflected {@link Type} convenience methods.
|
|
* @function install
|
|
* @name install
|
|
* @returns {undefined}
|
|
*/
|
|
textformat.install = function install() {
|
|
/**
|
|
* Parses a message of this type from protobuf text format. Convenience for {@link textformat.fromText}.
|
|
* @param {string} text Text format input
|
|
* @returns {Message<{}>} Message instance
|
|
*/
|
|
Type.prototype.fromText = function fromText(text) {
|
|
return textformat.fromText(this, text);
|
|
};
|
|
|
|
/**
|
|
* Formats a message of this type as protobuf text format. Convenience for {@link textformat.toText}.
|
|
* @param {Message<{}>|Object.<string,*>} message Message instance or plain object
|
|
* @param {ITextFormatOptions} [options] Text format options
|
|
* @returns {string} Text format output
|
|
*/
|
|
Type.prototype.toText = function toText(message, options) {
|
|
return textformat.toText(this, message, options);
|
|
};
|
|
};
|
|
|
|
function Tokenizer(source) {
|
|
this.source = source;
|
|
this.pos = 0;
|
|
this.len = source.length;
|
|
this.line = 1;
|
|
this.stack = [];
|
|
}
|
|
|
|
Tokenizer.prototype.error = function error(message) {
|
|
return Error(message + " (line " + this.line + ")");
|
|
};
|
|
|
|
Tokenizer.prototype.peek = function peek() {
|
|
var token = this.next();
|
|
this.push(token);
|
|
return token;
|
|
};
|
|
|
|
Tokenizer.prototype.push = function push(token) {
|
|
if (token)
|
|
this.stack.unshift(token);
|
|
};
|
|
|
|
Tokenizer.prototype.next = function next() {
|
|
if (this.stack.length)
|
|
return this.stack.shift();
|
|
this.skipSpace();
|
|
if (this.pos >= this.len)
|
|
return null;
|
|
|
|
var ch = this.source.charAt(this.pos);
|
|
if (ch === "\"" || ch === "'")
|
|
return this.readString(ch);
|
|
if (ch === "." && numberStartRe.test(this.source.charAt(this.pos + 1)) || numberStartRe.test(ch))
|
|
return this.readNumber();
|
|
if (punct[ch]) {
|
|
++this.pos;
|
|
return { type: "punct", value: ch, raw: ch, line: this.line };
|
|
}
|
|
return this.readWord();
|
|
};
|
|
|
|
Tokenizer.prototype.expect = function expect(value) {
|
|
var token = this.next();
|
|
if (!token || token.value !== value)
|
|
throw this.error("expected '" + value + "'");
|
|
return token;
|
|
};
|
|
|
|
Tokenizer.prototype.skip = function skip(value) {
|
|
var token = this.next();
|
|
if (token && token.value === value)
|
|
return true;
|
|
this.push(token);
|
|
return false;
|
|
};
|
|
|
|
Tokenizer.prototype.skipSpace = function skipSpace() {
|
|
var ch;
|
|
while (this.pos < this.len) {
|
|
ch = this.source.charAt(this.pos);
|
|
if (ch === "#") {
|
|
while (this.pos < this.len && this.source.charAt(this.pos) !== "\n")
|
|
++this.pos;
|
|
} else if (ch === " " || ch === "\t" || ch === "\v" || ch === "\f" || ch === "\r") {
|
|
++this.pos;
|
|
} else if (ch === "\n") {
|
|
++this.pos;
|
|
++this.line;
|
|
} else
|
|
break;
|
|
}
|
|
};
|
|
|
|
Tokenizer.prototype.readWord = function readWord() {
|
|
var start = this.pos,
|
|
ch;
|
|
while (this.pos < this.len) {
|
|
ch = this.source.charAt(this.pos);
|
|
if (ch === "#" || ch === "\"" || ch === "'" || punct[ch] || /\s/.test(ch))
|
|
break;
|
|
++this.pos;
|
|
}
|
|
if (start === this.pos)
|
|
throw this.error("illegal character '" + this.source.charAt(this.pos) + "'");
|
|
var raw = this.source.substring(start, this.pos);
|
|
return { type: "word", value: raw, raw: raw, line: this.line };
|
|
};
|
|
|
|
Tokenizer.prototype.readNumber = function readNumber() {
|
|
var rest = this.source.substring(this.pos),
|
|
matches = [
|
|
floatRe.exec(rest),
|
|
hexIntRe.exec(rest),
|
|
octIntRe.exec(rest),
|
|
decIntRe.exec(rest)
|
|
],
|
|
raw = null;
|
|
for (var i = 0; i < matches.length; ++i)
|
|
if (matches[i] && (!raw || matches[i][0].length > raw.length))
|
|
raw = matches[i][0];
|
|
if (!raw)
|
|
throw this.error("illegal number");
|
|
this.pos += raw.length;
|
|
if (this.pos < this.len && wordCharRe.test(this.source.charAt(this.pos)))
|
|
throw this.error("illegal number '" + raw + this.source.charAt(this.pos) + "'");
|
|
return { type: "number", value: raw, raw: raw, line: this.line };
|
|
};
|
|
|
|
Tokenizer.prototype.readString = function readString(quote) {
|
|
var bytes = [],
|
|
ch,
|
|
code;
|
|
++this.pos;
|
|
while (this.pos < this.len) {
|
|
ch = this.source.charAt(this.pos++);
|
|
if (ch === quote)
|
|
return { type: "string", value: bytes, raw: quote, line: this.line };
|
|
if (ch === "\n")
|
|
throw this.error("illegal string");
|
|
if (ch !== "\\") {
|
|
code = ch.charCodeAt(0);
|
|
if ((code & 0xFC00) === 0xD800 && this.pos < this.len) {
|
|
var next = this.source.charCodeAt(this.pos);
|
|
if ((next & 0xFC00) === 0xDC00) {
|
|
++this.pos;
|
|
code = 0x10000 + ((code & 0x03FF) << 10) + (next & 0x03FF);
|
|
}
|
|
}
|
|
pushUtf8(bytes, code);
|
|
continue;
|
|
}
|
|
if (this.pos >= this.len)
|
|
throw this.error("illegal string escape");
|
|
ch = this.source.charAt(this.pos++);
|
|
switch (ch) {
|
|
case "a": bytes.push(7); break;
|
|
case "b": bytes.push(8); break;
|
|
case "f": bytes.push(12); break;
|
|
case "n": bytes.push(10); break;
|
|
case "r": bytes.push(13); break;
|
|
case "t": bytes.push(9); break;
|
|
case "v": bytes.push(11); break;
|
|
case "?": bytes.push(63); break;
|
|
case "\\": bytes.push(92); break;
|
|
case "'": bytes.push(39); break;
|
|
case "\"": bytes.push(34); break;
|
|
case "x":
|
|
case "X":
|
|
bytes.push(this.readHexEscape());
|
|
break;
|
|
case "u":
|
|
pushUtf8(bytes, this.readCodePoint(4));
|
|
break;
|
|
case "U":
|
|
pushUtf8(bytes, this.readCodePoint(8));
|
|
break;
|
|
default:
|
|
if (octRe.test(ch)) {
|
|
bytes.push(this.readOctEscape(ch));
|
|
break;
|
|
}
|
|
throw this.error("illegal string escape '\\" + ch + "'");
|
|
}
|
|
}
|
|
throw this.error("unterminated string");
|
|
};
|
|
|
|
Tokenizer.prototype.readHexEscape = function readHexEscape() {
|
|
var value = 0,
|
|
count = 0,
|
|
ch;
|
|
while (count < 2 && this.pos < this.len && hexRe.test(ch = this.source.charAt(this.pos))) {
|
|
value = value * 16 + parseInt(ch, 16);
|
|
++this.pos;
|
|
++count;
|
|
}
|
|
if (!count)
|
|
throw this.error("illegal hex escape");
|
|
return value;
|
|
};
|
|
|
|
Tokenizer.prototype.readOctEscape = function readOctEscape(first) {
|
|
var value = parseInt(first, 8),
|
|
count = 1,
|
|
ch;
|
|
while (count < 3 && this.pos < this.len && octRe.test(ch = this.source.charAt(this.pos))) {
|
|
value = value * 8 + parseInt(ch, 8);
|
|
++this.pos;
|
|
++count;
|
|
}
|
|
return value & 255;
|
|
};
|
|
|
|
Tokenizer.prototype.readCodePoint = function readCodePoint(size) {
|
|
var raw = this.source.substring(this.pos, this.pos + size);
|
|
if (raw.length !== size || !/^[0-9A-Fa-f]+$/.test(raw))
|
|
throw this.error("illegal unicode escape");
|
|
this.pos += size;
|
|
var code = parseInt(raw, 16);
|
|
if (code > 0x10FFFF || code >= 0xD800 && code <= 0xDFFF)
|
|
throw this.error("illegal unicode escape");
|
|
return code;
|
|
};
|
|
|
|
function Parser(source) {
|
|
this.tn = new Tokenizer(source);
|
|
}
|
|
|
|
Parser.prototype.error = function error(message) {
|
|
throw this.tn.error(message);
|
|
};
|
|
|
|
Parser.prototype.expectEnd = function expectEnd() {
|
|
var token = this.tn.next();
|
|
if (token)
|
|
this.error("unexpected token '" + token.value + "'");
|
|
};
|
|
|
|
Parser.prototype.parseMessage = function parseMessage(type, end, depth) {
|
|
if (depth > util.recursionLimit)
|
|
this.error("max depth exceeded");
|
|
var object = {},
|
|
seen = {};
|
|
for (;;) {
|
|
var token = this.tn.peek();
|
|
if (!token) {
|
|
if (end)
|
|
this.error("expected '" + end + "'");
|
|
return object;
|
|
}
|
|
if (end && token.value === end) {
|
|
this.tn.next();
|
|
return object;
|
|
}
|
|
if (isSeparator(token))
|
|
this.error("unexpected separator");
|
|
this.parseField(type, object, seen, depth);
|
|
token = this.tn.peek();
|
|
if (token && isSeparator(token)) {
|
|
this.tn.next();
|
|
token = this.tn.peek();
|
|
if (token && isSeparator(token))
|
|
this.error("unexpected separator");
|
|
}
|
|
}
|
|
};
|
|
|
|
Parser.prototype.parseField = function parseField(type, object, seen, depth) {
|
|
var info = this.readFieldName(type);
|
|
if (info.any) {
|
|
this.parseAny(type, info.name, object, seen, depth);
|
|
return;
|
|
}
|
|
if (!info.field) {
|
|
this.skipReservedValue(depth);
|
|
return;
|
|
}
|
|
|
|
var field = info.field.resolve();
|
|
if (field.map) {
|
|
this.parseMessageFieldDelimiter();
|
|
this.parseMapField(field, object, depth);
|
|
} else if (field.resolvedType instanceof Type) {
|
|
this.parseMessageFieldDelimiter();
|
|
if (this.tn.skip("[")) {
|
|
if (!field.repeated)
|
|
this.error(field.fullName + ": list value specified for singular field");
|
|
if (!this.tn.skip("]")) {
|
|
do {
|
|
addField(object, seen, field, this.parseMessageValue(field.resolvedType, depth));
|
|
} while (this.tn.skip(","));
|
|
this.tn.expect("]");
|
|
}
|
|
} else
|
|
addField(object, seen, field, this.parseMessageValue(field.resolvedType, depth));
|
|
} else {
|
|
this.tn.expect(":");
|
|
if (this.tn.skip("[")) {
|
|
if (!field.repeated)
|
|
this.error(field.fullName + ": list value specified for singular field");
|
|
if (!this.tn.skip("]")) {
|
|
do {
|
|
addField(object, seen, field, this.parseScalar(field));
|
|
} while (this.tn.skip(","));
|
|
this.tn.expect("]");
|
|
}
|
|
} else
|
|
addField(object, seen, field, this.parseScalar(field));
|
|
}
|
|
};
|
|
|
|
Parser.prototype.readFieldName = function readFieldName(type) {
|
|
var token = this.tn.next();
|
|
if (!token)
|
|
this.error("expected field name");
|
|
if (token.value === "[") {
|
|
var bracketName = this.readBracketName();
|
|
if (type.fullName === ".google.protobuf.Any" && bracketName.indexOf("/") >= 0)
|
|
return { any: true, name: bracketName };
|
|
return { field: lookupExtension(type, bracketName), name: bracketName };
|
|
}
|
|
if (token.type !== "word" || !identRe.test(token.value))
|
|
this.error("illegal field name '" + token.value + "'");
|
|
|
|
var field = lookupField(type, token.value);
|
|
if (!field) {
|
|
if (type.isReservedName(token.value))
|
|
return { name: token.value };
|
|
this.error(type.fullName + ": unknown field '" + token.value + "'");
|
|
}
|
|
return { field: field, name: token.value };
|
|
};
|
|
|
|
Parser.prototype.readBracketName = function readBracketName() {
|
|
var parts = [],
|
|
token;
|
|
while (token = this.tn.next()) {
|
|
if (token.value === "]")
|
|
return parts.join("");
|
|
parts.push(token.raw || token.value);
|
|
}
|
|
this.error("unterminated bracketed field name");
|
|
return null;
|
|
};
|
|
|
|
Parser.prototype.parseAny = function parseAny(type, typeUrl, object, seen, depth) {
|
|
this.parseMessageFieldDelimiter();
|
|
validateTypeUrl(typeUrl);
|
|
var typeName = typeUrl.substring(typeUrl.lastIndexOf("/") + 1),
|
|
embeddedType = type.root.lookupType(typeName.charAt(0) === "." ? typeName : "." + typeName),
|
|
embedded = this.parseMessageValue(embeddedType, depth),
|
|
typeUrlField = lookupField(type, "type_url") || lookupField(type, "typeUrl"),
|
|
valueField = lookupField(type, "value");
|
|
addField(object, seen, typeUrlField, typeUrl);
|
|
addField(object, seen, valueField, embeddedType.encode(embedded).finish());
|
|
};
|
|
|
|
Parser.prototype.parseMapField = function parseMapField(field, object, depth) {
|
|
var map = getField(object, field, {});
|
|
if (this.tn.skip("[")) {
|
|
if (!this.tn.skip("]")) {
|
|
do {
|
|
addMapEntry(field, map, this.parseMapEntry(field, depth + 1));
|
|
} while (this.tn.skip(","));
|
|
this.tn.expect("]");
|
|
}
|
|
} else
|
|
addMapEntry(field, map, this.parseMapEntry(field, depth + 1));
|
|
};
|
|
|
|
Parser.prototype.parseMessageFieldDelimiter = function parseMessageFieldDelimiter() {
|
|
this.tn.skip(":");
|
|
};
|
|
|
|
Parser.prototype.parseMapEntry = function parseMapEntry(field, depth) {
|
|
if (depth > util.recursionLimit)
|
|
this.error("max depth exceeded");
|
|
var end;
|
|
if (this.tn.skip("{"))
|
|
end = "}";
|
|
else if (this.tn.skip("<"))
|
|
end = ">";
|
|
else
|
|
this.error("expected map entry");
|
|
|
|
var entry = {},
|
|
token,
|
|
valueField = mapValueField(field),
|
|
keyField = mapKeyField(field);
|
|
for (;;) {
|
|
token = this.tn.peek();
|
|
if (!token)
|
|
this.error("expected '" + end + "'");
|
|
if (token.value === end) {
|
|
this.tn.next();
|
|
return entry;
|
|
}
|
|
if (isSeparator(token))
|
|
this.error("unexpected separator");
|
|
token = this.tn.next();
|
|
if (token.type !== "word")
|
|
this.error("expected map field name");
|
|
if (token.value === "key") {
|
|
this.tn.expect(":");
|
|
entry.key = this.parseScalar(keyField);
|
|
} else if (token.value === "value") {
|
|
if (valueField.resolvedType instanceof Type) {
|
|
this.parseMessageFieldDelimiter();
|
|
entry.value = this.parseMessageValue(valueField.resolvedType, depth);
|
|
} else {
|
|
this.tn.expect(":");
|
|
entry.value = this.parseScalar(valueField);
|
|
}
|
|
} else
|
|
this.error("unknown map field '" + token.value + "'");
|
|
token = this.tn.peek();
|
|
if (token && isSeparator(token)) {
|
|
this.tn.next();
|
|
token = this.tn.peek();
|
|
if (token && isSeparator(token))
|
|
this.error("unexpected separator");
|
|
}
|
|
}
|
|
};
|
|
|
|
Parser.prototype.parseMessageValue = function parseMessageValue(type, depth) {
|
|
var end;
|
|
if (this.tn.skip("{"))
|
|
end = "}";
|
|
else if (this.tn.skip("<"))
|
|
end = ">";
|
|
else
|
|
this.error("expected message value");
|
|
return this.parseMessage(type, end, depth + 1);
|
|
};
|
|
|
|
Parser.prototype.parseScalar = function parseScalar(field) {
|
|
if (field.type === "string" || field.type === "bytes") {
|
|
var bytes = this.readStringBytes();
|
|
return field.type === "bytes" ? util.newBuffer(bytes) : utf8Read(bytes, true);
|
|
}
|
|
|
|
var sign = 1;
|
|
if (this.tn.skip("-"))
|
|
sign = -1;
|
|
|
|
var token = this.tn.next();
|
|
if (!token)
|
|
this.error("expected value");
|
|
|
|
if (field.resolvedType instanceof Enum)
|
|
return parseEnum(field, token, sign);
|
|
|
|
switch (field.type) {
|
|
case "double":
|
|
case "float":
|
|
return parseFloatValue(token, sign);
|
|
case "bool":
|
|
return parseBool(token, sign);
|
|
case "int32":
|
|
case "sint32":
|
|
case "sfixed32":
|
|
return parseInteger(token, sign, false, 32);
|
|
case "uint32":
|
|
case "fixed32":
|
|
return parseInteger(token, sign, true, 32);
|
|
case "int64":
|
|
case "sint64":
|
|
case "sfixed64":
|
|
return parseInteger(token, sign, false, 64);
|
|
case "uint64":
|
|
case "fixed64":
|
|
return parseInteger(token, sign, true, 64);
|
|
default:
|
|
this.error(field.fullName + ": unsupported scalar type");
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
Parser.prototype.readStringBytes = function readStringBytes() {
|
|
var token = this.tn.next(),
|
|
bytes = [];
|
|
if (!token || token.type !== "string")
|
|
this.error("expected string");
|
|
Array.prototype.push.apply(bytes, token.value);
|
|
while ((token = this.tn.peek()) && token.type === "string") {
|
|
token = this.tn.next();
|
|
Array.prototype.push.apply(bytes, token.value);
|
|
}
|
|
return bytes;
|
|
};
|
|
|
|
Parser.prototype.skipReservedValue = function skipReservedValue(depth) {
|
|
this.tn.skip(":");
|
|
var token = this.tn.peek();
|
|
if (!token)
|
|
return;
|
|
if (token.value === "{") {
|
|
this.skipBalanced("{", "}", depth);
|
|
} else if (token.value === "<") {
|
|
this.skipBalanced("<", ">", depth);
|
|
} else if (token.value === "[") {
|
|
this.skipBalanced("[", "]", depth);
|
|
} else if (token.type === "string") {
|
|
this.readStringBytes();
|
|
} else {
|
|
if (token.value === "-")
|
|
this.tn.next();
|
|
this.tn.next();
|
|
}
|
|
};
|
|
|
|
Parser.prototype.skipBalanced = function skipBalanced(open, close, depth) {
|
|
var balance = 0,
|
|
token;
|
|
do {
|
|
token = this.tn.next();
|
|
if (!token)
|
|
this.error("expected '" + close + "'");
|
|
if (token.value === open) {
|
|
if (depth + ++balance > util.recursionLimit)
|
|
this.error("max depth exceeded");
|
|
}
|
|
else if (token.value === close)
|
|
--balance;
|
|
} while (balance);
|
|
};
|
|
|
|
function addField(object, seen, field, value) {
|
|
if (field.repeated) {
|
|
getField(object, field, []).push(value);
|
|
return;
|
|
}
|
|
if (Object.prototype.hasOwnProperty.call(seen, field.name))
|
|
throw Error(field.fullName + ": multiple values");
|
|
if (field.partOf) {
|
|
var oneofName = "$oneof:" + field.partOf.name;
|
|
if (seen[oneofName])
|
|
throw Error(field.partOf.name + ": multiple values");
|
|
seen[oneofName] = true;
|
|
}
|
|
seen[field.name] = true;
|
|
setField(object, field, value);
|
|
}
|
|
|
|
function getField(object, field, value) {
|
|
if (!Object.prototype.hasOwnProperty.call(object, field.name))
|
|
setField(object, field, value);
|
|
return object[field.name];
|
|
}
|
|
|
|
function setField(object, field, value) {
|
|
if (field.name === "__proto__")
|
|
util.makeProp(object, field.name);
|
|
object[field.name] = value;
|
|
}
|
|
|
|
function addMapEntry(field, map, entry) {
|
|
var keyField = mapKeyField(field),
|
|
valueField = mapValueField(field),
|
|
key = Object.prototype.hasOwnProperty.call(entry, "key")
|
|
? entry.key
|
|
: defaultScalarValue(keyField),
|
|
value = Object.prototype.hasOwnProperty.call(entry, "value")
|
|
? entry.value
|
|
: defaultMapValue(valueField);
|
|
var outKey = mapKey(keyField, key);
|
|
if (outKey === "__proto__")
|
|
util.makeProp(map, outKey);
|
|
map[outKey] = value;
|
|
}
|
|
|
|
function defaultMapValue(field) {
|
|
if (field.resolvedType instanceof Type)
|
|
return field.resolvedType.create();
|
|
if (field.resolvedType instanceof Enum)
|
|
return field.typeDefault;
|
|
return defaultScalarValue(field);
|
|
}
|
|
|
|
function defaultScalarValue(field) {
|
|
if (field.type === "string")
|
|
return "";
|
|
if (field.type === "bytes")
|
|
return util.newBuffer([]);
|
|
if (field.type === "bool")
|
|
return false;
|
|
return field.defaultValue;
|
|
}
|
|
|
|
function mapKeyField(field) {
|
|
return {
|
|
name: "key",
|
|
fullName: field.fullName + ".key",
|
|
type: field.keyType,
|
|
resolvedType: null,
|
|
defaultValue: field.keyType === "bool" ? false : field.keyType === "string" ? "" : 0
|
|
};
|
|
}
|
|
|
|
function mapValueField(field) {
|
|
return {
|
|
name: "value",
|
|
fullName: field.fullName + ".value",
|
|
type: field.type,
|
|
resolvedType: field.resolvedType,
|
|
defaultValue: field.resolvedType instanceof Enum ? field.resolvedType.values[Object.keys(field.resolvedType.values)[0]] : field.typeDefault
|
|
};
|
|
}
|
|
|
|
function mapKey(field, value) {
|
|
if (types.long[field.type] !== undefined)
|
|
return String(value);
|
|
if (field.type === "bool")
|
|
return value ? "true" : "false";
|
|
return String(value);
|
|
}
|
|
|
|
function lookupField(type, name) {
|
|
var fields = type.fieldsArray;
|
|
for (var i = 0; i < fields.length; ++i) {
|
|
var field = fields[i].resolve();
|
|
if (field.name === name || underScore(field.name) === name)
|
|
return field;
|
|
if (field.delimited && field.resolvedType instanceof Type) {
|
|
var groupName = field.resolvedType.name,
|
|
lowerName = name.toLowerCase(),
|
|
compactName = compact(name);
|
|
if (groupName === name || underScore(groupName) === name
|
|
|| field.name.toLowerCase() === lowerName
|
|
|| groupName.toLowerCase() === lowerName
|
|
|| compact(field.name) === compactName
|
|
|| compact(groupName) === compactName)
|
|
return field;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function lookupExtension(type, name) {
|
|
var fullName = name.charAt(0) === "." ? name : "." + name,
|
|
camelName = camelCaseLastPath(fullName),
|
|
field = type.get(fullName) || type.root.lookup(fullName, Field)
|
|
|| type.get(camelName) || type.root.lookup(camelName, Field);
|
|
if (!field)
|
|
field = lookupExtensionField(type, fullName);
|
|
if (field && field.extend !== undefined && field.extensionField)
|
|
field = field.extensionField;
|
|
if (!field || field.parent !== type)
|
|
throw Error(type.fullName + ": unknown extension '" + name + "'");
|
|
return field;
|
|
}
|
|
|
|
function camelCaseLastPath(name) {
|
|
var dot = name.lastIndexOf(".");
|
|
return name.substring(0, dot + 1) + util.camelCase(name.substring(dot + 1));
|
|
}
|
|
|
|
function parseEnum(field, token, sign) {
|
|
if (sign < 0) {
|
|
if (token.type !== "number")
|
|
throw Error(field.fullName + ": enum value expected");
|
|
return checkEnumValue(field, parseInteger(token, sign, false, 32));
|
|
}
|
|
if (token.type === "word") {
|
|
var value = field.resolvedType.values[token.value];
|
|
if (value === undefined)
|
|
throw Error(field.fullName + ": enum value expected");
|
|
return value;
|
|
}
|
|
return checkEnumValue(field, parseInteger(token, sign, false, 32));
|
|
}
|
|
|
|
function parseBool(token, sign) {
|
|
if (sign < 0)
|
|
throw Error("bool value expected");
|
|
if (token.type === "word") {
|
|
switch (token.value) {
|
|
case "true":
|
|
case "True":
|
|
case "t":
|
|
return true;
|
|
case "false":
|
|
case "False":
|
|
case "f":
|
|
return false;
|
|
}
|
|
} else if (token.type === "number") {
|
|
var value = parseInteger(token, 1, true, 32);
|
|
if (value === 0)
|
|
return false;
|
|
if (value === 1)
|
|
return true;
|
|
}
|
|
throw Error("bool value expected");
|
|
}
|
|
|
|
function parseFloatValue(token, sign) {
|
|
if (token.type === "word") {
|
|
switch (token.value.toLowerCase()) {
|
|
case "inf":
|
|
case "infinity":
|
|
return sign * Infinity;
|
|
case "nan":
|
|
return NaN;
|
|
}
|
|
throw Error("float value expected");
|
|
}
|
|
if (token.type !== "number")
|
|
throw Error("float value expected");
|
|
var raw = token.value;
|
|
if (/^0[xX]/.test(raw) || /^0[0-7]/.test(raw) && raw.length > 1)
|
|
throw Error("float value expected");
|
|
if (/[fF]$/.test(raw))
|
|
raw = raw.substring(0, raw.length - 1);
|
|
return sign * Number(raw);
|
|
}
|
|
|
|
function parseInteger(token, sign, unsigned, bits) {
|
|
if (token.type !== "number")
|
|
throw Error("integer value expected");
|
|
if (sign < 0 && unsigned)
|
|
throw Error("unsigned integer value expected");
|
|
|
|
var raw = token.value,
|
|
radix = 10,
|
|
digits = raw;
|
|
if (/^0[xX]/.test(raw)) {
|
|
radix = 16;
|
|
digits = raw.substring(2);
|
|
} else if (/^0[0-7]/.test(raw) && raw.length > 1) {
|
|
radix = 8;
|
|
digits = raw.substring(1);
|
|
} else if (!/^(?:0|[1-9][0-9]*)$/.test(raw))
|
|
throw Error("integer value expected");
|
|
|
|
if (typeof util.global.BigInt === "function")
|
|
return parseIntegerBigInt(digits, radix, sign, unsigned, bits);
|
|
|
|
var value = parseInt(digits, radix) * sign;
|
|
if (bits === 32) {
|
|
var min = unsigned ? 0 : -2147483648,
|
|
max = unsigned ? 4294967295 : 2147483647;
|
|
if (value < min || value > max)
|
|
throw Error((unsigned ? "unsigned " : "") + "integer value out of range");
|
|
}
|
|
if (bits === 64 && util.Long)
|
|
return util.Long.fromString(String(value), unsigned);
|
|
return value;
|
|
}
|
|
|
|
function parseIntegerBigInt(digits, radix, sign, unsigned, bits) {
|
|
var value,
|
|
BigInt = util.global.BigInt;
|
|
if (radix === 16)
|
|
value = BigInt("0x" + digits);
|
|
else if (radix === 8)
|
|
value = BigInt("0o" + digits);
|
|
else
|
|
value = BigInt(digits);
|
|
if (sign < 0)
|
|
value = -value;
|
|
|
|
var min,
|
|
max;
|
|
if (bits === 32) {
|
|
min = unsigned ? BigInt(0) : -BigInt(2147483648);
|
|
max = unsigned ? BigInt(4294967295) : BigInt(2147483647);
|
|
} else {
|
|
min = unsigned ? BigInt(0) : -BigInt("9223372036854775808");
|
|
max = unsigned ? BigInt("18446744073709551615") : BigInt("9223372036854775807");
|
|
}
|
|
if (value < min || value > max)
|
|
throw Error((unsigned ? "unsigned " : "") + "integer value out of range");
|
|
if (bits === 64 && util.Long)
|
|
return util.Long.fromString(value.toString(), unsigned, 10);
|
|
return Number(value);
|
|
}
|
|
|
|
function writeMessage(type, message, lines, indent, options, depth) {
|
|
if (depth > util.recursionLimit)
|
|
throw Error("max depth exceeded");
|
|
var fields = type.fieldsArray.slice().sort(util.compareFieldsById);
|
|
for (var i = 0; i < fields.length; ++i) {
|
|
var field = fields[i].resolve(),
|
|
value = message[field.name];
|
|
if (value == null || !Object.prototype.hasOwnProperty.call(message, field.name))
|
|
continue;
|
|
if (field.map)
|
|
writeMapField(field, value, lines, indent, options, depth);
|
|
else if (field.repeated)
|
|
for (var j = 0; j < value.length; ++j)
|
|
writeField(field, value[j], lines, indent, options, depth);
|
|
else
|
|
writeField(field, value, lines, indent, options, depth);
|
|
}
|
|
if (options.unknowns && message.$unknowns != null && Object.prototype.hasOwnProperty.call(message, "$unknowns"))
|
|
writeUnknowns(message.$unknowns, lines, indent);
|
|
}
|
|
|
|
function writeMapField(field, map, lines, indent, options, depth) {
|
|
var keys = Object.keys(map).sort(),
|
|
keyField = mapKeyField(field),
|
|
valueField = mapValueField(field),
|
|
name = formatFieldName(field),
|
|
sp = spaces(indent);
|
|
for (var i = 0; i < keys.length; ++i) {
|
|
if (depth + 1 > util.recursionLimit)
|
|
throw Error("max depth exceeded");
|
|
var key = keyField.long ? util.longFromKey(keys[i], keyField.type === "uint64" || keyField.type === "fixed64") : keys[i];
|
|
if (keyField.type === "bool")
|
|
key = util.boolFromKey(keys[i]);
|
|
lines.push(sp + name + " {");
|
|
lines.push(spaces(indent + 2) + "key: " + formatScalar(keyField, key));
|
|
if (valueField.resolvedType instanceof Type) {
|
|
lines.push(spaces(indent + 2) + "value {");
|
|
writeMessage(valueField.resolvedType, map[keys[i]], lines, indent + 4, options, depth + 2);
|
|
lines.push(spaces(indent + 2) + "}");
|
|
} else
|
|
lines.push(spaces(indent + 2) + "value: " + formatScalar(valueField, map[keys[i]]));
|
|
lines.push(sp + "}");
|
|
}
|
|
}
|
|
|
|
function writeField(field, value, lines, indent, options, depth) {
|
|
var name = formatFieldName(field),
|
|
sp = spaces(indent);
|
|
if (field.resolvedType instanceof Type) {
|
|
lines.push(sp + name + " {");
|
|
writeMessage(field.resolvedType, value, lines, indent + 2, options, depth + 1);
|
|
lines.push(sp + "}");
|
|
} else
|
|
lines.push(sp + name + ": " + formatScalar(field, value));
|
|
}
|
|
|
|
function writeUnknowns(unknowns, lines, indent) {
|
|
for (var i = 0; i < unknowns.length; ++i) {
|
|
var reader = Reader.create(unknowns[i]);
|
|
writeUnknownFieldSet(reader, lines, indent, undefined, textformat.unknownRecursionLimit);
|
|
if (reader.pos !== reader.len)
|
|
throw Error("invalid unknown field data");
|
|
}
|
|
}
|
|
|
|
function writeUnknownFieldSet(reader, lines, indent, endGroup, recursionBudget) {
|
|
if (recursionBudget < 0)
|
|
throw Error("max depth exceeded");
|
|
while (reader.pos < reader.len) {
|
|
var tag = reader.tag(),
|
|
fieldNumber = tag >>> 3,
|
|
wireType = tag & 7;
|
|
if (!fieldNumber)
|
|
throw Error("illegal tag: field number 0");
|
|
if (wireType === 4) {
|
|
if (fieldNumber !== endGroup)
|
|
throw Error("invalid end group tag");
|
|
return;
|
|
}
|
|
writeUnknownField(reader, fieldNumber, wireType, lines, indent, recursionBudget);
|
|
}
|
|
if (endGroup !== undefined)
|
|
throw Error("missing end group tag");
|
|
}
|
|
|
|
function writeUnknownField(reader, fieldNumber, wireType, lines, indent, recursionBudget) {
|
|
var sp = spaces(indent),
|
|
lo;
|
|
switch (wireType) {
|
|
case 0:
|
|
lines.push(sp + fieldNumber + ": " + readUnknownVarint(reader));
|
|
break;
|
|
case 1:
|
|
lo = reader.fixed32();
|
|
lines.push(sp + fieldNumber + ": 0x" + hexPad(reader.fixed32(), 8) + hexPad(lo, 8));
|
|
break;
|
|
case 2:
|
|
writeUnknownLengthDelimited(reader, fieldNumber, lines, indent, recursionBudget);
|
|
break;
|
|
case 3:
|
|
lines.push(sp + fieldNumber + " {");
|
|
writeUnknownFieldSet(reader, lines, indent + 2, fieldNumber, recursionBudget - 1);
|
|
lines.push(sp + "}");
|
|
break;
|
|
case 5:
|
|
lines.push(sp + fieldNumber + ": 0x" + hexPad(reader.fixed32(), 8));
|
|
break;
|
|
default:
|
|
throw Error("invalid wire type " + wireType);
|
|
}
|
|
}
|
|
|
|
function writeUnknownLengthDelimited(reader, fieldNumber, lines, indent, recursionBudget) {
|
|
var value = reader.bytes(),
|
|
nested = value.length && recursionBudget > 0
|
|
? tryFormatUnknownMessage(value, indent + 2, recursionBudget - 1)
|
|
: null,
|
|
sp = spaces(indent);
|
|
if (nested) {
|
|
lines.push(sp + fieldNumber + " {");
|
|
for (var i = 0; i < nested.length; ++i)
|
|
lines.push(nested[i]);
|
|
lines.push(sp + "}");
|
|
} else
|
|
lines.push(sp + fieldNumber + ": " + quoteBytes(value));
|
|
}
|
|
|
|
function tryFormatUnknownMessage(bytes, indent, recursionBudget) {
|
|
var reader = Reader.create(bytes),
|
|
lines = [];
|
|
try {
|
|
writeUnknownFieldSet(reader, lines, indent, undefined, recursionBudget);
|
|
return reader.pos === reader.len ? lines : null;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function readUnknownVarint(reader) {
|
|
var BigInt = util.global.BigInt;
|
|
if (typeof BigInt !== "function")
|
|
return String(reader.uint64());
|
|
|
|
var value = BigInt(0),
|
|
shift = BigInt(0),
|
|
b;
|
|
for (var i = 0; i < 10; ++i) {
|
|
if (reader.pos >= reader.len)
|
|
throw Error("invalid varint encoding");
|
|
b = reader.buf[reader.pos++];
|
|
value += BigInt(b & 127) << shift;
|
|
if (b < 128)
|
|
return value.toString();
|
|
shift += BigInt(7);
|
|
}
|
|
throw Error("invalid varint encoding");
|
|
}
|
|
|
|
function hexPad(value, length) {
|
|
var str = value.toString(16);
|
|
while (str.length < length)
|
|
str = "0" + str;
|
|
return str;
|
|
}
|
|
|
|
function formatFieldName(field) {
|
|
if (field.declaringField || field.name.charAt(0) === ".")
|
|
return "[" + formatExtensionName(field) + "]";
|
|
if (field.delimited && field.resolvedType instanceof Type && compact(field.name) === compact(field.resolvedType.name))
|
|
return field.resolvedType.name;
|
|
return underScore(field.name);
|
|
}
|
|
|
|
function formatExtensionName(field) {
|
|
var name = field.name.replace(/^\./, ""),
|
|
dot = name.lastIndexOf("."),
|
|
path = name.substring(0, dot + 1),
|
|
last = name.substring(dot + 1);
|
|
if (field.delimited && field.resolvedType instanceof Type && field.resolvedType.group)
|
|
last = last.toLowerCase();
|
|
else
|
|
last = underScore(last);
|
|
return path + last;
|
|
}
|
|
|
|
function formatScalar(field, value) {
|
|
if (field.resolvedType instanceof Enum) {
|
|
var name = field.resolvedType.valuesById[value];
|
|
return name === undefined ? String(value) : name;
|
|
}
|
|
switch (field.type) {
|
|
case "string":
|
|
return quoteBytes(utf8Bytes(String(value)));
|
|
case "bytes":
|
|
return quoteBytes(bytesValue(value));
|
|
case "bool":
|
|
return value ? "true" : "false";
|
|
case "double":
|
|
case "float":
|
|
if (isNaN(value))
|
|
return "nan";
|
|
if (value === 0 && 1 / value === -Infinity)
|
|
return "-0";
|
|
if (value === Infinity)
|
|
return "inf";
|
|
if (value === -Infinity)
|
|
return "-inf";
|
|
return String(value);
|
|
default:
|
|
return String(value);
|
|
}
|
|
}
|
|
|
|
function bytesValue(value) {
|
|
if (typeof value === "string") {
|
|
var length = util.base64.length(value),
|
|
buffer = util.newBuffer(length);
|
|
util.base64.decode(value, buffer, 0);
|
|
return buffer;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function quoteBytes(bytes) {
|
|
var out = "\"",
|
|
oct;
|
|
for (var i = 0; i < bytes.length; ++i) {
|
|
var b = bytes[i];
|
|
switch (b) {
|
|
case 9: out += "\\t"; break;
|
|
case 10: out += "\\n"; break;
|
|
case 13: out += "\\r"; break;
|
|
case 34: out += "\\\""; break;
|
|
case 92: out += "\\\\"; break;
|
|
default:
|
|
if (b >= 32 && b <= 126)
|
|
out += String.fromCharCode(b);
|
|
else {
|
|
oct = b.toString(8);
|
|
out += "\\" + (oct.length < 2 ? "00" : oct.length < 3 ? "0" : "") + oct;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return out + "\"";
|
|
}
|
|
|
|
function spaces(indent) {
|
|
var str = "";
|
|
while (str.length < indent)
|
|
str += " ";
|
|
return str;
|
|
}
|
|
|
|
function utf8Read(bytes, strict) {
|
|
if (strict && !validUtf8(bytes))
|
|
throw Error("invalid UTF-8 string");
|
|
var buffer = util.newBuffer(bytes);
|
|
return util.utf8.read(buffer, 0, buffer.length);
|
|
}
|
|
|
|
function validUtf8(bytes) {
|
|
for (var i = 0, b; i < bytes.length;) {
|
|
b = bytes[i++];
|
|
if (b <= 0x7F)
|
|
continue;
|
|
if (b >= 0xC2 && b <= 0xDF) {
|
|
if ((b = bytes[i++]) < 0x80 || b > 0xBF)
|
|
return false;
|
|
} else if (b === 0xE0) {
|
|
if ((b = bytes[i++]) < 0xA0 || b > 0xBF || (b = bytes[i++]) < 0x80 || b > 0xBF)
|
|
return false;
|
|
} else if (b >= 0xE1 && b <= 0xEC || b >= 0xEE && b <= 0xEF) {
|
|
if ((b = bytes[i++]) < 0x80 || b > 0xBF || (b = bytes[i++]) < 0x80 || b > 0xBF)
|
|
return false;
|
|
} else if (b === 0xED) {
|
|
if ((b = bytes[i++]) < 0x80 || b > 0x9F || (b = bytes[i++]) < 0x80 || b > 0xBF)
|
|
return false;
|
|
} else if (b === 0xF0) {
|
|
if ((b = bytes[i++]) < 0x90 || b > 0xBF || (b = bytes[i++]) < 0x80 || b > 0xBF || (b = bytes[i++]) < 0x80 || b > 0xBF)
|
|
return false;
|
|
} else if (b >= 0xF1 && b <= 0xF3) {
|
|
if ((b = bytes[i++]) < 0x80 || b > 0xBF || (b = bytes[i++]) < 0x80 || b > 0xBF || (b = bytes[i++]) < 0x80 || b > 0xBF)
|
|
return false;
|
|
} else if (b === 0xF4) {
|
|
if ((b = bytes[i++]) < 0x80 || b > 0x8F || (b = bytes[i++]) < 0x80 || b > 0xBF || (b = bytes[i++]) < 0x80 || b > 0xBF)
|
|
return false;
|
|
} else
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function utf8Bytes(str) {
|
|
var buffer = util.newBuffer(util.utf8.length(str));
|
|
util.utf8.write(str, buffer, 0);
|
|
return buffer;
|
|
}
|
|
|
|
function pushUtf8(bytes, code) {
|
|
if (code < 0x80) {
|
|
bytes.push(code);
|
|
} else if (code < 0x800) {
|
|
bytes.push(code >> 6 | 192, code & 63 | 128);
|
|
} else if (code < 0x10000) {
|
|
bytes.push(code >> 12 | 224, code >> 6 & 63 | 128, code & 63 | 128);
|
|
} else {
|
|
bytes.push(code >> 18 | 240, code >> 12 & 63 | 128, code >> 6 & 63 | 128, code & 63 | 128);
|
|
}
|
|
}
|
|
|
|
function verifyTextMessage(type, message) {
|
|
var fields = type.fieldsArray;
|
|
for (var i = 0; i < fields.length; ++i) {
|
|
var field = fields[i].resolve(),
|
|
value = message[field.name];
|
|
if (value == null || !Object.prototype.hasOwnProperty.call(message, field.name)) {
|
|
if (field.required)
|
|
return field.fullName + ": missing required field";
|
|
continue;
|
|
}
|
|
if (field.map) {
|
|
if (field.resolvedType instanceof Type)
|
|
for (var key in value)
|
|
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
var mapErr = verifyTextMessage(field.resolvedType, value[key]);
|
|
if (mapErr)
|
|
return mapErr;
|
|
}
|
|
} else if (field.repeated) {
|
|
if (field.resolvedType instanceof Type)
|
|
for (var j = 0; j < value.length; ++j) {
|
|
var repeatedErr = verifyTextMessage(field.resolvedType, value[j]);
|
|
if (repeatedErr)
|
|
return repeatedErr;
|
|
}
|
|
} else if (field.resolvedType instanceof Type) {
|
|
var err = verifyTextMessage(field.resolvedType, value);
|
|
if (err)
|
|
return err;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function isSeparator(token) {
|
|
return token.value === "," || token.value === ";";
|
|
}
|
|
|
|
function validateTypeUrl(typeUrl) {
|
|
for (var i = 0; i < typeUrl.length; ++i)
|
|
if (typeUrl.charAt(i) === "%") {
|
|
if (i + 2 >= typeUrl.length || !hexRe.test(typeUrl.charAt(i + 1)) || !hexRe.test(typeUrl.charAt(i + 2)))
|
|
throw Error("invalid Any type URL");
|
|
i += 2;
|
|
}
|
|
}
|
|
|
|
function lookupExtensionField(type, fullName) {
|
|
var fields = type.fieldsArray;
|
|
for (var i = 0; i < fields.length; ++i) {
|
|
var field = fields[i].resolve();
|
|
if (field.name.charAt(0) === "." && "." + formatExtensionName(field) === fullName)
|
|
return field;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function checkEnumValue(field, value) {
|
|
if (isClosedEnum(field) && field.resolvedType.valuesById[value] === undefined)
|
|
throw Error(field.fullName + ": enum value expected");
|
|
return value;
|
|
}
|
|
|
|
function isClosedEnum(field) {
|
|
return field.resolvedType
|
|
&& field.resolvedType._features
|
|
&& field.resolvedType._features.enum_type === "CLOSED";
|
|
}
|
|
|
|
function compact(str) {
|
|
return str.replace(/_/g, "").toLowerCase();
|
|
}
|
|
|
|
function underScore(str) {
|
|
return str.substring(0, 1)
|
|
+ str.substring(1)
|
|
.replace(/([A-Z])(?=[a-z]|$)/g, function($0, $1) { return "_" + $1.toLowerCase(); });
|
|
}
|