Skip to content

Commit

Permalink
Merge pull request #453 from markw65/jsdoc-generate-bytecode
Browse files Browse the repository at this point in the history
Make generate-bytecode.js ts-clean
  • Loading branch information
hildjj authored Dec 12, 2023
2 parents 63b913d + 1f4b15c commit 88c0d75
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 11 deletions.
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);
}
}

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

0 comments on commit 88c0d75

Please sign in to comment.