diff --git a/compile.js b/compile.js index 957aabc..cd07ba6 100644 --- a/compile.js +++ b/compile.js @@ -4,16 +4,12 @@ import {readFileSync} from 'fs'; const version = JSON.parse(readFileSync(new URL('package.json', import.meta.url))).version; export function compile(proto) { - let code = 'const exports = {};\n'; - code += `${compileRaw(proto, {legacy: true})}\n`; - code += 'return exports;\n'; - return new Function(code)(); + return new Function(`const exports = {};\n${compileRaw(proto, {legacy: true})}\nreturn exports;`)(); } -export function compileRaw(proto, options) { - const pre = `// code generated by pbf v${version}\n`; +export function compileRaw(proto, options = {}) { const context = buildDefaults(buildContext(proto, null), proto.syntax); - return pre + writeContext(context, options || {}); + return `// code generated by pbf v${version}\n${writeContext(context, options)}`; } function writeContext(ctx, options) { @@ -28,50 +24,50 @@ function writeContext(ctx, options) { } function writeMessage(ctx, options) { - const name = ctx._name; const fields = ctx._proto.fields; let code = '\n'; - const indent = ' '; - const readName = `read${ name}`; if (!options.noRead) { - code += `${writeFunctionExport(options, readName)}function ${readName}(pbf, end) {\n`; - code += `${indent}return pbf.readFields(${readName}Tag, ${compileDest(ctx)}, end);\n`; - code += '}\n'; - code += `function ${readName}Tag(tag, obj, pbf) {\n`; - + const readName = `read${ctx._name}`; + code += +`${writeFunctionExport(options, readName)}function ${readName}(pbf, end) { + return pbf.readFields(${readName}Tag, ${compileDest(ctx)}, end); +} +function ${readName}Tag(tag, obj, pbf) { +`; for (let i = 0; i < fields.length; i++) { const field = fields[i]; + const {type, name, repeated, oneof, tag} = field; const readCode = compileFieldRead(ctx, field); const packed = willSupportPacked(ctx, field); - code += `${indent + (i ? 'else if' : 'if') - } (tag === ${field.tag}) ${field.type === 'map' ? ' { ' : ''}${ - field.type === 'map' ? compileMapRead(readCode, field.name) : - field.repeated && !packed ? `obj.${ field.name }.push(${ readCode })` : - field.repeated && packed ? readCode : `obj.${ field.name } = ${ readCode}`}`; - - if (field.oneof) { - code += `, obj.${ field.oneof } = ${ JSON.stringify(field.name)}`; + + let fieldRead = + type === 'map' ? compileMapRead(readCode, name) : + repeated ? (packed ? readCode : `obj.${name}.push(${readCode})`) : + `obj.${name} = ${readCode}`; + + if (oneof) { + fieldRead += `; obj.${oneof} = ${JSON.stringify(name)}`; } - code += `;${ field.type === 'map' ? ' }' : '' }\n`; + fieldRead = type === 'map' || oneof ? `{ ${fieldRead}; }` : `${fieldRead};`; + + code += +` ${i ? 'else ' : ''}if (tag === ${tag}) ${fieldRead}\n`; } code += '}\n'; } - const writeName = `write${ name}`; - if (!options.noWrite) { + const writeName = `write${ctx._name}`; code += `${writeFunctionExport(options, writeName)}function ${writeName}(obj, pbf) {\n`; - for (let i = 0; i < fields.length; i++) { - const field = fields[i]; - const writeCode = field.repeated && !isPacked(field) ? - compileRepeatedWrite(ctx, field) : - field.type === 'map' ? compileMapWrite(ctx, field) : - compileFieldWrite(ctx, field, `obj.${ field.name}`); + for (const field of fields) { + const writeCode = + field.repeated && !isPacked(field) ? compileRepeatedWrite(ctx, field) : + field.type === 'map' ? compileMapWrite(ctx, field) : compileFieldWrite(ctx, field, `obj.${field.name}`); code += getDefaultWriteTest(ctx, field); - code += `${writeCode };\n`; + code += `${writeCode};\n`; } code += '}\n'; } @@ -98,13 +94,12 @@ function writeEnum(ctx, {legacy}) { } function compileDest(ctx) { - const props = {}; - for (let i = 0; i < ctx._proto.fields.length; i++) { - const field = ctx._proto.fields[i]; - props[`${field.name }: ${ JSON.stringify(ctx._defaults[field.name])}`] = true; - if (field.oneof) props[`${field.oneof }: undefined`] = true; + const props = new Set(); + for (const {name, oneof} of ctx._proto.fields) { + props.add(`${name}: ${JSON.stringify(ctx._defaults[name])}`); + if (oneof) props.add(`${oneof }: undefined`); } - return `{${ Object.keys(props).join(', ') }}`; + return `{${[...props].join(', ')}}`; } function isEnum(type) { @@ -115,7 +110,6 @@ function getType(ctx, field) { if (field.type === 'map') { return ctx[getMapMessageName(field.tag)]; } - const path = field.type.split('.'); return path.reduce((ctx, name) => ctx && ctx[name], ctx); } @@ -144,19 +138,19 @@ function fieldShouldUseStringAsNumber(field) { function compileFieldRead(ctx, field) { const type = getType(ctx, field); if (type) { - if (type._proto.fields) return `read${ type._name }(pbf, pbf.readEnd())`; - if (!isEnum(type)) throw new Error(`Unexpected type: ${ type._name}`); + if (type._proto.fields) return `read${type._name}(pbf, pbf.readEnd())`; + if (!isEnum(type)) throw new Error(`Unexpected type: ${type._name}`); } const fieldType = isEnum(type) ? 'enum' : field.type; let prefix = 'pbf.read'; const signed = fieldType === 'int32' || fieldType === 'int64' ? 'true' : ''; - let suffix = `(${ signed })`; + let suffix = `(${signed})`; if (willSupportPacked(ctx, field)) { prefix += 'Packed'; - suffix = `(obj.${ field.name }${signed ? `, ${ signed}` : '' })`; + suffix = `(obj.${field.name}${signed ? `, ${signed}` : ''})`; } if (fieldShouldUseStringAsNumber(field)) { @@ -164,23 +158,23 @@ function compileFieldRead(ctx, field) { } switch (fieldType) { - case 'string': return `${prefix }String${ suffix}`; - case 'float': return `${prefix }Float${ suffix}`; - case 'double': return `${prefix }Double${ suffix}`; - case 'bool': return `${prefix }Boolean${ suffix}`; + case 'string': return `${prefix}String${suffix}`; + case 'float': return `${prefix}Float${suffix}`; + case 'double': return `${prefix}Double${suffix}`; + case 'bool': return `${prefix}Boolean${suffix}`; case 'enum': case 'uint32': case 'uint64': case 'int32': - case 'int64': return `${prefix }Varint${ suffix}`; + case 'int64': return `${prefix}Varint${suffix}`; case 'sint32': - case 'sint64': return `${prefix }SVarint${ suffix}`; - case 'fixed32': return `${prefix }Fixed32${ suffix}`; - case 'fixed64': return `${prefix }Fixed64${ suffix}`; - case 'sfixed32': return `${prefix }SFixed32${ suffix}`; - case 'sfixed64': return `${prefix }SFixed64${ suffix}`; - case 'bytes': return `${prefix }Bytes${ suffix}`; - default: throw new Error(`Unexpected type: ${ field.type}`); + case 'sint64': return `${prefix}SVarint${suffix}`; + case 'fixed32': return `${prefix}Fixed32${suffix}`; + case 'fixed64': return `${prefix}Fixed64${suffix}`; + case 'sfixed32': return `${prefix}SFixed32${suffix}`; + case 'sfixed64': return `${prefix}SFixed64${suffix}`; + case 'bytes': return `${prefix}Bytes${suffix}`; + default: throw new Error(`Unexpected type: ${field.type}`); } } @@ -190,59 +184,59 @@ function compileFieldWrite(ctx, field, name) { if (fieldShouldUseStringAsNumber(field)) { if (field.type === 'float' || field.type === 'double') { - name = `parseFloat(${ name })`; + name = `parseFloat(${name})`; } else { - name = `parseInt(${ name }, 10)`; + name = `parseInt(${name}, 10)`; } } - const postfix = `${isPacked(field) ? '' : 'Field' }(${ field.tag }, ${ name })`; + const postfix = `${isPacked(field) ? '' : 'Field'}(${field.tag}, ${name})`; const type = getType(ctx, field); if (type) { - if (type._proto.fields) return `${prefix }Message(${ field.tag }, write${ type._name }, ${ name })`; - if (type._proto.values) return `${prefix }Varint${ postfix}`; - throw new Error(`Unexpected type: ${ type._name}`); + if (type._proto.fields) return `${prefix}Message(${field.tag}, write${type._name}, ${name})`; + if (type._proto.values) return `${prefix}Varint${postfix}`; + throw new Error(`Unexpected type: ${type._name}`); } switch (field.type) { - case 'string': return `${prefix }String${ postfix}`; - case 'float': return `${prefix }Float${ postfix}`; - case 'double': return `${prefix }Double${ postfix}`; - case 'bool': return `${prefix }Boolean${ postfix}`; + case 'string': return `${prefix}String${postfix}`; + case 'float': return `${prefix}Float${postfix}`; + case 'double': return `${prefix}Double${postfix}`; + case 'bool': return `${prefix}Boolean${postfix}`; case 'enum': case 'uint32': case 'uint64': case 'int32': - case 'int64': return `${prefix }Varint${ postfix}`; + case 'int64': return `${prefix}Varint${postfix}`; case 'sint32': - case 'sint64': return `${prefix }SVarint${ postfix}`; - case 'fixed32': return `${prefix }Fixed32${ postfix}`; - case 'fixed64': return `${prefix }Fixed64${ postfix}`; - case 'sfixed32': return `${prefix }SFixed32${ postfix}`; - case 'sfixed64': return `${prefix }SFixed64${ postfix}`; - case 'bytes': return `${prefix }Bytes${ postfix}`; - default: throw new Error(`Unexpected type: ${ field.type}`); + case 'sint64': return `${prefix}SVarint${postfix}`; + case 'fixed32': return `${prefix}Fixed32${postfix}`; + case 'fixed64': return `${prefix}Fixed64${postfix}`; + case 'sfixed32': return `${prefix}SFixed32${postfix}`; + case 'sfixed64': return `${prefix}SFixed64${postfix}`; + case 'bytes': return `${prefix}Bytes${postfix}`; + default: throw new Error(`Unexpected type: ${field.type}`); } } function compileMapRead(readCode, name) { - return `const {key, value} = ${ readCode }; obj.${ name }[key] = value`; + return `const {key, value} = ${readCode}; obj.${name}[key] = value`; } function compileRepeatedWrite(ctx, field) { - return `for (const item of obj.${ field.name }) ${ + return `for (const item of obj.${field.name}) ${ compileFieldWrite(ctx, field, 'item')}`; } function compileMapWrite(ctx, field) { - const name = `obj.${ field.name}`; + const name = `obj.${field.name}`; - return `for (const key of Object.keys(${ name })) ${ - compileFieldWrite(ctx, field, `{key, value: ${ name }[key]}`)}`; + return `for (const key of Object.keys(${name})) ${ + compileFieldWrite(ctx, field, `{key, value: ${name}[key]}`)}`; } function getMapMessageName(tag) { - return `_FieldEntry${ tag}`; + return `_FieldEntry${tag}`; } function getMapField(name, type, tag) { @@ -404,18 +398,18 @@ function buildDefaults(ctx, syntax) { function getDefaultWriteTest(ctx, field) { const def = ctx._defaults[field.name]; const type = getType(ctx, field); - let code = ` if (obj.${ field.name}`; + let code = ` if (obj.${field.name}`; if (!field.repeated && (!type || !type._proto.fields)) { if (def === undefined || def) { code += ' != null'; } if (def) { - code += ` && obj.${ field.name } !== ${ JSON.stringify(def)}`; + code += ` && obj.${field.name} !== ${JSON.stringify(def)}`; } } - return `${code }) `; + return `${code}) `; } function isPacked(field) {