Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make generate-bytecode.js ts-clean #453

Merged
merged 4 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Released: TBD

### Minor Changes

- [#452](https://github.com/peggyjs/peggy/pull/432) Fixes to prepare generate-bytecode.js for ts-check
- [#453](https://github.com/peggyjs/peggy/pull/453) Make generate-bytecode.js ts-clean
- [#452](https://github.com/peggyjs/peggy/pull/452) Fixes to prepare generate-bytecode.js for ts-check
- [#432](https://github.com/peggyjs/peggy/pull/432) Add peggy.code-workspace
- [#451](https://github.com/peggyjs/peggy/pull/451) Make stack.js ts clean
- [#439](https://github.com/peggyjs/peggy/pull/439) Make peg$computePosDetails a little faster
Expand Down
2 changes: 1 addition & 1 deletion docs/js/benchmark-bundle.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/js/test-bundle.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/vendor/peggy/peggy.min.js

Large diffs are not rendered by default.

136 changes: 129 additions & 7 deletions lib/compiler/passes/generate-bytecode.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
// @ts-check
"use strict";

const asts = require("../asts");
const op = require("../opcodes");
const visitor = require("../visitor");
const { ALWAYS_MATCH, SOMETIMES_MATCH, NEVER_MATCH } = require("./inference-match-result");

/**
* @typedef {import("../../peg")} PEG
*/

// Generates bytecode.
//
// Instructions
Expand Down Expand Up @@ -270,19 +275,37 @@ const { ALWAYS_MATCH, SOMETIMES_MATCH, NEVER_MATCH } = require("./inference-matc
// that is equivalent of an unknown match result and signals the generator that
// runtime check for the |FAILED| is required. Trick is explained on the
// Wikipedia page (https://en.wikipedia.org/wiki/Asm.js#Code_generation)
/**
*
* @param {PEG.ast.Grammar} ast
* @param {PEG.ParserOptions} options
*/
function generateBytecode(ast, options) {
/** @type string[] */
const literals = [];
/** @type PEG.ast.GrammarCharacterClass[] */
const classes = [];
/** @type PEG.ast.GrammarExpectation[] */
const expectations = [];
/** @type PEG.ast.FunctionConst[] */
const functions = [];
/** @type PEG.LocationRange[] */
const locations = [];

/**
* @param {string} value
* @returns {number}
*/
function addLiteralConst(value) {
const index = literals.indexOf(value);

return index === -1 ? literals.push(value) - 1 : index;
}

/**
* @param {PEG.ast.CharacterClass} node
* @returns {number}
*/
function addClassConst(node) {
const cls = {
value: node.parts,
Expand All @@ -295,13 +318,23 @@ function generateBytecode(ast, options) {
return index === -1 ? classes.push(cls) - 1 : index;
}

/**
* @param {PEG.ast.GrammarExpectation} expected
* @returns {number}
*/
function addExpectedConst(expected) {
const pattern = JSON.stringify(expected);
const index = expectations.findIndex(e => JSON.stringify(e) === pattern);

return index === -1 ? expectations.push(expected) - 1 : index;
}

/**
* @param {boolean} predicate
* @param {string[]} params
* @param {{code:string; codeLocation: PEG.LocationRange}} node
* @returns {number}
*/
function addFunctionConst(predicate, params, node) {
const func = {
predicate,
Expand All @@ -315,14 +348,26 @@ function generateBytecode(ast, options) {
return index === -1 ? functions.push(func) - 1 : index;
}

/**
* @param {PEG.LocationRange} location
* @returns {number}
*/
function addLocation(location) {
// Don't bother de-duplicating. There can be a lot of locations,
// they will almost never collide, and unlike the "consts" above,
// it won't affect code generation even if they do.
return locations.push(location) - 1;
}

/** @typedef {Record<string, number>} Env */
/** @typedef {{ sp: number; env:Env; action:PEG.ast.Action|null; pluck?: number[] }} Context */

/**
* @param {Env} env
* @returns {Env}
*/
function cloneEnv(env) {
/** @type {Env} */
const clone = {};

Object.keys(env).forEach(name => {
Expand All @@ -332,10 +377,22 @@ function generateBytecode(ast, options) {
return clone;
}

/**
* @param {number[]} first
* @param {number[][]} args
* @returns {number[]}
*/
function buildSequence(first, ...args) {
return first.concat(...args);
}

/**
* @param {number} match
* @param {number[]} condCode
* @param {number[]} thenCode
* @param {number[]} elseCode
* @returns {number[]}
*/
function buildCondition(match, condCode, thenCode, elseCode) {
if (match === ALWAYS_MATCH) { return thenCode; }
if (match === NEVER_MATCH) { return elseCode; }
Expand All @@ -347,16 +404,35 @@ function generateBytecode(ast, options) {
);
}

/**
* @param {number[]} condCode
* @param {number[]} bodyCode
* @returns {number[]}
*/
function buildLoop(condCode, bodyCode) {
return condCode.concat([bodyCode.length], bodyCode);
}

/**
* @param {number} functionIndex
* @param {number} delta
* @param {Env} env
* @param {number} sp
* @returns {number[]}
*/
function buildCall(functionIndex, delta, env, sp) {
const params = Object.keys(env).map(name => sp - env[name]);

return [op.CALL, functionIndex, delta, params.length].concat(params);
}

/**
* @template T
* @param {PEG.ast.Expr<T>} expression
* @param {boolean} negative
* @param {Context} context
* @returns {number[]}
*/
function buildSimplePredicate(expression, negative, context) {
const match = expression.match || 0;

Expand Down Expand Up @@ -386,7 +462,13 @@ function generateBytecode(ast, options) {
)
);
}

/**
*
* @param {PEG.ast.SemanticPredicate} node
* @param {boolean} negative
* @param {Context} context
* @returns {number[]}
*/
function buildSemanticPredicate(node, negative, context) {
const functionIndex = addFunctionConst(
true, Object.keys(context.env), node
Expand All @@ -410,18 +492,32 @@ function generateBytecode(ast, options) {
);
}

/**
* @param {number[]} expressionCode
* @returns {number[]}
*/
function buildAppendLoop(expressionCode) {
return buildLoop(
[op.WHILE_NOT_ERROR],
buildSequence([op.APPEND], expressionCode)
);
}

/**
* @param {never} boundary
* @returns {Error}
*/
function unknownBoundary(boundary) {
const b = /** @type {{ type: string }} */(boundary);
return new Error(`Unknown boundary type "${b.type}" for the "repeated" node`);
}

/**
*
* @param {import("../../peg").ast.RepeatedBoundary} boundary
* @param {{ [label: string]: number}} env Mapping of label names to stack positions
* @param {number} sp Number of the first free slot in the stack
* @param {number} offset
*
* @returns {{ pre: number[], post: number[], sp: number}}
* Bytecode that should be added before and after parsing and new
Expand Down Expand Up @@ -453,7 +549,7 @@ function generateBytecode(ast, options) {

// istanbul ignore next Because we never generate invalid boundary type we cannot reach this branch
default:
throw new Error(`Unknown boundary type "${boundary.type}" for the "repeated" node`);
throw unknownBoundary(boundary);
hildjj marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand All @@ -469,7 +565,7 @@ function generateBytecode(ast, options) {
if (max.value !== null) {
const checkCode = max.type === "constant"
? [op.IF_GE, max.value]
: [op.IF_GE_DYNAMIC, max.sp];
: [op.IF_GE_DYNAMIC, max.sp || 0];

// Push `peg$FAILED` - this break loop on next iteration, so |result|
// will contains not more then |max| elements.
Expand All @@ -495,7 +591,7 @@ function generateBytecode(ast, options) {
function buildCheckMin(expressionCode, min) {
const checkCode = min.type === "constant"
? [op.IF_LT, min.value]
: [op.IF_LT_DYNAMIC, min.sp];
: [op.IF_LT_DYNAMIC, min.sp || 0];

return buildSequence(
expressionCode, // result = [elem...]; stack:[ pos, [elem...] ]
Expand All @@ -510,7 +606,15 @@ function generateBytecode(ast, options) {
)
);
}

/**
*
* @param {PEG.ast.Expression|null} delimiterNode
* @param {number} expressionMatch
* @param {number[]} expressionCode
* @param {Context} context
* @param {number} offset
* @returns {number[]}
*/
function buildRangeBody(
delimiterNode,
expressionMatch,
Expand Down Expand Up @@ -556,10 +660,16 @@ function generateBytecode(ast, options) {
return expressionCode;
}

/**
* @param {PEG.compiler.visitor.NodeTypes} generators
* @returns {PEG.compiler.visitor.AnyFunction}
*/
function wrapGenerators(generators) {
if (options && options.output === "source-and-map") {
Object.keys(generators).forEach(name => {
// @ts-ignore
const generator = generators[name];
// @ts-ignore
generators[name] = function(node, ...args) {
const generated = generator(node, ...args);
// Some generators ("grammar" and "rule") don't return anything,
Expand Down Expand Up @@ -623,6 +733,12 @@ function generateBytecode(ast, options) {
},

choice(node, context) {
/**
*
* @param {PEG.ast.Expression[]} alternatives
* @param {Context} context
* @returns {number[]}
*/
function buildAlternativesCode(alternatives, context) {
const match = alternatives[0].match || 0;
const first = generate(alternatives[0], {
Expand Down Expand Up @@ -694,6 +810,12 @@ function generateBytecode(ast, options) {
},

sequence(node, context) {
/**
*
* @param {PEG.ast.Expression[]} elements
* @param {Context} context
* @returns {number[]}
*/
function buildElementsCode(elements, context) {
if (elements.length > 0) {
const processedCount = node.elements.length - elements.length + 1;
Expand Down Expand Up @@ -722,7 +844,7 @@ function generateBytecode(ast, options) {
)
);
} else {
if (context.pluck.length > 0) {
if (context.pluck && context.pluck.length > 0) {
return buildSequence(
[op.PLUCK, node.elements.length + 1, context.pluck.length],
context.pluck.map(eSP => context.sp - eSP)
Expand Down Expand Up @@ -769,7 +891,7 @@ function generateBytecode(ast, options) {

if (label) {
env = cloneEnv(context.env);
context.env[node.label] = sp;
context.env[label] = sp;
}

if (node.pick) {
Expand Down
4 changes: 4 additions & 0 deletions lib/peg.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,13 +252,17 @@ declare namespace ast {
interface VariableBoundary extends Boundary<"variable"> {
/** Repetition count - name of the label of the one of preceding expressions. */
value: string;
/** Stack offset, added by generateBytecode. */
sp?: number;
}

interface FunctionBoundary extends Boundary<"function"> {
/** The code from the grammar. */
value: string;
/** Span that covers all code between `{` and `}`. */
codeLocation: LocationRange;
/** Stack offset, added by generateBytecode. */
sp?: number;
}

type RepeatedBoundary
Expand Down