pbjs
Version:
A minimal implementation of Google Protocol Buffers for JavaScript
985 lines (984 loc) • 46 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
function quote(value) {
return JSON.stringify(value, null, 2);
}
function generate(schema, options) {
options = options || {};
let pkg = 'exports';
const typescript = !!options.typescript;
const es6 = typescript || !!options.es6;
const varOrLet = es6 ? 'let' : 'var';
const prefix = es6 ? '' : pkg + '.';
const enums = {};
const lines = [];
const packableTypes = {
'bool': true,
'double': true,
'fixed32': true,
'fixed64': true,
'float': true,
'int32': true,
'int64': true,
'sfixed32': true,
'sfixed64': true,
'sint32': true,
'sint64': true,
'uint32': true,
'uint64': true,
};
function ts(code) {
return typescript ? ': ' + code : '';
}
const TYPE_VAR_INT = 0;
const TYPE_SIZE_8 = 1;
const TYPE_SIZE_N = 2;
const TYPE_SIZE_4 = 5;
function codeForEnumExport(name) {
return es6 ? `export const ${name}` : `${pkg}.${name}`;
}
for (const def of schema.enums) {
const prefix = def.name + '_';
const items = [];
const encode = [];
const decode = [];
for (let key in def.values) {
const value = def.values[key];
// Protocol buffers made the stupid decision to use C-style enum scoping
// rules. Attempt to fix this by stripping the "EnumType_" prefix if present.
if (key.slice(0, prefix.length) === prefix) {
key = key.slice(prefix.length);
}
items.push(` ${key} = ${quote(key)},`);
encode.push(` ${key}: ${value.value},`);
decode.push(` ${value.value}: ${typescript ? `${def.name}.${key}` : quote(key)},`);
}
if (typescript) {
lines.push(`export const enum ${def.name} {`);
lines.push.apply(lines, items);
lines.push(`}`);
lines.push(``);
}
lines.push(`${codeForEnumExport('encode' + def.name)}${ts(`{ [key: string]: number }`)} = {`);
lines.push.apply(lines, encode);
lines.push(`};`);
lines.push('');
lines.push(`${codeForEnumExport('decode' + def.name)}${ts(`{ [key: number]: ${def.name} }`)} = {`);
lines.push.apply(lines, decode);
lines.push(`};`);
lines.push('');
enums[def.name] = true;
packableTypes[def.name] = true;
}
// Validate the "packed" option once
for (const def of schema.messages) {
for (const field of def.fields) {
if (field.options.packed === 'true' && (!field.repeated || !(field.type in packableTypes))) {
throw new Error(field.name + ': [packed = true] can only be specified for repeated primitive fields');
}
}
}
function codeForFunctionExport(name) {
return es6 ? `export function ${name}` : `${pkg}.${name} = function `;
}
const protoToType = {
bool: 'boolean',
bytes: 'Uint8Array',
double: 'number',
fixed32: 'number',
float: 'number',
int32: 'number',
sfixed32: 'number',
sint32: 'number',
uint32: 'number',
fixed64: 'Long',
int64: 'Long',
sfixed64: 'Long',
sint64: 'Long',
uint64: 'Long',
};
for (const def of schema.messages) {
if (typescript) {
lines.push(`export interface ${def.name} {`);
for (const field of def.fields) {
let type = protoToType[field.type] || field.type;
if (field.map !== null) {
let { from, to } = field.map;
if (from === 'bool' || from === 'string')
from = 'string';
else if (protoToType[from] === 'number')
from = 'number';
else if (protoToType[from] === 'Long')
from = 'string';
else
throw new Error(`The type ${from} cannot be used as a map key`);
type = `{ [key: ${from}]: ${protoToType[to] || to} }`;
}
const required = field.required ? '' : '?';
const repeated = field.repeated ? '[]' : '';
lines.push(` ${field.name}${required}: ${type}${repeated};`);
}
lines.push(`}`);
lines.push(``);
}
lines.push(`${codeForFunctionExport('encode' + def.name)}(message${ts(def.name)})${ts('Uint8Array')} {`);
lines.push(` ${varOrLet} bb = popByteBuffer();`);
lines.push(` _encode${def.name}(message, bb);`);
lines.push(` return toUint8Array(bb);`);
lines.push(`}`);
lines.push(``);
lines.push(`function _encode${def.name}(message${ts(def.name)}, bb${ts('ByteBuffer')})${ts('void')} {`);
function encodeValue(name, buffer, value, nested = 'nested') {
let type;
let write;
switch (name) {
case 'bool':
type = TYPE_VAR_INT;
write = [`writeByte(${buffer}, ${value} ? 1 : 0)`];
break;
case 'bytes':
type = TYPE_SIZE_N;
write = [`writeVarint32(${buffer}, ${value}.length), writeBytes(${buffer}, ${value})`];
break;
case 'double':
type = TYPE_SIZE_8;
write = [`writeDouble(${buffer}, ${value})`];
break;
case 'fixed32':
type = TYPE_SIZE_4;
write = [`writeInt32(${buffer}, ${value})`];
break;
case 'fixed64':
type = TYPE_SIZE_8;
write = [`writeInt64(${buffer}, ${value})`];
break;
case 'float':
type = TYPE_SIZE_4;
write = [`writeFloat(${buffer}, ${value})`];
break;
case 'int32':
type = TYPE_VAR_INT;
write = [`writeVarint64(${buffer}, intToLong(${value}))`];
break;
case 'int64':
type = TYPE_VAR_INT;
write = [`writeVarint64(${buffer}, ${value})`];
break;
case 'sfixed32':
type = TYPE_SIZE_4;
write = [`writeInt32(${buffer}, ${value})`];
break;
case 'sfixed64':
type = TYPE_SIZE_8;
write = [`writeInt64(${buffer}, ${value})`];
break;
case 'sint32':
type = TYPE_VAR_INT;
write = [`writeVarint32ZigZag(${buffer}, ${value})`];
break;
case 'sint64':
type = TYPE_VAR_INT;
write = [`writeVarint64ZigZag(${buffer}, ${value})`];
break;
case 'uint32':
type = TYPE_VAR_INT;
write = [`writeVarint32(${buffer}, ${value})`];
break;
case 'uint64':
type = TYPE_VAR_INT;
write = [`writeVarint64(${buffer}, ${value})`];
break;
case 'string':
type = TYPE_SIZE_N;
write = [`writeString(${buffer}, ${value})`];
break;
default: {
if (name in enums) {
type = TYPE_VAR_INT;
write = [`writeVarint32(${buffer}, ${prefix}encode${name}[${value}])`];
}
else {
type = TYPE_SIZE_N;
write = [
`${varOrLet} ${nested} = popByteBuffer()`,
`_encode${name}(${value}, ${nested})`,
`writeVarint32(${buffer}, ${nested}.limit)`,
`writeByteBuffer(${buffer}, ${nested})`,
`pushByteBuffer(${nested})`,
];
}
break;
}
}
return { type, write };
}
for (const field of def.fields) {
const isPacked = field.repeated && field.options.packed !== 'false' && field.type in packableTypes;
const modifier = field.repeated ? 'repeated ' : field.required ? 'required ' : 'optional ';
let humanType = field.type;
if (field.map !== null)
humanType += `<${field.map.from}, ${field.map.to}>`;
lines.push(` // ${modifier}${humanType} ${field.name} = ${field.tag};`);
if (field.repeated || field.map !== null) {
const collection = `${field.repeated ? 'array' : 'map'}$${field.name}`;
lines.push(` ${varOrLet} ${collection} = message.${field.name};`);
lines.push(` if (${collection} !== undefined) {`);
if (field.map !== null) {
let { from, to } = field.map;
let keyValue = 'key';
if (from === 'bool')
keyValue = 'key === "true"';
else if (protoToType[from] === 'number')
keyValue = '+key';
else if (protoToType[from] === 'Long')
keyValue = 'stringToLong(key)';
const key = encodeValue(from, 'nested', keyValue, 'nestedKey');
const value = encodeValue(to, 'nested', 'value', 'nestedValue');
lines.push(` for (${varOrLet} key in ${collection}) {`);
lines.push(` ${varOrLet} nested = popByteBuffer();`);
lines.push(` ${varOrLet} value = ${collection}[key];`);
lines.push(` writeVarint32(nested, ${(1 << 3) | key.type});`);
for (const line of key.write)
lines.push(` ${line};`);
lines.push(` writeVarint32(nested, ${(2 << 3) | value.type});`);
for (const line of value.write)
lines.push(` ${line};`);
lines.push(` writeVarint32(bb, ${(field.tag << 3) | TYPE_SIZE_N});`);
lines.push(` writeVarint32(bb, nested.offset);`);
lines.push(` writeByteBuffer(bb, nested);`);
lines.push(` pushByteBuffer(nested);`);
lines.push(` }`);
}
else if (isPacked) {
const { write } = encodeValue(field.type, 'packed', 'value');
lines.push(` ${varOrLet} packed = popByteBuffer();`);
if (es6) {
lines.push(` for (let value of ${collection}) {`);
}
else {
lines.push(` for (var i = 0; i < ${collection}.length; i++) {`);
lines.push(` var value = ${collection}[i];`);
}
for (const line of write)
lines.push(` ${line};`);
lines.push(` }`);
lines.push(` writeVarint32(bb, ${(field.tag << 3) | TYPE_SIZE_N});`);
lines.push(` writeVarint32(bb, packed.offset);`);
lines.push(` writeByteBuffer(bb, packed);`);
lines.push(` pushByteBuffer(packed);`);
}
else {
const { type, write } = encodeValue(field.type, 'bb', 'value');
if (es6) {
lines.push(` for (let value of ${collection}) {`);
}
else {
lines.push(` for (var i = 0; i < ${collection}.length; i++) {`);
lines.push(` var value = ${collection}[i];`);
}
lines.push(` writeVarint32(bb, ${(field.tag << 3) | type});`);
for (const line of write)
lines.push(` ${line};`);
lines.push(` }`);
}
lines.push(` }`);
lines.push(``);
}
else {
const value = `$${field.name}`;
const { type, write } = encodeValue(field.type, 'bb', value);
lines.push(` ${varOrLet} ${value} = message.${field.name};`);
lines.push(` if (${value} !== undefined) {`);
lines.push(` writeVarint32(bb, ${(field.tag << 3) | type});`);
for (const line of write)
lines.push(` ${line};`);
lines.push(` }`);
lines.push(``);
}
}
if (def.fields.length > 0) {
lines.pop();
}
lines.push(es6 ? '}' : '};');
lines.push(``);
lines.push(`${codeForFunctionExport('decode' + def.name)}(binary${ts('Uint8Array')})${ts(def.name)} {`);
lines.push(` return _decode${def.name}(wrapByteBuffer(binary));`);
lines.push(`}`);
lines.push(``);
lines.push(`function _decode${def.name}(bb${ts('ByteBuffer')})${ts(def.name)} {`);
lines.push(` ${varOrLet} message${ts(def.name)} = {}${typescript ? ' as any' : ''};`);
lines.push(``);
lines.push(` end_of_message: while (!isAtEnd(bb)) {`);
lines.push(` ${varOrLet} tag = readVarint32(bb);`);
lines.push(``);
lines.push(` switch (tag >>> 3) {`);
lines.push(` case 0:`);
lines.push(` break end_of_message;`);
lines.push(``);
function decodeValue(name, limit = 'limit') {
let read;
let before = null;
let after = null;
switch (name) {
case 'bool':
read = `!!readByte(bb)`;
break;
case 'bytes':
read = `readBytes(bb, readVarint32(bb))`;
break;
case 'double':
read = `readDouble(bb)`;
break;
case 'fixed32':
read = `readInt32(bb) >>> 0`;
break;
case 'fixed64':
read = `readInt64(bb, /* unsigned */ true)`;
break;
case 'float':
read = `readFloat(bb)`;
break;
case 'int32':
read = `readVarint32(bb)`;
break;
case 'int64':
read = `readVarint64(bb, /* unsigned */ false)`;
break;
case 'sfixed32':
read = `readInt32(bb)`;
break;
case 'sfixed64':
read = `readInt64(bb, /* unsigned */ false)`;
break;
case 'sint32':
read = `readVarint32ZigZag(bb)`;
break;
case 'sint64':
read = `readVarint64ZigZag(bb)`;
break;
case 'string':
read = `readString(bb, readVarint32(bb))`;
break;
case 'uint32':
read = `readVarint32(bb) >>> 0`;
break;
case 'uint64':
read = `readVarint64(bb, /* unsigned */ true)`;
break;
default: {
if (name in enums) {
read = `${prefix}decode${name}[readVarint32(bb)]`;
}
else {
before = `${varOrLet} ${limit} = pushTemporaryLength(bb)`;
read = `_decode${name}(bb)`;
after = `bb.limit = ${limit}`;
}
break;
}
}
return { read, before, after };
}
for (const field of def.fields) {
const modifier = field.repeated ? 'repeated ' : field.required ? 'required ' : 'optional ';
let humanType = field.type;
if (field.map !== null)
humanType += `<${field.map.from}, ${field.map.to}>`;
lines.push(` // ${modifier}${humanType} ${field.name} = ${field.tag};`);
lines.push(` case ${field.tag}: {`);
if (field.map !== null) {
const { from, to } = field.map;
const key = decodeValue(from, 'keyLimit');
const value = decodeValue(to, 'valueLimit');
lines.push(` ${varOrLet} values = message.${field.name} || (message.${field.name} = {});`);
lines.push(` ${varOrLet} outerLimit = pushTemporaryLength(bb);`);
lines.push(` ${varOrLet} key${ts(`${protoToType[from] || from} | undefined`)};`);
lines.push(` ${varOrLet} value${ts(`${protoToType[to] || to} | undefined`)};`);
lines.push(` end_of_entry: while (!isAtEnd(bb)) {`);
lines.push(` ${varOrLet} tag = readVarint32(bb);`);
lines.push(` switch (tag >>> 3) {`);
lines.push(` case 0:`);
lines.push(` break end_of_entry;`);
lines.push(` case 1: {`);
if (key.before)
lines.push(` ${key.before};`);
lines.push(` key = ${key.read};`);
if (key.after)
lines.push(` ${key.after};`);
lines.push(` break;`);
lines.push(` }`);
lines.push(` case 2: {`);
if (value.before)
lines.push(` ${value.before};`);
lines.push(` value = ${value.read};`);
if (value.after)
lines.push(` ${value.after};`);
lines.push(` break;`);
lines.push(` }`);
lines.push(` default:`);
lines.push(` skipUnknownField(bb, tag & 7);`);
lines.push(` }`);
lines.push(` }`);
lines.push(` if (key === undefined || value === undefined)`);
lines.push(` throw new Error(${quote(`Invalid data for map: ${field.name}`)});`);
if (from === 'bool')
lines.push(` values[key + ''] = value;`);
else if (protoToType[from] === 'Long')
lines.push(` values[longToString(key)] = value;`);
else
lines.push(` values[key] = value;`);
lines.push(` bb.limit = outerLimit;`);
}
else if (field.repeated) {
const { read, before, after } = decodeValue(field.type);
if (before)
lines.push(` ${before};`);
lines.push(` ${varOrLet} values = message.${field.name} || (message.${field.name} = []);`);
// Support both packed and unpacked encodings for primitive types
if (field.type in packableTypes) {
lines.push(` if ((tag & 7) === ${TYPE_SIZE_N}) {`);
lines.push(` ${varOrLet} outerLimit = pushTemporaryLength(bb);`);
lines.push(` while (!isAtEnd(bb)) {`);
lines.push(` values.push(${read});`);
if (after)
lines.push(` ${after};`);
lines.push(` }`);
lines.push(` bb.limit = outerLimit;`);
lines.push(` } else {`);
lines.push(` values.push(${read});`);
if (after)
lines.push(` ${after};`);
lines.push(` }`);
}
else {
lines.push(` values.push(${read});`);
if (after)
lines.push(` ${after};`);
}
}
else {
const { read, before, after } = decodeValue(field.type);
if (before)
lines.push(` ${before};`);
lines.push(` message.${field.name} = ${read};`);
if (after)
lines.push(` ${after};`);
}
lines.push(` break;`);
lines.push(` }`);
lines.push(``);
}
lines.push(` default:`);
lines.push(` skipUnknownField(bb, tag & 7);`);
lines.push(` }`);
lines.push(` }`);
lines.push(``);
for (const field of def.fields) {
if (field.required) {
lines.push(` if (message.${field.name} === undefined)`);
lines.push(` throw new Error(${quote(`Missing required field: ${field.name}`)});`);
lines.push(``);
}
}
lines.push(` return message;`);
lines.push(es6 ? '}' : '};');
lines.push(``);
}
if (typescript) {
lines.push(`export interface Long {`);
lines.push(` low: number;`);
lines.push(` high: number;`);
lines.push(` unsigned: boolean;`);
lines.push(`}`);
lines.push(``);
lines.push(`interface ByteBuffer {`);
lines.push(` bytes: Uint8Array;`);
lines.push(` offset: number;`);
lines.push(` limit: number;`);
lines.push(`}`);
lines.push(``);
}
lines.push(`function pushTemporaryLength(bb${ts('ByteBuffer')})${ts('number')} {`);
lines.push(` ${varOrLet} length = readVarint32(bb);`);
lines.push(` ${varOrLet} limit = bb.limit;`);
lines.push(` bb.limit = bb.offset + length;`);
lines.push(` return limit;`);
lines.push(`}`);
lines.push(``);
lines.push(`function skipUnknownField(bb${ts('ByteBuffer')}, type${ts('number')})${ts('void')} {`);
lines.push(` switch (type) {`);
lines.push(` case ${TYPE_VAR_INT}: while (readByte(bb) & 0x80) { } break;`);
lines.push(` case ${TYPE_SIZE_N}: skip(bb, readVarint32(bb)); break;`);
lines.push(` case ${TYPE_SIZE_4}: skip(bb, 4); break;`);
lines.push(` case ${TYPE_SIZE_8}: skip(bb, 8); break;`);
lines.push(` default: throw new Error("Unimplemented type: " + type);`);
lines.push(` }`);
lines.push(`}`);
lines.push(``);
lines.push(`function stringToLong(value${ts('string')})${ts('Long')} {`);
lines.push(` return {`);
lines.push(` low: value.charCodeAt(0) | (value.charCodeAt(1) << 16),`);
lines.push(` high: value.charCodeAt(2) | (value.charCodeAt(3) << 16),`);
lines.push(` unsigned: false,`);
lines.push(` };`);
lines.push(`}`);
lines.push(``);
lines.push(`function longToString(value${ts('Long')})${ts('string')} {`);
lines.push(` ${varOrLet} low = value.low;`);
lines.push(` ${varOrLet} high = value.high;`);
lines.push(` return String.fromCharCode(`);
lines.push(` low & 0xFFFF,`);
lines.push(` low >>> 16,`);
lines.push(` high & 0xFFFF,`);
lines.push(` high >>> 16);`);
lines.push(`}`);
lines.push(``);
lines.push(`// The code below was modified from https://github.com/protobufjs/bytebuffer.js`);
lines.push(`// which is under the Apache License 2.0.`);
lines.push(``);
lines.push(`${varOrLet} f32 = new Float32Array(1);`);
lines.push(`${varOrLet} f32_u8 = new Uint8Array(f32.buffer);`);
lines.push(``);
lines.push(`${varOrLet} f64 = new Float64Array(1);`);
lines.push(`${varOrLet} f64_u8 = new Uint8Array(f64.buffer);`);
lines.push(``);
lines.push(`function intToLong(value${ts('number')})${ts('Long')} {`);
lines.push(` value |= 0;`);
lines.push(` return {`);
lines.push(` low: value,`);
lines.push(` high: value >> 31,`);
lines.push(` unsigned: value >= 0,`);
lines.push(` };`);
lines.push(`}`);
lines.push(``);
lines.push(`${varOrLet} bbStack${ts('ByteBuffer[]')} = [];`);
lines.push(``);
lines.push(`function popByteBuffer()${ts('ByteBuffer')} {`);
lines.push(` const bb = bbStack.pop();`);
lines.push(` if (!bb) return { bytes: new Uint8Array(64), offset: 0, limit: 0 };`);
lines.push(` bb.offset = bb.limit = 0;`);
lines.push(` return bb;`);
lines.push(`}`);
lines.push(``);
lines.push(`function pushByteBuffer(bb${ts('ByteBuffer')})${ts('void')} {`);
lines.push(` bbStack.push(bb);`);
lines.push(`}`);
lines.push(``);
lines.push(`function wrapByteBuffer(bytes${typescript ? ': Uint8Array' : ''})${ts('ByteBuffer')} {`);
lines.push(` return { bytes, offset: 0, limit: bytes.length };`);
lines.push(`}`);
lines.push(``);
lines.push(`function toUint8Array(bb${ts('ByteBuffer')})${ts('Uint8Array')} {`);
lines.push(` ${varOrLet} bytes = bb.bytes;`);
lines.push(` ${varOrLet} limit = bb.limit;`);
lines.push(` return bytes.length === limit ? bytes : bytes.subarray(0, limit);`);
lines.push(`}`);
lines.push(``);
lines.push(`function skip(bb${ts('ByteBuffer')}, offset${ts('number')})${ts('void')} {`);
lines.push(` if (bb.offset + offset > bb.limit) {`);
lines.push(` throw new Error('Skip past limit');`);
lines.push(` }`);
lines.push(` bb.offset += offset;`);
lines.push(`}`);
lines.push(``);
lines.push(`function isAtEnd(bb${ts('ByteBuffer')})${ts('boolean')} {`);
lines.push(` return bb.offset >= bb.limit;`);
lines.push(`}`);
lines.push(``);
lines.push(`function grow(bb${ts('ByteBuffer')}, count${ts('number')})${ts('number')} {`);
lines.push(` ${varOrLet} bytes = bb.bytes;`);
lines.push(` ${varOrLet} offset = bb.offset;`);
lines.push(` ${varOrLet} limit = bb.limit;`);
lines.push(` ${varOrLet} finalOffset = offset + count;`);
lines.push(` if (finalOffset > bytes.length) {`);
lines.push(` ${varOrLet} newBytes = new Uint8Array(finalOffset * 2);`);
lines.push(` newBytes.set(bytes);`);
lines.push(` bb.bytes = newBytes;`);
lines.push(` }`);
lines.push(` bb.offset = finalOffset;`);
lines.push(` if (finalOffset > limit) {`);
lines.push(` bb.limit = finalOffset;`);
lines.push(` }`);
lines.push(` return offset;`);
lines.push(`}`);
lines.push(``);
lines.push(`function advance(bb${ts('ByteBuffer')}, count${ts('number')})${ts('number')} {`);
lines.push(` ${varOrLet} offset = bb.offset;`);
lines.push(` if (offset + count > bb.limit) {`);
lines.push(` throw new Error('Read past limit');`);
lines.push(` }`);
lines.push(` bb.offset += count;`);
lines.push(` return offset;`);
lines.push(`}`);
lines.push(``);
lines.push(`function readBytes(bb${ts('ByteBuffer')}, count${ts('number')})${ts('Uint8Array')} {`);
lines.push(` ${varOrLet} offset = advance(bb, count);`);
lines.push(` return bb.bytes.subarray(offset, offset + count);`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeBytes(bb${ts('ByteBuffer')}, buffer${ts('Uint8Array')})${ts('void')} {`);
lines.push(` ${varOrLet} offset = grow(bb, buffer.length);`);
lines.push(` bb.bytes.set(buffer, offset);`);
lines.push(`}`);
lines.push(``);
lines.push(`function readString(bb${ts('ByteBuffer')}, count${ts('number')})${ts('string')} {`);
lines.push(` // Sadly a hand-coded UTF8 decoder is much faster than subarray+TextDecoder in V8`);
lines.push(` ${varOrLet} offset = advance(bb, count);`);
lines.push(` ${varOrLet} fromCharCode = String.fromCharCode;`);
lines.push(` ${varOrLet} bytes = bb.bytes;`);
lines.push(` ${varOrLet} invalid = '\\uFFFD';`);
lines.push(` ${varOrLet} text = '';`);
lines.push(``);
lines.push(` for (${varOrLet} i = 0; i < count; i++) {`);
lines.push(` ${varOrLet} c1 = bytes[i + offset], c2${ts('number')}, c3${ts('number')}, c4${ts('number')}, c${ts('number')};`);
lines.push(``);
lines.push(` // 1 byte`);
lines.push(` if ((c1 & 0x80) === 0) {`);
lines.push(` text += fromCharCode(c1);`);
lines.push(` }`);
lines.push(``);
lines.push(` // 2 bytes`);
lines.push(` else if ((c1 & 0xE0) === 0xC0) {`);
lines.push(` if (i + 1 >= count) text += invalid;`);
lines.push(` else {`);
lines.push(` c2 = bytes[i + offset + 1];`);
lines.push(` if ((c2 & 0xC0) !== 0x80) text += invalid;`);
lines.push(` else {`);
lines.push(` c = ((c1 & 0x1F) << 6) | (c2 & 0x3F);`);
lines.push(` if (c < 0x80) text += invalid;`);
lines.push(` else {`);
lines.push(` text += fromCharCode(c);`);
lines.push(` i++;`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(``);
lines.push(` // 3 bytes`);
lines.push(` else if ((c1 & 0xF0) == 0xE0) {`);
lines.push(` if (i + 2 >= count) text += invalid;`);
lines.push(` else {`);
lines.push(` c2 = bytes[i + offset + 1];`);
lines.push(` c3 = bytes[i + offset + 2];`);
lines.push(` if (((c2 | (c3 << 8)) & 0xC0C0) !== 0x8080) text += invalid;`);
lines.push(` else {`);
lines.push(` c = ((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);`);
lines.push(` if (c < 0x0800 || (c >= 0xD800 && c <= 0xDFFF)) text += invalid;`);
lines.push(` else {`);
lines.push(` text += fromCharCode(c);`);
lines.push(` i += 2;`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(``);
lines.push(` // 4 bytes`);
lines.push(` else if ((c1 & 0xF8) == 0xF0) {`);
lines.push(` if (i + 3 >= count) text += invalid;`);
lines.push(` else {`);
lines.push(` c2 = bytes[i + offset + 1];`);
lines.push(` c3 = bytes[i + offset + 2];`);
lines.push(` c4 = bytes[i + offset + 3];`);
lines.push(` if (((c2 | (c3 << 8) | (c4 << 16)) & 0xC0C0C0) !== 0x808080) text += invalid;`);
lines.push(` else {`);
lines.push(` c = ((c1 & 0x07) << 0x12) | ((c2 & 0x3F) << 0x0C) | ((c3 & 0x3F) << 0x06) | (c4 & 0x3F);`);
lines.push(` if (c < 0x10000 || c > 0x10FFFF) text += invalid;`);
lines.push(` else {`);
lines.push(` c -= 0x10000;`);
lines.push(` text += fromCharCode((c >> 10) + 0xD800, (c & 0x3FF) + 0xDC00);`);
lines.push(` i += 3;`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(``);
lines.push(` else text += invalid;`);
lines.push(` }`);
lines.push(``);
lines.push(` return text;`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeString(bb${ts('ByteBuffer')}, text${ts('string')})${ts('void')} {`);
lines.push(` // Sadly a hand-coded UTF8 encoder is much faster than TextEncoder+set in V8`);
lines.push(` ${varOrLet} n = text.length;`);
lines.push(` ${varOrLet} byteCount = 0;`);
lines.push(``);
lines.push(` // Write the byte count first`);
lines.push(` for (${varOrLet} i = 0; i < n; i++) {`);
lines.push(` ${varOrLet} c = text.charCodeAt(i);`);
lines.push(` if (c >= 0xD800 && c <= 0xDBFF && i + 1 < n) {`);
lines.push(` c = (c << 10) + text.charCodeAt(++i) - 0x35FDC00;`);
lines.push(` }`);
lines.push(` byteCount += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;`);
lines.push(` }`);
lines.push(` writeVarint32(bb, byteCount);`);
lines.push(``);
lines.push(` ${varOrLet} offset = grow(bb, byteCount);`);
lines.push(` ${varOrLet} bytes = bb.bytes;`);
lines.push(``);
lines.push(` // Then write the bytes`);
lines.push(` for (${varOrLet} i = 0; i < n; i++) {`);
lines.push(` ${varOrLet} c = text.charCodeAt(i);`);
lines.push(` if (c >= 0xD800 && c <= 0xDBFF && i + 1 < n) {`);
lines.push(` c = (c << 10) + text.charCodeAt(++i) - 0x35FDC00;`);
lines.push(` }`);
lines.push(` if (c < 0x80) {`);
lines.push(` bytes[offset++] = c;`);
lines.push(` } else {`);
lines.push(` if (c < 0x800) {`);
lines.push(` bytes[offset++] = ((c >> 6) & 0x1F) | 0xC0;`);
lines.push(` } else {`);
lines.push(` if (c < 0x10000) {`);
lines.push(` bytes[offset++] = ((c >> 12) & 0x0F) | 0xE0;`);
lines.push(` } else {`);
lines.push(` bytes[offset++] = ((c >> 18) & 0x07) | 0xF0;`);
lines.push(` bytes[offset++] = ((c >> 12) & 0x3F) | 0x80;`);
lines.push(` }`);
lines.push(` bytes[offset++] = ((c >> 6) & 0x3F) | 0x80;`);
lines.push(` }`);
lines.push(` bytes[offset++] = (c & 0x3F) | 0x80;`);
lines.push(` }`);
lines.push(` }`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeByteBuffer(bb${ts('ByteBuffer')}, buffer${ts('ByteBuffer')})${ts('void')} {`);
lines.push(` ${varOrLet} offset = grow(bb, buffer.limit);`);
lines.push(` ${varOrLet} from = bb.bytes;`);
lines.push(` ${varOrLet} to = buffer.bytes;`);
lines.push(``);
lines.push(` // This for loop is much faster than subarray+set on V8`);
lines.push(` for (${varOrLet} i = 0, n = buffer.limit; i < n; i++) {`);
lines.push(` from[i + offset] = to[i];`);
lines.push(` }`);
lines.push(`}`);
lines.push(``);
lines.push(`function readByte(bb${ts('ByteBuffer')})${ts('number')} {`);
lines.push(` return bb.bytes[advance(bb, 1)];`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeByte(bb${ts('ByteBuffer')}, value${ts('number')})${ts('void')} {`);
lines.push(` ${varOrLet} offset = grow(bb, 1);`);
lines.push(` bb.bytes[offset] = value;`);
lines.push(`}`);
lines.push(``);
lines.push(`function readFloat(bb${ts('ByteBuffer')})${ts('number')} {`);
lines.push(` ${varOrLet} offset = advance(bb, 4);`);
lines.push(` ${varOrLet} bytes = bb.bytes;`);
lines.push(``);
lines.push(` // Manual copying is much faster than subarray+set in V8`);
lines.push(` f32_u8[0] = bytes[offset++];`);
lines.push(` f32_u8[1] = bytes[offset++];`);
lines.push(` f32_u8[2] = bytes[offset++];`);
lines.push(` f32_u8[3] = bytes[offset++];`);
lines.push(` return f32[0];`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeFloat(bb${ts('ByteBuffer')}, value${ts('number')})${ts('void')} {`);
lines.push(` ${varOrLet} offset = grow(bb, 4);`);
lines.push(` ${varOrLet} bytes = bb.bytes;`);
lines.push(` f32[0] = value;`);
lines.push(``);
lines.push(` // Manual copying is much faster than subarray+set in V8`);
lines.push(` bytes[offset++] = f32_u8[0];`);
lines.push(` bytes[offset++] = f32_u8[1];`);
lines.push(` bytes[offset++] = f32_u8[2];`);
lines.push(` bytes[offset++] = f32_u8[3];`);
lines.push(`}`);
lines.push(``);
lines.push(`function readDouble(bb${ts('ByteBuffer')})${ts('number')} {`);
lines.push(` ${varOrLet} offset = advance(bb, 8);`);
lines.push(` ${varOrLet} bytes = bb.bytes;`);
lines.push(``);
lines.push(` // Manual copying is much faster than subarray+set in V8`);
lines.push(` f64_u8[0] = bytes[offset++];`);
lines.push(` f64_u8[1] = bytes[offset++];`);
lines.push(` f64_u8[2] = bytes[offset++];`);
lines.push(` f64_u8[3] = bytes[offset++];`);
lines.push(` f64_u8[4] = bytes[offset++];`);
lines.push(` f64_u8[5] = bytes[offset++];`);
lines.push(` f64_u8[6] = bytes[offset++];`);
lines.push(` f64_u8[7] = bytes[offset++];`);
lines.push(` return f64[0];`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeDouble(bb${ts('ByteBuffer')}, value${ts('number')})${ts('void')} {`);
lines.push(` ${varOrLet} offset = grow(bb, 8);`);
lines.push(` ${varOrLet} bytes = bb.bytes;`);
lines.push(` f64[0] = value;`);
lines.push(``);
lines.push(` // Manual copying is much faster than subarray+set in V8`);
lines.push(` bytes[offset++] = f64_u8[0];`);
lines.push(` bytes[offset++] = f64_u8[1];`);
lines.push(` bytes[offset++] = f64_u8[2];`);
lines.push(` bytes[offset++] = f64_u8[3];`);
lines.push(` bytes[offset++] = f64_u8[4];`);
lines.push(` bytes[offset++] = f64_u8[5];`);
lines.push(` bytes[offset++] = f64_u8[6];`);
lines.push(` bytes[offset++] = f64_u8[7];`);
lines.push(`}`);
lines.push(``);
lines.push(`function readInt32(bb${ts('ByteBuffer')})${ts('number')} {`);
lines.push(` ${varOrLet} offset = advance(bb, 4);`);
lines.push(` ${varOrLet} bytes = bb.bytes;`);
lines.push(` return (`);
lines.push(` bytes[offset] |`);
lines.push(` (bytes[offset + 1] << 8) |`);
lines.push(` (bytes[offset + 2] << 16) |`);
lines.push(` (bytes[offset + 3] << 24)`);
lines.push(` );`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeInt32(bb${ts('ByteBuffer')}, value${ts('number')})${ts('void')} {`);
lines.push(` ${varOrLet} offset = grow(bb, 4);`);
lines.push(` ${varOrLet} bytes = bb.bytes;`);
lines.push(` bytes[offset] = value;`);
lines.push(` bytes[offset + 1] = value >> 8;`);
lines.push(` bytes[offset + 2] = value >> 16;`);
lines.push(` bytes[offset + 3] = value >> 24;`);
lines.push(`}`);
lines.push(``);
lines.push(`function readInt64(bb${ts('ByteBuffer')}, unsigned${ts('boolean')})${ts('Long')} {`);
lines.push(` return {`);
lines.push(` low: readInt32(bb),`);
lines.push(` high: readInt32(bb),`);
lines.push(` unsigned,`);
lines.push(` };`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeInt64(bb${ts('ByteBuffer')}, value${ts('Long')})${ts('void')} {`);
lines.push(` writeInt32(bb, value.low);`);
lines.push(` writeInt32(bb, value.high);`);
lines.push(`}`);
lines.push(``);
lines.push(`function readVarint32(bb${ts('ByteBuffer')})${ts('number')} {`);
lines.push(` ${varOrLet} c = 0;`);
lines.push(` ${varOrLet} value = 0;`);
lines.push(` ${varOrLet} b${ts('number')};`);
lines.push(` do {`);
lines.push(` b = readByte(bb);`);
lines.push(` if (c < 32) value |= (b & 0x7F) << c;`);
lines.push(` c += 7;`);
lines.push(` } while (b & 0x80);`);
lines.push(` return value;`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeVarint32(bb${ts('ByteBuffer')}, value${ts('number')})${ts('void')} {`);
lines.push(` value >>>= 0;`);
lines.push(` while (value >= 0x80) {`);
lines.push(` writeByte(bb, (value & 0x7f) | 0x80);`);
lines.push(` value >>>= 7;`);
lines.push(` }`);
lines.push(` writeByte(bb, value);`);
lines.push(`}`);
lines.push(``);
lines.push(`function readVarint64(bb${ts('ByteBuffer')}, unsigned${ts('boolean')})${ts('Long')} {`);
lines.push(` ${varOrLet} part0 = 0;`);
lines.push(` ${varOrLet} part1 = 0;`);
lines.push(` ${varOrLet} part2 = 0;`);
lines.push(` ${varOrLet} b${ts('number')};`);
lines.push(``);
lines.push(` b = readByte(bb); part0 = (b & 0x7F); if (b & 0x80) {`);
lines.push(` b = readByte(bb); part0 |= (b & 0x7F) << 7; if (b & 0x80) {`);
lines.push(` b = readByte(bb); part0 |= (b & 0x7F) << 14; if (b & 0x80) {`);
lines.push(` b = readByte(bb); part0 |= (b & 0x7F) << 21; if (b & 0x80) {`);
lines.push(``);
lines.push(` b = readByte(bb); part1 = (b & 0x7F); if (b & 0x80) {`);
lines.push(` b = readByte(bb); part1 |= (b & 0x7F) << 7; if (b & 0x80) {`);
lines.push(` b = readByte(bb); part1 |= (b & 0x7F) << 14; if (b & 0x80) {`);
lines.push(` b = readByte(bb); part1 |= (b & 0x7F) << 21; if (b & 0x80) {`);
lines.push(``);
lines.push(` b = readByte(bb); part2 = (b & 0x7F); if (b & 0x80) {`);
lines.push(` b = readByte(bb); part2 |= (b & 0x7F) << 7;`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(` }`);
lines.push(``);
lines.push(` return {`);
lines.push(` low: part0 | (part1 << 28),`);
lines.push(` high: (part1 >>> 4) | (part2 << 24),`);
lines.push(` unsigned,`);
lines.push(` };`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeVarint64(bb${ts('ByteBuffer')}, value${ts('Long')})${ts('void')} {`);
lines.push(` ${varOrLet} part0 = value.low >>> 0;`);
lines.push(` ${varOrLet} part1 = ((value.low >>> 28) | (value.high << 4)) >>> 0;`);
lines.push(` ${varOrLet} part2 = value.high >>> 24;`);
lines.push(``);
lines.push(` // ref: src/google/protobuf/io/coded_stream.cc`);
lines.push(` ${varOrLet} size =`);
lines.push(` part2 === 0 ?`);
lines.push(` part1 === 0 ?`);
lines.push(` part0 < 1 << 14 ?`);
lines.push(` part0 < 1 << 7 ? 1 : 2 :`);
lines.push(` part0 < 1 << 21 ? 3 : 4 :`);
lines.push(` part1 < 1 << 14 ?`);
lines.push(` part1 < 1 << 7 ? 5 : 6 :`);
lines.push(` part1 < 1 << 21 ? 7 : 8 :`);
lines.push(` part2 < 1 << 7 ? 9 : 10;`);
lines.push(``);
lines.push(` ${varOrLet} offset = grow(bb, size);`);
lines.push(` ${varOrLet} bytes = bb.bytes;`);
lines.push(``);
lines.push(` switch (size) {`);
lines.push(` case 10: bytes[offset + 9] = (part2 >>> 7) & 0x01;`);
lines.push(` case 9: bytes[offset + 8] = size !== 9 ? part2 | 0x80 : part2 & 0x7F;`);
lines.push(` case 8: bytes[offset + 7] = size !== 8 ? (part1 >>> 21) | 0x80 : (part1 >>> 21) & 0x7F;`);
lines.push(` case 7: bytes[offset + 6] = size !== 7 ? (part1 >>> 14) | 0x80 : (part1 >>> 14) & 0x7F;`);
lines.push(` case 6: bytes[offset + 5] = size !== 6 ? (part1 >>> 7) | 0x80 : (part1 >>> 7) & 0x7F;`);
lines.push(` case 5: bytes[offset + 4] = size !== 5 ? part1 | 0x80 : part1 & 0x7F;`);
lines.push(` case 4: bytes[offset + 3] = size !== 4 ? (part0 >>> 21) | 0x80 : (part0 >>> 21) & 0x7F;`);
lines.push(` case 3: bytes[offset + 2] = size !== 3 ? (part0 >>> 14) | 0x80 : (part0 >>> 14) & 0x7F;`);
lines.push(` case 2: bytes[offset + 1] = size !== 2 ? (part0 >>> 7) | 0x80 : (part0 >>> 7) & 0x7F;`);
lines.push(` case 1: bytes[offset] = size !== 1 ? part0 | 0x80 : part0 & 0x7F;`);
lines.push(` }`);
lines.push(`}`);
lines.push(``);
lines.push(`function readVarint32ZigZag(bb${ts('ByteBuffer')})${ts('number')} {`);
lines.push(` ${varOrLet} value = readVarint32(bb);`);
lines.push(``);
lines.push(` // ref: src/google/protobuf/wire_format_lite.h`);
lines.push(` return (value >>> 1) ^ -(value & 1);`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeVarint32ZigZag(bb${ts('ByteBuffer')}, value${ts('number')})${ts('void')} {`);
lines.push(` // ref: src/google/protobuf/wire_format_lite.h`);
lines.push(` writeVarint32(bb, (value << 1) ^ (value >> 31));`);
lines.push(`}`);
lines.push(``);
lines.push(`function readVarint64ZigZag(bb${ts('ByteBuffer')})${ts('Long')} {`);
lines.push(` ${varOrLet} value = readVarint64(bb, /* unsigned */ false);`);
lines.push(` ${varOrLet} low = value.low;`);
lines.push(` ${varOrLet} high = value.high;`);
lines.push(` ${varOrLet} flip = -(low & 1);`);
lines.push(``);
lines.push(` // ref: src/google/protobuf/wire_format_lite.h`);
lines.push(` return {`);
lines.push(` low: ((low >>> 1) | (high << 31)) ^ flip,`);
lines.push(` high: (high >>> 1) ^ flip,`);
lines.push(` unsigned: false,`);
lines.push(` };`);
lines.push(`}`);
lines.push(``);
lines.push(`function writeVarint64ZigZag(bb${ts('ByteBuffer')}, value${ts('Long')})${ts('void')} {`);
lines.push(` ${varOrLet} low = value.low;`);
lines.push(` ${varOrLet} high = value.high;`);
lines.push(` ${varOrLet} flip = high >> 31;`);
lines.push(``);
lines.push(` // ref: src/google/protobuf/wire_format_lite.h`);
lines.push(` writeVarint64(bb, {`);
lines.push(` low: (low << 1) ^ flip,`);
lines.push(` high: ((high << 1) | (low >>> 31)) ^ flip,`);
lines.push(` unsigned: false,`);
lines.push(` });`);
lines.push(`}`);
lines.push(``);
return lines.join('\n');
}
exports.generate = generate;