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

GrammarLocation #309

Merged
merged 10 commits into from
Feb 16, 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: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ Released: TBD
editors, from @Mingun
- [#299](https://github.com/peggyjs/peggy/issues/299) Add example grammar for a
[SemVer.org](https://semver.org) semantic version string, from @dselman
- [[#307](https://github.com/peggyjs/peggy/issues/307)] Allow grammars to have
relative offsets into their source files (e.g. if embedded in another doc),
from @hildjj.
- [#308](https://github.com/peggyjs/peggy/pull/308) Add support for reading test data from stdin using `-T -`, from @hildjj.

### Bug Fixes
Expand Down
6 changes: 6 additions & 0 deletions docs/documentation.html
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,12 @@ <h2 id="locations">Locations</h2>
because that string will be used when getting formatted error representation
with <a href="#error-messages"><code>e.format()</code></a>.</p>

<p>For certain special cases, you can use an instance of the
<code>GrammarLocation</code> class as the <code>grammarSource</code>.
<code>GrammarLocation</code> allows you to specify the offset of the grammar
source in another file, such as when that grammar is embedded in a larger
document.</p>

<p>If <code>source</code> is <code>null</code> or <code>undefined</code> it doesn't appear in the formatted messages.
The default value for <code>source</code> is <code>undefined</code>.</p>

Expand Down
2 changes: 1 addition & 1 deletion docs/js/benchmark-bundle.min.js

Large diffs are not rendered by default.

18 changes: 13 additions & 5 deletions docs/js/examples.js

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

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.

12 changes: 9 additions & 3 deletions lib/compiler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ function processOptions(options, defaults) {
return processedOptions;
}

function isSourceMapCapable(target) {
if (typeof target === "string") {
return target.length > 0;
}
return target && (typeof target.offset === "function");
}

const compiler = {
// AST node visitor builder. Useful mainly for plugins which manipulate the
// AST.
Expand Down Expand Up @@ -97,9 +104,8 @@ const compiler = {
// grammarSource is required
if (((options.output === "source-and-map")
|| (options.output === "source-with-inline-map"))
&& ((typeof options.grammarSource !== "string")
|| (options.grammarSource.length === 0))) {
throw new Error("Must provide grammarSource (as a string) in order to generate source maps");
&& !isSourceMapCapable(options.grammarSource)) {
throw new Error("Must provide grammarSource (as a string or GrammarLocation) in order to generate source maps");
}

const session = new Session(options);
Expand Down
49 changes: 30 additions & 19 deletions lib/compiler/passes/generate-js.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const Stack = require("../stack");
const VERSION = require("../../version");
const { stringEscape, regexpClassEscape } = require("../utils");
const { SourceNode } = require("source-map-generator");
const GrammarLocation = require("../../grammar-location");

/**
* Converts source text from the grammar into the `source-map` object
Expand All @@ -19,22 +20,23 @@ const { SourceNode } = require("source-map-generator");
* Code will be splitted by lines if necessary
*/
function toSourceNode(code, location, name) {
const line = location.start.line;
const start = GrammarLocation.offsetStart(location);
const line = start.line;
// `source-map` columns are 0-based, peggy columns is 1-based
const column = location.start.column - 1;
const column = start.column - 1;
const lines = code.split("\n");

if (lines.length === 1) {
return new SourceNode(
line, column, location.source, code, name
line, column, String(location.source), code, name
);
}

return new SourceNode(
null, null, location.source, lines.map((l, i) => new SourceNode(
null, null, String(location.source), lines.map((l, i) => new SourceNode(
line + i,
i === 0 ? column : 0,
location.source,
String(location.source),
i === lines.length - 1 ? l : [l, "\n"],
name
))
Expand All @@ -59,16 +61,17 @@ function wrapInSourceNode(prefix, chunk, location, suffix, name) {
// by a plugin and does not provide location information, see
// plugin-api.spec.js/"can replace parser") returns original chunk
if (location) {
return new SourceNode(null, null, location.source, [
const end = GrammarLocation.offsetEnd(location);
return new SourceNode(null, null, String(location.source), [
prefix,
toSourceNode(chunk, location, name),
// Mark end location with column information otherwise
// mapping will be always continue to the end of line
new SourceNode(
location.end.line,
end.line,
// `source-map` columns are 0-based, peggy columns is 1-based
location.end.column - 1,
location.source,
end.column - 1,
String(location.source),
suffix
),
]);
Expand Down Expand Up @@ -223,7 +226,7 @@ function generateJS(ast, options) {
"peg$tracer.trace({",
" type: \"rule.enter\",",
" rule: " + ruleNameCode + ",",
" location: peg$computeLocation(startPos, startPos)",
" location: peg$computeLocation(startPos, startPos, true)",
"});",
""
);
Expand All @@ -246,13 +249,13 @@ function generateJS(ast, options) {
" type: \"rule.match\",",
" rule: " + ruleNameCode + ",",
" result: cached.result,",
" location: peg$computeLocation(startPos, peg$currPos)",
" location: peg$computeLocation(startPos, peg$currPos, true)",
" });",
"} else {",
" peg$tracer.trace({",
" type: \"rule.fail\",",
" rule: " + ruleNameCode + ",",
" location: peg$computeLocation(startPos, startPos)",
" location: peg$computeLocation(startPos, startPos, true)",
" });",
"}",
""
Expand Down Expand Up @@ -287,13 +290,13 @@ function generateJS(ast, options) {
" type: \"rule.match\",",
" rule: " + ruleNameCode + ",",
" result: " + resultCode + ",",
" location: peg$computeLocation(startPos, peg$currPos)",
" location: peg$computeLocation(startPos, peg$currPos, true)",
" });",
"} else {",
" peg$tracer.trace({",
" type: \"rule.fail\",",
" rule: " + ruleNameCode + ",",
" location: peg$computeLocation(startPos, startPos)",
" location: peg$computeLocation(startPos, startPos, true)",
" });",
"}"
);
Expand Down Expand Up @@ -707,16 +710,19 @@ function generateJS(ast, options) {
" }",
" }",
" var s = this.location.start;",
" var loc = this.location.source + \":\" + s.line + \":\" + s.column;",
" var offset_s = (this.location.source && (typeof this.location.source.offset === \"function\"))",
" ? this.location.source.offset(s)",
" : s;",
" var loc = this.location.source + \":\" + offset_s.line + \":\" + offset_s.column;",
" if (src) {",
" var e = this.location.end;",
" var filler = peg$padEnd(\"\", s.line.toString().length, ' ');",
" var filler = peg$padEnd(\"\", offset_s.line.toString().length, ' ');",
" var line = src[s.line - 1];",
" var last = s.line === e.line ? e.column : line.length + 1;",
" var hatLen = (last - s.column) || 1;",
" str += \"\\n --> \" + loc + \"\\n\"",
" + filler + \" |\\n\"",
" + s.line + \" | \" + line + \"\\n\"",
" + offset_s.line + \" | \" + line + \"\\n\"",
hildjj marked this conversation as resolved.
Show resolved Hide resolved
" + filler + \" | \" + peg$padEnd(\"\", s.column - 1, ' ')",
" + peg$padEnd(\"\", hatLen, \"^\");",
" } else {",
Expand Down Expand Up @@ -1034,11 +1040,11 @@ function generateJS(ast, options) {
" }",
" }",
"",
" function peg$computeLocation(startPos, endPos) {",
" function peg$computeLocation(startPos, endPos, offset) {",
" var startPosDetails = peg$computePosDetails(startPos);",
" var endPosDetails = peg$computePosDetails(endPos);",
"",
" return {",
" var res = {",
" source: peg$source,",
" start: {",
" offset: startPos,",
Expand All @@ -1051,6 +1057,11 @@ function generateJS(ast, options) {
" column: endPosDetails.column",
" }",
" };",
" if (offset && peg$source && (typeof peg$source.offset === \"function\")) {",
" res.start = peg$source.offset(res.start);",
" res.end = peg$source.offset(res.end);",
" }",
" return res;",
" }",
"",
" function peg$fail(expected) {",
Expand Down
15 changes: 9 additions & 6 deletions lib/compiler/stack.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict";

const { SourceNode } = require("source-map-generator");
const GrammarLocation = require("../grammar-location.js");

/** Utility class that helps generating code for C-like languages. */
class Stack {
Expand Down Expand Up @@ -46,10 +47,11 @@ class Stack {
}

sourceNode(location, chunks, name) {
const start = GrammarLocation.offsetStart(location);
return new SourceNode(
location.start.line,
location.start.column ? location.start.column - 1 : null,
location.source,
start.line,
start.column ? start.column - 1 : null,
String(location.source),
chunks,
name
);
Expand Down Expand Up @@ -272,10 +274,11 @@ class Stack {
: chunk + "\n"
);
if (chunks.length) {
const start = GrammarLocation.offsetStart(location);
parts.push(new SourceNode(
location.start.line,
location.start.column - 1,
location.source,
start.line,
start.column - 1,
String(location.source),
chunks
));
}
Expand Down
13 changes: 9 additions & 4 deletions lib/grammar-error.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use strict";

const GrammarLocation = require("./grammar-location");

// See: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
// This is roughly what typescript generates, it's not called after super(), where it's needed.
// istanbul ignore next This is a special black magic that cannot be covered everywhere
Expand Down Expand Up @@ -98,6 +100,7 @@ class GrammarError extends Error {
let str = "";
const src = srcLines.find(({ source }) => source === location.source);
const s = location.start;
const offset_s = GrammarLocation.offsetStart(location);
if (src) {
const e = location.end;
const line = src.text[s.line - 1];
Expand All @@ -107,12 +110,12 @@ class GrammarError extends Error {
str += `\nnote: ${message}`;
}
str += `
--> ${location.source}:${s.line}:${s.column}
--> ${location.source}:${offset_s.line}:${offset_s.column}
${"".padEnd(indent)} |
${s.line.toString().padStart(indent)} | ${line}
${offset_s.line.toString().padStart(indent)} | ${line}
${"".padEnd(indent)} | ${"".padEnd(s.column - 1)}${"".padEnd(hatLen, "^")}`;
} else {
str += `\n at ${location.source}:${s.line}:${s.column}`;
str += `\n at ${location.source}:${offset_s.line}:${offset_s.column}`;
if (message) {
str += `: ${message}`;
}
Expand All @@ -135,7 +138,9 @@ ${"".padEnd(indent)} | ${"".padEnd(s.column - 1)}${"".padEnd(hatLen, "^")}`;
let maxLine;
if (location) {
maxLine = diagnostics.reduce(
(t, { location }) => Math.max(t, location.start.line),
(t, { location }) => Math.max(
t, GrammarLocation.offsetStart(location).line
),
location.start.line
);
} else {
Expand Down
Loading