Skip to content

Commit

Permalink
Add support for npm: loaded library grammars
Browse files Browse the repository at this point in the history
  • Loading branch information
hildjj committed Dec 21, 2023
1 parent 8a5e05d commit e158774
Show file tree
Hide file tree
Showing 17 changed files with 236 additions and 67 deletions.
26 changes: 22 additions & 4 deletions bin/peggy-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class PeggyCLI extends Command {

this
.version(peggy.VERSION, "-v, --version")
.argument("[input_file...]", 'Grammar file(s) to read. Use "-" to read stdin. If multiple files are given, they are combined in the given order to produce a single output.', ["-"])
.argument("[input_file...]", 'Grammar file(s) to read. Use "-" to read stdin. If multiple files are given, they are combined in the given order to produce a single output. Use npm:"<pacakgeName>/file.peggy" to import from an npm dependency.', ["-"])
.allowExcessArguments(false)
.addOption(
new Option(
Expand Down Expand Up @@ -298,10 +298,17 @@ class PeggyCLI extends Command {

if (!this.outputFile) {
if (this.inputFiles.indexOf("-") === -1) {
this.outputJS = this.inputFiles[0].slice(
let inFile = this.inputFiles[0];
// You might just want to run a fragment grammar as-is,
// particularyly with a specified start rule.
const m = inFile.match(/^npm:.*\/([^/]+)$/);
if (m) {
inFile = m[1];
}
this.outputJS = inFile.slice(
0,
this.inputFiles[0].length
- path.extname(this.inputFiles[0]).length
inFile.length
- path.extname(inFile).length
) + ".js";

this.outputFile = ((typeof this.progOptions.test !== "string")
Expand Down Expand Up @@ -636,6 +643,7 @@ class PeggyCLI extends Command {

let exitCode = 1;
let errorText = "";
let prevSource = process.cwd() + "/";
try {
for (const source of this.inputFiles) {
const input = { source, text: null };
Expand All @@ -644,7 +652,17 @@ class PeggyCLI extends Command {
input.source = "stdin";
this.std.in.resume();
input.text = await readStream(this.std.in);
} else if (source.startsWith("npm:")) {
const req = (
// In node 12+, createRequire is documented.
// In node 10, createRequireFromPath is the least-undocumented approach.
Module.createRequire || Module.createRequireFromPath
)(prevSource);
prevSource = req.resolve(source.slice(4));
input.source = prevSource;
input.text = await fs.promises.readFile(prevSource, "utf8");
} else {
prevSource = path.resolve(source);
input.text = await fs.promises.readFile(source, "utf8");
}
sources.push(input);
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.

6 changes: 5 additions & 1 deletion lib/compiler/asts.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ const visitor = require("./visitor");
function combinePossibleArrays(a, b) {
const aa = Array.isArray(a) ? a : [a];
const bb = Array.isArray(b) ? b : [b];
return aa.concat(bb);

// Put library code above code that uses it, so that variables
// will be in scope.
const res = bb.filter(x => x).concat(aa.filter(x => x)); // Remove nulls.
return res;
}

// AST utilities.
Expand Down
80 changes: 26 additions & 54 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,9 @@ function peg$parse(input, options) {
if (s3 !== peg$FAILED) {
s4 = peg$parse__();
s5 = peg$parseNameSpaceImport();
if (s5 === peg$FAILED) {
s5 = peg$parseNamedImports();
}
if (s5 !== peg$FAILED) {
s1 = [s1, s2, s3, s4, s5];
s0 = s1;
Expand All @@ -1013,37 +1016,6 @@ function peg$parse(input, options) {
peg$currPos = s0;
s0 = peg$FAILED;
}
if (s0 === peg$FAILED) {
s0 = peg$currPos;
s1 = peg$parseBindingIdentifier();
if (s1 !== peg$FAILED) {
s2 = peg$parse__();
if (input.charCodeAt(peg$currPos) === 44) {
s3 = peg$c4;
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e4); }
}
if (s3 !== peg$FAILED) {
s4 = peg$parse__();
s5 = peg$parseNamedImports();
if (s5 !== peg$FAILED) {
s1 = [s1, s2, s3, s4, s5];
s0 = s1;
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
}
}
}
}
Expand Down Expand Up @@ -1256,29 +1228,23 @@ function peg$parse(input, options) {
function peg$parseImportSpecifier() {
var s0, s1, s2, s3, s4, s5;

s0 = peg$parseBindingIdentifier();
if (s0 === peg$FAILED) {
s0 = peg$currPos;
s1 = peg$parseModuleExportName();
if (s1 !== peg$FAILED) {
s2 = peg$parse__();
if (input.substr(peg$currPos, 2) === peg$c6) {
s3 = peg$c6;
peg$currPos += 2;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e6); }
}
if (s3 !== peg$FAILED) {
s4 = peg$parse__();
s5 = peg$parseBindingIdentifier();
if (s5 !== peg$FAILED) {
s1 = [s1, s2, s3, s4, s5];
s0 = s1;
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
s0 = peg$currPos;
s1 = peg$parseModuleExportName();
if (s1 !== peg$FAILED) {
s2 = peg$parse__();
if (input.substr(peg$currPos, 2) === peg$c6) {
s3 = peg$c6;
peg$currPos += 2;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e6); }
}
if (s3 !== peg$FAILED) {
s4 = peg$parse__();
s5 = peg$parseBindingIdentifier();
if (s5 !== peg$FAILED) {
s1 = [s1, s2, s3, s4, s5];
s0 = s1;
} else {
peg$currPos = s0;
s0 = peg$FAILED;
Expand All @@ -1287,6 +1253,12 @@ function peg$parse(input, options) {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
if (s0 === peg$FAILED) {
s0 = peg$parseBindingIdentifier();
}

return s0;
Expand Down
7 changes: 3 additions & 4 deletions src/parser.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ ImportClause
= ImportedDefaultBinding
/ NameSpaceImport
/ NamedImports
/ ImportedDefaultBinding __ "," __ NameSpaceImport
/ ImportedDefaultBinding __ "," __ NamedImports
/ ImportedDefaultBinding __ "," __ (NameSpaceImport / NamedImports)

ImportedDefaultBinding = ImportedBinding
NameSpaceImport = "*" __ "as" __ ImportedBinding
Expand All @@ -103,8 +102,8 @@ NamedImports
FromClause = "from" __ ModuleSpecifier
ImportsList = ImportSpecifier|1.., __ "," __|
ImportSpecifier
= ImportedBinding
/ ModuleExportName __ "as" __ ImportedBinding
= ModuleExportName __ "as" __ ImportedBinding
/ ImportedBinding
ModuleSpecifier = StringLiteral
ImportedBinding
= BindingIdentifier
Expand Down
2 changes: 2 additions & 0 deletions test/cli/fixtures/frags/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.js
node_modules/
8 changes: 8 additions & 0 deletions test/cli/fixtures/frags/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "frags",
"version": "0.0.0",
"description": "Fragments of peggy grammars, in an npm package",
"private": true,
"author": "Joe Hildebrand <joe-github@cursive.net>",
"license": "MIT"
}
12 changes: 12 additions & 0 deletions test/cli/fixtures/frags/path.peggy
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{
// From path.peggy
import path from "path";

const PATH = "path";
}}

{
const PATH2 = "path2";
}

pth = p:$.* &{ return p === path.resolve(p) }
65 changes: 65 additions & 0 deletions test/cli/fixtures/frags/unicode.peggy
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{{
import path from 'node:path';
}}

{
const c = 'too';
}
IdentifierName
= $(IdentifierStart IdentifierPart*)

IdentifierStart
= UnicodeIDStart
/ "$"
/ "_"
/ "\\" UnicodeEscapeSequence

IdentifierPart
= UnicodeIDContinue
/ "$"
/ "\\" UnicodeEscapeSequence
/ "\u200C"
/ "\u200D"

UnicodeEscapeSequence
= "u" Hex4Digits
/ "u{" CodePoint "}"

CodePoint
= HexDigits ?{ return parseInt(n, 16) <= 0x10FFFF }

Hex4Digits
= HexDigit HexDigit HexDigit HexDigit

HexDigits
= HexDigit+

HexDigit = [0-9a-f]i


UnicodeIDStart = ID_Start

UnicodeIDContinue = ID_Continue

// Separator, Space
Zs = c:SourceCharacter &{ return /\p{Zs}/u.test(c) }

ID_Start
= c:SourceCharacter &{ return /\p{ID_Start}/u.test(c) }

ID_Continue
= c:SourceCharacter &{ return /\p{ID_Continue}/u.test(c) }

SourceCharacter
= SourceCharacterLow
/ SourceCharacterHigh

// Not surrogates
SourceCharacterLow
= [\u0000-\uD7FF\uE000-\uFFFF]

// Can be properly-matched surrogates or lone surrogates.
SourceCharacterHigh
= $([\uD800-\uDBFF][\uDC00-\uDFFF]) // Surrogate pair
/ [\uD800-\uDBFF] // Lone first surrogate
/ [\uDC00-\uDFFF] // Lone second surrogate
2 changes: 2 additions & 0 deletions test/cli/fixtures/useFrags/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.js
node_modules/
16 changes: 16 additions & 0 deletions test/cli/fixtures/useFrags/fs.peggy
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{{
// From fs.peggy
import {readFileSync as rfs1} from "fs";

const FS = "fs";
}}

{
// From fs.peggy
const pk = JSON.parse(rfs1('package.json', 'utf8'));
console.log({name: pk.name})
}

pkg
= p:$.* &{ return p === pk.name }
/ pth
1 change: 1 addition & 0 deletions test/cli/fixtures/useFrags/identifier.peggy
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
id = IdentifierName
24 changes: 24 additions & 0 deletions test/cli/fixtures/useFrags/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions test/cli/fixtures/useFrags/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "usefrags",
"version": "0.0.0",
"description": "Use fragments from another npm package",
"type": "module",
"private": "true",
"scripts": {
"build": "../../../../bin/peggy.js identifier.peggy npm:frags/unicode.peggy && ../../../../bin/peggy.js fs.peggy npm:frags/path.peggy"
},
"author": "Joe Hildebrand <joe-github@cursive.net>",
"license": "MIT",
"dependencies": {
"frags": "file:../frags"
}
}
33 changes: 32 additions & 1 deletion test/cli/run.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,9 @@ Arguments:
input_file Grammar file(s) to read. Use "-" to read
stdin. If multiple files are given, they
are combined in the given order to produce a
single output. (default: ["-"])
single output. Use
npm:"<pacakgeName>/file.peggy" to import
from an npm dependency. (default: ["-"])
Options:
-v, --version output the version number
Expand Down Expand Up @@ -850,6 +852,35 @@ Options:
fs.unlinkSync(out);
});

it("handles npm: sources", async() => {
let input1 = path.join(__dirname, "fixtures", "useFrags", "identifier.peggy");
let out = path.join(__dirname, "fixtures", "useFrags", "identifier.js");
await expect(exec({
args: [input1, "npm:frags/unicode.peggy"],
exitCode: 0,
})).resolves.toBe("");

fs.unlinkSync(out);

input1 = path.join(__dirname, "fixtures", "useFrags", "fs.peggy");
out = path.join(__dirname, "fixtures", "useFrags", "fs.js");
await expect(exec({
args: [input1, "npm:frags/path.peggy"],
exitCode: 0,
})).resolves.toBe("");

fs.unlinkSync(out);

out = path.join(__dirname, "..", "..", "path.js");
await expect(exec({
args: ["npm:frags/path.peggy"],

exitCode: 0,
})).resolves.toBe("");

fs.unlinkSync(out);
});

describe("handles source map", () => {
describe("with default name without --output", () => {
const sourceMap = path.resolve(__dirname, "..", "..", "source.map");
Expand Down

0 comments on commit e158774

Please sign in to comment.