diff --git a/src/compiler/header-builder.ts b/src/compiler/header-builder.ts index 9f5bee7..c14d3ed 100644 --- a/src/compiler/header-builder.ts +++ b/src/compiler/header-builder.ts @@ -1,20 +1,16 @@ import * as frontend from 'llparse-frontend'; -import source = frontend.source; - -export interface IHeaderBuilderOptions { - readonly prefix: string; - readonly headerGuard?: string; - readonly properties: ReadonlyArray; - readonly spans: ReadonlyArray; -} +import { IStructStateFields } from './struct-state-fields-builder'; export class HeaderBuilder { - public build(options: IHeaderBuilderOptions): string { + public build(headerGuard: string | undefined, + fields: IStructStateFields, + options: frontend.IFrontendResult): string { let res = ''; const PREFIX = options.prefix.toUpperCase().replace(/[^a-z]/gi, '_'); - const DEFINE = options.headerGuard === undefined ? - `INCLUDE_${PREFIX}_H_` : options.headerGuard; + const DEFINE = headerGuard === undefined ? + `INCLUDE_${PREFIX}_H_` : headerGuard; + // ifdef res += `#ifndef ${DEFINE}\n`; res += `#define ${DEFINE}\n`; res += '#ifdef __cplusplus\n'; @@ -28,48 +24,19 @@ export class HeaderBuilder { // Structure res += `typedef struct ${options.prefix}_s ${options.prefix}_t;\n`; res += `struct ${options.prefix}_s {\n`; - res += ' int32_t _index;\n'; - - for (const [ index, field ] of options.spans.entries()) { - res += ` void* _span_pos${index};\n`; - if (field.callbacks.length > 1) { - res += ` void* _span_cb${index};\n`; - } - } - - res += ' int32_t error;\n'; - res += ' const char* reason;\n'; - res += ' const char* error_pos;\n'; - res += ' void* data;\n'; - res += ' void* _current;\n'; - - for (const prop of options.properties) { - let ty: string; - if (prop.ty === 'i8') { - ty = 'uint8_t'; - } else if (prop.ty === 'i16') { - ty = 'uint16_t'; - } else if (prop.ty === 'i32') { - ty = 'uint32_t'; - } else if (prop.ty === 'i64') { - ty = 'uint64_t'; - } else if (prop.ty === 'ptr') { - ty = 'void*'; - } else { - throw new Error( - `Unknown state property type: "${prop.ty}"`); - } - res += ` ${ty} ${prop.name};\n`; + for (const { name, type } of fields) { + res += ` ${type} ${name};\n`; } res += '};\n'; - res += '\n'; + // Functions: init, execute res += `int ${options.prefix}_init(${options.prefix}_t* s);\n`; res += `int ${options.prefix}_execute(${options.prefix}_t* s, ` + 'const char* p, const char* endp);\n'; - res += '\n'; + + // endif res += '#ifdef __cplusplus\n'; res += '} /* extern "C" *\/\n'; res += '#endif\n'; diff --git a/src/compiler/index.ts b/src/compiler/index.ts index 7206ce4..866defb 100644 --- a/src/compiler/index.ts +++ b/src/compiler/index.ts @@ -7,6 +7,7 @@ import * as bitcodeImpl from '../implementation/bitcode'; import * as cImpl from '../implementation/c'; import * as jsImpl from '../implementation/js'; import { HeaderBuilder } from './header-builder'; +import { StructStateFieldsBuilder } from './struct-state-fields-builder'; const debug = debugAPI('llparse:compiler'); @@ -117,15 +118,13 @@ export class Compiler { this.options.frontend); const info = f.compile(root, properties); - debug('Building header'); - const hb = new HeaderBuilder(); + debug('Building fields for state struct'); + const fields = new StructStateFieldsBuilder().build(info); - const header = hb.build({ - headerGuard: this.options.headerGuard, - prefix: this.prefix, - properties, - spans: info.spans, - }); + debug('Building header'); + const header = new HeaderBuilder().build(this.options.headerGuard, + fields, + info); const result: IWritableCompilerResult = { bitcode: undefined, @@ -134,7 +133,7 @@ export class Compiler { debug('Building bitcode'); if (bitcode) { - result.bitcode = bitcode.compile(info); + result.bitcode = bitcode.compile(fields, info); } debug('Building C'); diff --git a/src/compiler/struct-state-fields-builder.ts b/src/compiler/struct-state-fields-builder.ts new file mode 100644 index 0000000..6acebc3 --- /dev/null +++ b/src/compiler/struct-state-fields-builder.ts @@ -0,0 +1,70 @@ +import * as frontend from 'llparse-frontend'; + +interface IStructStateField { + readonly name: string; + readonly size: number; + readonly type: string; +} + +export type IStructStateFields = IStructStateField[]; + +export class StructStateFieldsBuilder { + public build(options: frontend.IFrontendResult): IStructStateFields { + const fields: IStructStateFields = []; + fields.push({ name: '_index', size: 4, type: 'int32_t' }); + + for (const [ index, field ] of options.spans.entries()) { + fields.push({ name: `_span_pos${index}`, size: -1, type: 'void*' }); + if (field.callbacks.length > 1) { + fields.push({ name: `_span_cb${index}`, size: -1, type: 'void*' }); + } + } + + fields.push({ name: 'error', size: 4, type: 'int32_t' }); + fields.push({ name: 'reason', size: -1, type: 'const char*' }); + fields.push({ name: 'error_pos', size: -1, type: 'const char*' }); + fields.push({ name: 'data', size: -1, type: 'void*' }); + fields.push({ name: '_current', size: -1, type: 'void*' }); + + for (const { name, ty } of options.properties) { + if (ty === 'i8') { + fields.push({ name, size: 1, type: 'uint8_t' }); + } else if (ty === 'i16') { + fields.push({ name, size: 2, type: 'uint16_t' }); + } else if (ty === 'i32') { + fields.push({ name, size: 4, type: 'uint32_t' }); + } else if (ty === 'i64') { + fields.push({ name, size: 8, type: 'uint64_t' }); + } else if (ty === 'ptr') { + fields.push({ name, size: -1, type: 'void*' }); + } else { + throw new Error(`Unknown state property type: "${ty}"`); + } + } + + // Sort fields from bigger size to lower. + // Pointers are special, they should be after 8-bytes, but before 4-bytes. + // In this case align work for 32-bit and 64-bit. + fields.sort((a, b) => { + if (a.name === b.name) { + throw new Error(`Two fields with same name: "${a.name}"`); + } + + if (a.size === b.size) { + return a.name < b.name ? -1 : 1; + } + + if (a.size === -1) { + return b.size >= 8 ? 1 : -1; + } + + if (b.size === -1) { + return a.size >= 8 ? -1 : 1; + } + + return b.size - a.size; + }); + + return fields; + } +} diff --git a/src/implementation/bitcode/compilation.ts b/src/implementation/bitcode/compilation.ts index 45d1a80..407685b 100644 --- a/src/implementation/bitcode/compilation.ts +++ b/src/implementation/bitcode/compilation.ts @@ -7,6 +7,7 @@ import { import { Buffer } from 'buffer'; import * as frontend from 'llparse-frontend'; +import { IStructStateFields } from '../../compiler/struct-state-fields-builder' import * as constants from './constants'; import { MatchSequence } from './helpers/match-sequence'; import { Code } from './code'; @@ -83,8 +84,8 @@ export class Compilation { private debugMethod: IRDeclaration | undefined = undefined; constructor(public readonly prefix: string, + fields: IStructStateFields, private readonly properties: ReadonlyArray, - spans: ReadonlyArray, public readonly options: ICompilationOptions) { this.ir = this.bitcode.createBuilder(); this.invariantGroup = this.ir.metadata([ @@ -120,36 +121,42 @@ export class Compilation { ]), }; - // Put most used fields first - this.state.addField(constants.TYPE_INDEX, constants.STATE_INDEX); + const propTypeMap: { [key: string]: IRType } = { + uint8_t: constants.I8, + uint16_t: constants.I16, + uint32_t: constants.I32, + uint64_t: constants.I64, + 'void*': constants.PTR, + }; + + const typeMap: { [key: string]: IRType } = { + [constants.STATE_INDEX]: constants.TYPE_INDEX, + [constants.STATE_ERROR]: constants.TYPE_ERROR, + [constants.STATE_REASON]: constants.TYPE_REASON, + [constants.STATE_ERROR_POS]: constants.TYPE_ERROR_POS, + [constants.STATE_DATA]: constants.TYPE_DATA, + [constants.STATE_CURRENT]: this.signature.node.ptr(), + }; - spans.forEach((span) => { - this.state.addField(constants.TYPE_SPAN_POS, - constants.STATE_SPAN_POS + span.index); - if (span.callbacks.length > 1) { - this.state.addField(this.signature.callback.span.ptr(), - constants.STATE_SPAN_CB + span.index); + for (const { name, type } of fields) { + const isProp = properties.some((p) => p.name === name); + if (isProp) { + this.state.addField(propTypeMap[type], name); + continue; } - }); - this.state.addField(constants.TYPE_ERROR, constants.STATE_ERROR); - this.state.addField(constants.TYPE_REASON, constants.STATE_REASON); - this.state.addField(constants.TYPE_ERROR_POS, constants.STATE_ERROR_POS); - this.state.addField(constants.TYPE_DATA, constants.STATE_DATA); - this.state.addField(this.signature.node.ptr(), constants.STATE_CURRENT); - - for (const property of properties) { - let ty: IRType; - switch (property.ty) { - case 'i8': ty = constants.I8; break; - case 'i16': ty = constants.I16; break; - case 'i32': ty = constants.I32; break; - case 'i64': ty = constants.I64; break; - case 'ptr': ty = constants.PTR; break; - default: throw new Error(`Unsupported user type: "${property.ty}"`); + let ty = typeMap[name]; + if (!ty) { + if (name.startsWith(constants.STATE_SPAN_POS)) { + ty = constants.TYPE_SPAN_POS; + } else if (name.startsWith(constants.STATE_SPAN_CB)) { + ty = this.signature.callback.span.ptr(); + } else { + throw new Error(`Unknow field: "${name}"`); + } } - this.state.addField(ty, property.name); + this.state.addField(ty, name); } this.state.finalize(); diff --git a/src/implementation/bitcode/index.ts b/src/implementation/bitcode/index.ts index 40ea3ad..1ba8423 100644 --- a/src/implementation/bitcode/index.ts +++ b/src/implementation/bitcode/index.ts @@ -2,6 +2,7 @@ import { Buffer } from 'buffer'; import * as debugAPI from 'debug'; import * as frontend from 'llparse-frontend'; +import { IStructStateFields } from '../../compiler/struct-state-fields-builder' import { Compilation } from './compilation'; import { CONTAINER_KEY } from './constants'; import code from './code'; @@ -23,10 +24,10 @@ export class BitcodeCompiler { container.add(CONTAINER_KEY, { code, node, transform }); } - public compile(info: frontend.IFrontendResult): Buffer { + public compile(fields: IStructStateFields, info: frontend.IFrontendResult): Buffer { // Compile to bitcode - const compilation = new Compilation(info.prefix, info.properties, - info.spans, this.options); + const compilation = new Compilation(info.prefix, fields, info.properties, + this.options); debug('building root'); const root = info.root as frontend.ContainerWrap;