Skip to content

Commit

Permalink
Merge pull request #9 from hildjj/features
Browse files Browse the repository at this point in the history
features
  • Loading branch information
hildjj authored Nov 20, 2024
2 parents 8c456e3 + e802e5d commit e81b9d2
Show file tree
Hide file tree
Showing 12 changed files with 615 additions and 98 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
Expand All @@ -25,6 +25,12 @@ jobs:
- run: npm run build
- run: npm run test
- run: npm pkg delete devDependencies scripts packageManager
- name: Deploy Docs
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs
publish_branch: gh-pages
- run: npm publish --access public --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
coverage/
node_modules/
.vscode/
docs/
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
.ncurc
.vscode/
coverage/
docs/
eslint.config.js
test/
tsconfig.json
typedoc.config.js
41 changes: 32 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,39 +38,62 @@ Each test definition has the following type:
```ts
export type PeggyTestOptions<T> = {
/**
* Which valid start rule to use? Default: grammar
* default start rule.
* Which valid start rule to use? Default: grammar default start rule.
*/
startRule?: string | undefined;
/**
* If specified, check this against the startRule.
*/
validInput?: string | undefined;
/**
* What result should startRule return for validInput?
* Default: validInput.
* What result should startRule return for validInput? Default:
* validInput.
*/
validResult?: T | undefined;
validResult?: T | ((res: T) => any) | undefined;
/**
* If specified, ensure that the grammar fails
* on this input.
* If specified, ensure that the grammar fails on this input.
*/
invalidInput?: string | undefined;
/**
* Expected peg$maxFailPos.
*/
peg$maxFailPos?: number | undefined;
/**
* What to append to validInput to make it
* invalid, so that library mode will return a prefix match.
* What to append to validInput to make it invalid, so that library mode
* will return a prefix match.
*/
invalid?: string | undefined;
/**
* If any test has this set to true, only run the tests with this set to
* true.
*/
only?: boolean | undefined;
/**
* If true, skip this test.
*/
skip?: boolean | undefined;
/**
* Extra options to pass to parse(), overriding whatever else this library
* would have otherwise used.
*/
options?: (import("peggy").ParserOptions & ExtraParserOptions) | undefined;
};

export type ExtraParserOptions = {
/**
* In the augmented code only, use this function as the start rule rather
* than the default. This gives access to functions that are NOT valid
* start rules for internal testing.
*/
peg$startRuleFunction?: string | undefined;
/**
* Number of times for each of the given rules to succeed before they
* fail. Only applies in the augmented code.
*/
peg$failAfter?: {
[ruleName: string]: number;
} | undefined;
};
```
## Runtime support
Expand Down
21 changes: 19 additions & 2 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @typedef {object} TestPeggyOptions
* @prop {boolean} [noDelete] Do not delete the generated file.
* @prop {boolean} [noMap] Do not add a sourcemap to the generated file.
* Defaults to true if peggy$debugger is set on any start, otherwise false.
* Defaults to true if peg$debugger is set on any start, otherwise false.
* @prop {boolean} [noGenerate] Do not generate a file, only run tests on the
* original.
* @prop {boolean} [noOriginal] Do not run tests on the original code, only
Expand Down Expand Up @@ -32,6 +32,14 @@ export type ExtraParserOptions = {
* to functions that are NOT valid start rules for internal testing.
*/
peg$startRuleFunction?: string | undefined;
/**
* Number of times for each of the
* given rules to succeed before they fail. Only applies in the augmented
* code.
*/
peg$failAfter?: {
[ruleName: string]: number;
} | undefined;
};
export type PeggyTestOptions<T> = {
/**
Expand Down Expand Up @@ -62,6 +70,15 @@ export type PeggyTestOptions<T> = {
* invalid, so that library mode will return a prefix match.
*/
invalid?: string | undefined;
/**
* If any test has this set to true, only run the
* tests with this set to true.
*/
only?: boolean | undefined;
/**
* If true, skip this test.
*/
skip?: boolean | undefined;
/**
* Extra options to pass to parse(), overriding whatever else this library
* would have otherwise used.
Expand All @@ -82,7 +99,7 @@ export type TestPeggyOptions = {
noDelete?: boolean | undefined;
/**
* Do not add a sourcemap to the generated file.
* Defaults to true if peggy$debugger is set on any start, otherwise false.
* Defaults to true if peg$debugger is set on any start, otherwise false.
*/
noMap?: boolean | undefined;
/**
Expand Down
94 changes: 82 additions & 12 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ let counter = 0;
* @prop {string} [peg$startRuleFunction] In the augmented code only, use this
* function as the start rule rather than the default. This gives access
* to functions that are NOT valid start rules for internal testing.
* @prop {{[ruleName: string]: number}} [peg$failAfter] Number of times for each of the
* given rules to succeed before they fail. Only applies in the augmented
* code.
*/

/**
Expand All @@ -41,6 +44,9 @@ let counter = 0;
* @prop {number} [peg$maxFailPos = 0] Expected peg$maxFailPos.
* @prop {string} [invalid = "\uffff"] What to append to validInput to make it
* invalid, so that library mode will return a prefix match.
* @prop {boolean} [only] If any test has this set to true, only run the
* tests with this set to true.
* @prop {boolean} [skip] If true, skip this test.
* @prop {import('peggy').ParserOptions & ExtraParserOptions} [options = {}]
* Extra options to pass to parse(), overriding whatever else this library
* would have otherwise used.
Expand All @@ -63,28 +69,49 @@ let counter = 0;
* @param {TestCounts} counts
*/
function checkParserStarts(grammar, starts, modified, counts) {
let count = 0;
const M = modified ? "M_" : "U_";
const only = starts.some(s => s.only);
for (const start of starts) {
let source = `${M}valid_${count++}`;
const startRule = start.startRule || undefined; // NOT `??`
const peg$maxFailPos = start.peg$maxFailPos ?? undefined;
const options = start.options ?? {};
const options = start.options ?? Object.create(null);
const invalid = start.invalid ?? INVALID;
if (!modified && options.peg$startRuleFunction) {
if (start.skip || (only && !start.only)) {
continue;
}
if (!modified) {
if (options.peg$startRuleFunction || options.peg$failAfter) {
continue;
}
}
if (typeof start.validInput === "string") {
// Note: validResult might be specified as undefined.
const expected = Object.prototype.hasOwnProperty.call(start, "validResult")
? start.validResult
: start.validInput;

let res = grammar.parse(start.validInput, { startRule, ...options });
// eslint-disable-next-line no-useless-assignment
let res = undefined;
try {
res = grammar.parse(start.validInput, {
startRule,
grammarSource: source,
...options,
});
} catch (er) {
const e = /** @type {import('peggy').parser.SyntaxError} */ (er);
e.message = e.format([{ source, text: start.validInput }]);
throw e;
}
if (typeof expected === "string") {
equal(res, expected);
equal(res, expected, `${source} (eq): "${start.validInput}"`);
} else if (typeof expected === "function") {
// @ts-expect-error Can't figure this out
res = expected(res);
} else {
deepEqual(res, expected);
deepEqual(res, expected, `${source} (dp_eq): "${start.validInput}"`);
}

const expectedLib = {
Expand All @@ -96,37 +123,51 @@ function checkParserStarts(grammar, starts, modified, counts) {
if (peg$maxFailPos === undefined) {
delete expectedLib.peg$maxFailPos;
}
source = `${M}valid_library_${count}`;
// No exception in library mode
let lib = grammar.parse(start.validInput, {
peg$library: true,
startRule,
grammarSource: source,
...options,
});
if (lib.peg$result === lib.peg$FAILED) {
throw new Error(`Failed in library context: ${source}`);
}

// @ts-expect-error This is for testing.
delete lib.peg$maxFailExpected;
if (peg$maxFailPos === undefined) {
// @ts-expect-error This is for testing.
delete lib.peg$maxFailPos;
}

if (typeof expected === "function") {
// @ts-ignore
lib.peg$result = expected(lib.peg$result);
}
deepEqual(lib, expectedLib);
deepEqual(lib, expectedLib, `${source} (dp_eq): "${start.validInput}"`);

if (invalid) {
source = `${M}valid+_${count}`;
lib = grammar.parse(start.validInput + invalid, {
peg$library: true,
startRule,
grammarSource: source,
...options,
});

// @ts-expect-error This is for testing.
delete lib.peg$maxFailExpected;
if (peg$maxFailPos === undefined) {
// @ts-expect-error This is for testing.
delete lib.peg$maxFailPos;
}
if (typeof expected === "function") {
// @ts-ignore
lib.peg$result = expected(lib.peg$result);
}
deepEqual(lib, expectedLib);
deepEqual(lib, expectedLib, `${source} (dp_eq): "${start.validInput}"`);

throws(() => grammar.parse(start.validInput + invalid, {
startRule,
Expand All @@ -143,9 +184,11 @@ function checkParserStarts(grammar, starts, modified, counts) {
startRule,
...options,
});
fail("Cannot reach here");
fail("Expected error but none received");
} catch (er) {
ok(er instanceof grammar.SyntaxError);
if (!(er instanceof grammar.SyntaxError)) {
throw er;
}
equal(typeof er.format, "function");
let fmt = er.format([{ source: "test", text: start.invalidInput }]);
equal(typeof fmt, "string");
Expand Down Expand Up @@ -186,7 +229,7 @@ function checkParserStarts(grammar, starts, modified, counts) {
* @typedef {object} TestPeggyOptions
* @prop {boolean} [noDelete] Do not delete the generated file.
* @prop {boolean} [noMap] Do not add a sourcemap to the generated file.
* Defaults to true if peggy$debugger is set on any start, otherwise false.
* Defaults to true if peg$debugger is set on any start, otherwise false.
* @prop {boolean} [noGenerate] Do not generate a file, only run tests on the
* original.
* @prop {boolean} [noOriginal] Do not run tests on the original code, only
Expand Down Expand Up @@ -274,13 +317,31 @@ export async function testPeggy(grammarUrl, starts, opts) {
if (/^\s*peg\$result = peg\$startRuleFunction\(\);/.test(line)) {
src.add(`\
//#region Inserted by @peggyjs/coverage
let replacements = Object.create(null);
(() => {
if (options.peg$debugger) {
debugger;
}
if (options.peg$startRuleFunction) {
peg$startRuleFunction = eval(options.peg$startRuleFunction); // Ew.
}
if (options.peg$failAfter) {
for (const [name, count] of Object.entries(options.peg$failAfter)) {
const fn = eval(name); // Ew, again.
replacements[name] = fn;
if (typeof fn !== 'function') {
throw new Error('Unknown function for peg$failAfter: ' + name);
}
let remains = count;
const replace = name + \` = (...args) => {
if (remains-- > 0) {
return fn.call(this, ...args)
}
return peg$FAILED;
}\`;
eval(replace); // We've already called eval twice. What's one more?
}
}
text();
offset();
Expand Down Expand Up @@ -346,8 +407,18 @@ export async function testPeggy(grammarUrl, starts, opts) {
//#endregion
`);
src.add(new SourceNode(lineNum++, 0, grammarPath, line));
src.add(`
//#region Inserted by @peggyjs/coverage
for (const [name, orig] of Object.entries(replacements)) {
eval(name + '= orig');
}
replacements = Object.create(null);
//#endregion
`);
} else {
src.add(new SourceNode(lineNum++, 0, grammarPath, line));
}
src.add(new SourceNode(lineNum++, 0, grammarPath, line));
}

const withMap = src.toStringWithSourceMap();
Expand All @@ -362,7 +433,6 @@ export async function testPeggy(grammarUrl, starts, opts) {
${start} sourceMappingURL=data:application/json;charset=utf-8;base64,${map}
`;
}

await fs.writeFile(modifiedPath, code);
try {
const agrammar = /** @type {Parser} */ (
Expand Down
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,23 @@
"test": "c8 node --test test/*.test.js"
},
"dependencies": {
"peggy": "4.1.1",
"peggy": "4.2.0",
"source-map-generator": "0.8.0"
},
"devDependencies": {
"@peggyjs/eslint-config": "5.0.1",
"@types/node": "22.9.0",
"@types/node": "22.9.1",
"c8": "10.1.2",
"eslint": "9.14.0",
"eslint": "9.15.0",
"typedoc": "0.26.11",
"typescript": "5.6.3"
},
"packageManager": "pnpm@9.12.3",
"packageManager": "pnpm@9.14.2",
"pnpm": {
"overrides": {
"@eslint/plugin-kit": "^0.2.3",
"braces": "^3.0.3",
"cross-spawn": "^7.0.6",
"micromatch": "^4.0.8"
}
},
Expand Down
Loading

0 comments on commit e81b9d2

Please sign in to comment.