llparse
Version:
Compile incremental parsers to C code
260 lines • 7.97 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Compilation = void 0;
const assert = require("assert");
const constants_1 = require("./constants");
const match_sequence_1 = require("./helpers/match-sequence");
// Number of hex words per line of blob declaration
const BLOB_GROUP_SIZE = 11;
class Compilation {
prefix;
properties;
options;
stateMap = new Map();
blobs = new Map();
codeMap = new Map();
matchSequence = new Map();
resumptionTargets = new Set();
constructor(prefix, properties, resumptionTargets, options) {
this.prefix = prefix;
this.properties = properties;
this.options = options;
for (const node of resumptionTargets) {
this.resumptionTargets.add(constants_1.STATE_PREFIX + node.ref.id.name);
}
}
buildStateEnum(out) {
out.push('enum llparse_state_e {');
out.push(` ${constants_1.STATE_ERROR},`);
for (const stateName of this.stateMap.keys()) {
if (this.resumptionTargets.has(stateName)) {
out.push(` ${stateName},`);
}
}
out.push('};');
out.push('typedef enum llparse_state_e llparse_state_t;');
}
buildBlobs(out) {
if (this.blobs.size === 0) {
return;
}
for (const blob of this.blobs.values()) {
const buffer = blob.buffer;
let align = '';
if (blob.alignment) {
align = ` ALIGN(${blob.alignment})`;
}
if (blob.alignment) {
out.push('#ifdef __SSE4_2__');
}
out.push(`static const unsigned char${align} ${blob.name}[] = {`);
for (let i = 0; i < buffer.length; i += BLOB_GROUP_SIZE) {
const limit = Math.min(buffer.length, i + BLOB_GROUP_SIZE);
const hex = [];
for (let j = i; j < limit; j++) {
const value = buffer[j];
assert(value !== undefined);
hex.push(this.toChar(value));
}
let line = ' ' + hex.join(', ');
if (limit !== buffer.length) {
line += ',';
}
out.push(line);
}
out.push(`};`);
if (blob.alignment) {
out.push('#endif /* __SSE4_2__ */');
}
}
out.push('');
}
buildMatchSequence(out) {
if (this.matchSequence.size === 0) {
return;
}
match_sequence_1.MatchSequence.buildGlobals(out);
out.push('');
for (const match of this.matchSequence.values()) {
match.build(this, out);
out.push('');
}
}
reserveSpans(spans) {
for (const span of spans) {
for (const callback of span.callbacks) {
this.buildCode(this.unwrapCode(callback));
}
}
}
debug(out, message) {
if (this.options.debug === undefined) {
return;
}
const args = [
this.stateArg(),
`(const char*) ${this.posArg()}`,
`(const char*) ${this.endPosArg()}`,
];
out.push(`${this.options.debug}(${args.join(', ')},`);
out.push(` ${this.cstring(message)});`);
}
buildGlobals(out) {
if (this.options.debug !== undefined) {
out.push(`void ${this.options.debug}(`);
out.push(` ${this.prefix}_t* s, const char* p, const char* endp,`);
out.push(' const char* msg);');
}
this.buildBlobs(out);
this.buildMatchSequence(out);
this.buildStateEnum(out);
for (const code of this.codeMap.values()) {
out.push('');
code.build(this, out);
}
}
buildResumptionStates(out) {
this.stateMap.forEach((lines, name) => {
if (!this.resumptionTargets.has(name)) {
return;
}
out.push(`case ${name}:`);
out.push(`${constants_1.LABEL_PREFIX}${name}: {`);
lines.forEach((line) => out.push(` ${line}`));
out.push(' UNREACHABLE;');
out.push('}');
});
}
buildInternalStates(out) {
this.stateMap.forEach((lines, name) => {
if (this.resumptionTargets.has(name)) {
return;
}
out.push(`${constants_1.LABEL_PREFIX}${name}: {`);
lines.forEach((line) => out.push(` ${line}`));
out.push(' UNREACHABLE;');
out.push('}');
});
}
addState(state, lines) {
assert(!this.stateMap.has(state));
this.stateMap.set(state, lines);
}
buildCode(code) {
if (this.codeMap.has(code.ref.name)) {
assert.strictEqual(this.codeMap.get(code.ref.name), code, `Code name conflict for "${code.ref.name}"`);
}
else {
this.codeMap.set(code.ref.name, code);
}
return code.ref.name;
}
getFieldType(field) {
for (const property of this.properties) {
if (property.name === field) {
return property.ty;
}
}
throw new Error(`Field "${field}" not found`);
}
// Helpers
unwrapCode(code) {
const container = code;
return container.get(constants_1.CONTAINER_KEY);
}
unwrapNode(node) {
const container = node;
return container.get(constants_1.CONTAINER_KEY);
}
unwrapTransform(node) {
const container = node;
return container.get(constants_1.CONTAINER_KEY);
}
indent(out, lines, pad) {
for (const line of lines) {
out.push(`${pad}${line}`);
}
}
// MatchSequence cache
getMatchSequence(transform, select) {
const wrap = this.unwrapTransform(transform);
let res;
if (this.matchSequence.has(wrap.ref.name)) {
res = this.matchSequence.get(wrap.ref.name);
}
else {
res = new match_sequence_1.MatchSequence(wrap);
this.matchSequence.set(wrap.ref.name, res);
}
return res.getName();
}
// Arguments
stateArg() {
return constants_1.ARG_STATE;
}
posArg() {
return constants_1.ARG_POS;
}
endPosArg() {
return constants_1.ARG_ENDPOS;
}
matchVar() {
return constants_1.VAR_MATCH;
}
// State fields
indexField() {
return this.stateField('_index');
}
currentField() {
return this.stateField('_current');
}
errorField() {
return this.stateField('error');
}
reasonField() {
return this.stateField('reason');
}
errorPosField() {
return this.stateField('error_pos');
}
spanPosField(index) {
return this.stateField(`_span_pos${index}`);
}
spanCbField(index) {
return this.stateField(`_span_cb${index}`);
}
stateField(name) {
return `${this.stateArg()}->${name}`;
}
// Globals
cstring(value) {
return JSON.stringify(value);
}
blob(value, alignment) {
if (this.blobs.has(value)) {
return this.blobs.get(value).name;
}
const res = constants_1.BLOB_PREFIX + this.blobs.size;
this.blobs.set(value, {
alignment,
buffer: value,
name: res,
});
return res;
}
toChar(value) {
const ch = String.fromCharCode(value);
// `'`, `\`
if (value === 0x27 || value === 0x5c) {
return `'\\${ch}'`;
}
else if (value >= 0x20 && value <= 0x7e) {
return `'${ch}'`;
}
else {
return `0x${value.toString(16)}`;
}
}
}
exports.Compilation = Compilation;
//# sourceMappingURL=compilation.js.map