Skip to content

Commit

Permalink
cleanup codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
mourner committed Jul 4, 2024
1 parent de82bf0 commit 3221951
Showing 1 changed file with 77 additions and 83 deletions.
160 changes: 77 additions & 83 deletions compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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';
}
Expand All @@ -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) {
Expand All @@ -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);
}
Expand Down Expand Up @@ -144,43 +138,43 @@ 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)) {
suffix += '.toString()';
}

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}`);
}
}

Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 3221951

Please sign in to comment.