From e59a378033c4b710c0b10b13d883076e29d6649f Mon Sep 17 00:00:00 2001 From: markw65 Date: Thu, 7 Sep 2023 14:55:35 -0700 Subject: [PATCH] fixes and coverage --- lib/compiler/passes/generate-js.js | 31 +++++----- lib/peg.d.ts | 10 ++-- test/types/peg.test-d.ts | 12 +++- test/unit/compiler/passes/generate-js.spec.js | 56 +++++++++++++++++++ 4 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 test/unit/compiler/passes/generate-js.spec.js diff --git a/lib/compiler/passes/generate-js.js b/lib/compiler/passes/generate-js.js index 03108718..ece86ac3 100644 --- a/lib/compiler/passes/generate-js.js +++ b/lib/compiler/passes/generate-js.js @@ -90,8 +90,8 @@ function wrapInSourceNode(prefix, chunk, location, suffix, name) { * * @typedef {PEG.SourceBuildOptions} SourceBuildOptions * @typedef {object} ExtraOptions - * @property {undefined} [dependencies] - * @property {undefined} [exportVar] + * @property {PEG.Dependencies} [dependencies] + * @property {string} [exportVar] * @typedef {SourceBuildOptions & ExtraOptions} Options */ /** @@ -101,12 +101,13 @@ function wrapInSourceNode(prefix, chunk, location, suffix, name) { * @param {Options} options */ function generateJS(ast, options) { - if (!ast.literals || !ast.locations) { + if (!ast.literals || !ast.locations || !ast.classes + || !ast.expectations || !ast.functions) { throw new Error( "generateJS: generate bytecode was not called." ); } - const { literals, locations } = ast; + const { literals, locations, classes, expectations, functions } = ast; if (!options.allowedStartRules) { throw new Error( "generateJS: options.allowedStartRules was not set." @@ -191,7 +192,7 @@ function generateJS(ast, options) { } if (code instanceof SourceNode) { inSourceNode++; - code.children = (helper(code.children)); + code.children = helper(code.children); inSourceNode--; return code; } @@ -232,7 +233,7 @@ function generateJS(ast, options) { return "\"" + stringEscape(literal) + "\""; } - /** @param {PEG.ast.CharacterRange} cls */ + /** @param {PEG.ast.GrammarCharacterClass} cls */ function buildRegexp(cls) { return "/^[" + (cls.inverted ? "^" : "") @@ -244,7 +245,7 @@ function generateJS(ast, options) { + "]/" + (cls.ignoreCase ? "i" : ""); } - /** @param {PEG.ast.Expectation} e */ + /** @param {PEG.ast.GrammarExpectation} e */ function buildExpectation(e) { switch (e.type) { case "rule": { @@ -289,14 +290,14 @@ function generateJS(ast, options) { return new SourceNode( null, null, options.grammarSource, [ - (ast.literals || []).map( + literals.map( (c, i) => " var " + l(i) + " = " + buildLiteral(c) + ";" - ).concat("", (ast.classes || []).map( + ).concat("", classes.map( (c, i) => " var " + r(i) + " = " + buildRegexp(c) + ";" - )).concat("", (ast.expectations || []).map( + )).concat("", expectations.map( (c, i) => " var " + e(i) + " = " + buildExpectation(c) + ";" )).concat("").join("\n"), - ...(ast.functions || []).map(buildFunc), + ...functions.map(buildFunc), ] ); } @@ -409,10 +410,8 @@ function generateJS(ast, options) { function generateRuleFunction(rule) { /** @type {SourceArray} */ const parts = []; - if (!rule.bytecode) { - return parts; - } - const stack = new Stack(rule.name, "s", "var", rule.bytecode); + const bytecode = /** @type {number[]} */(rule.bytecode); + const stack = new Stack(rule.name, "s", "var", bytecode); /** @param {number[]} bc */ function compile(bc) { @@ -798,7 +797,7 @@ function generateJS(ast, options) { return parts; } - const code = compile(rule.bytecode); + const code = compile(bytecode); parts.push(wrapInSourceNode( "function ", diff --git a/lib/peg.d.ts b/lib/peg.d.ts index 68a08c78..7cacf822 100644 --- a/lib/peg.d.ts +++ b/lib/peg.d.ts @@ -55,17 +55,17 @@ declare namespace ast { * Type of the classes field on a Grammar node. Not quite the same as * CharacterClass (`parts` was renamed to `value`). */ - interface CharacterRange { + interface GrammarCharacterClass { value: (string[] | string)[]; inverted: boolean; ignoreCase: boolean; } - type Expectation = + type GrammarExpectation = | { type: "any" } | { type: "literal"; value: string; ignoreCase: boolean } | { type: "rule"; value: string } - | CharacterRange & { type: "class" } + | GrammarCharacterClass & { type: "class" } ; /** The main Peggy AST class returned by the parser. */ @@ -88,8 +88,8 @@ declare namespace ast { * bytecodes to refer back to via index. */ literals?: string[]; - classes?: CharacterRange[]; - expectations?: Expectation[]; + classes?: GrammarCharacterClass[]; + expectations?: GrammarExpectation[]; functions?: FunctionConst[]; locations?: LocationRange[]; } diff --git a/test/types/peg.test-d.ts b/test/types/peg.test-d.ts index dc4e0829..ce1d4e0d 100644 --- a/test/types/peg.test-d.ts +++ b/test/types/peg.test-d.ts @@ -176,7 +176,6 @@ describe("peg.d.ts", () => { it("creates an AST", () => { const grammar = peggy.parser.parse(src); expectType(grammar); - const visited: { [typ: string]: number } = {}; function add(typ: string): void { if (!visited[typ]) { @@ -197,6 +196,17 @@ describe("peg.d.ts", () => { ); expectType(node.initializer); expectType(node.rules); + expectType(node.literals); + expectType(node.classes); + expectType( + node.expectations + ); + expectType( + node.functions + ); + expectType( + node.locations + ); if (node.topLevelInitializer) { visit(node.topLevelInitializer); diff --git a/test/unit/compiler/passes/generate-js.spec.js b/test/unit/compiler/passes/generate-js.spec.js new file mode 100644 index 00000000..7b13bd10 --- /dev/null +++ b/test/unit/compiler/passes/generate-js.spec.js @@ -0,0 +1,56 @@ +// @ts-check +"use strict"; + +const chai = require("chai"); +const pass = require("../../../../lib/compiler/passes/generate-js"); + +const { expect } = chai; +/** + * @typedef {import("../../../../lib/peg")} PEG + */ + +describe("compiler pass |generateJS|", () => { + describe("coverage", () => { + /** @type {PEG.ast.Grammar} */ + const ast = { + type: "grammar", + rules: [], + location: { + source: "", + start: { line:1, column:1, offset:0 }, + end: { line:1, column:1, offset:0 }, + }, + }; + const options + = /** @type {PEG.SourceBuildOptions} */({}); + it("throws unless various grammar fields are set", () => { + expect( + () => pass(ast, options) + ).to.throw(Error, "generateJS: generate bytecode was not called."); + ast.literals = []; + expect( + () => pass({ ...ast, literals:[] }, options) + ).to.throw(Error, "generateJS: generate bytecode was not called."); + ast.locations = []; + expect( + () => pass({ ...ast, literals:[] }, options) + ).to.throw(Error, "generateJS: generate bytecode was not called."); + ast.classes = []; + expect( + () => pass({ ...ast, literals:[] }, options) + ).to.throw(Error, "generateJS: generate bytecode was not called."); + ast.expectations = []; + expect( + () => pass({ ...ast, literals:[] }, options) + ).to.throw(Error, "generateJS: generate bytecode was not called."); + ast.functions = []; + expect( + () => pass(ast, options) + ).to.throw(Error, "generateJS: options.allowedStartRules was not set."); + options.allowedStartRules = ["start"]; + expect( + () => pass(ast, options) + ).to.not.throw(); + }); + }); +});