Skip to content

Commit 6b39853

Browse files
authored
Merge pull request #160 from Mingun/session
API for registering errors, warnings and informational messages
2 parents a9bf4e7 + 4ba81f8 commit 6b39853

23 files changed

+757
-105
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ Released: TBD
1313
- Add support for generating source maps.
1414
[@Mingun](https://github.com/peggyjs/peggy/pull/163)
1515

16+
- Introduce an API for reporting errors, warnings and information messages from
17+
passes. New API allows reporting several diagnostics at once with intermediate
18+
results checking after each compilation stage.
19+
[@Mingun](https://github.com/peggyjs/peggy/pull/160)
20+
1621
### Minor Changes
1722

1823
- New CLI [@hildjj](https://github.com/peggyjs/peggy/pull/167)

README.md

+82-2
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ object to `peg.generate`. The following options are supported:
244244
_global initializer_ and the _per-parse initializer_. Unless the parser is
245245
to be generated in different formats, it is recommended to rather import
246246
dependencies from within the _global initializer_. (default: `{}`)
247+
- `error` — a callback for errors. See [Error Reporting](#error-reporting)
247248
- `exportVar` — name of a global variable into which the parser object is
248249
assigned to when no module loader is detected; valid only when `format` is
249250
set to `"globals"` or `"umd"` (default: `null`)
@@ -254,6 +255,7 @@ object to `peg.generate`. The following options are supported:
254255
`source` property (default: `undefined`). This object will be used even if
255256
`options.grammarSource` is redefined in the grammar. It is useful to attach
256257
the file information to the errors, for example
258+
- `info` — a callback for informational messages. See [Error Reporting](#error-reporting)
257259
- `output` — if set to `"parser"`, the method will return generated parser
258260
object; if set to `"source"`, it will return parser source code as a string.
259261
If set to `"source-and-map"`, it will return a [`SourceNode`] object; you can
@@ -265,6 +267,27 @@ object to `peg.generate`. The following options are supported:
265267
> a not-empty string if you set this value to `"source-and-map"`
266268
- `plugins` — plugins to use. See the [Plugins API](#plugins-api) section
267269
- `trace` — makes the parser trace its progress (default: `false`)
270+
- `warning` — a callback for warnings. See [Error Reporting](#error-reporting)
271+
272+
#### Error Reporting
273+
274+
While generating the parser, the compiler may throw a `GrammarError` which collects
275+
all of the issues that were seen.
276+
277+
There is also another way to collect problems as fast as they are reported —
278+
register one or more of these callbacks:
279+
280+
- `error(stage: Stage, message: string, location?: LocationRange, notes?: DiagnosticNote[]): void`
281+
- `warning(stage: Stage, message: string, location?: LocationRange, notes?: DiagnosticNote[]): void`
282+
- `info(stage: Stage, message: string, location?: LocationRange, notes?: DiagnosticNote[]): void`
283+
284+
All parameters are the same as the parameters of the [reporting API](#session-api)
285+
except the first. The `stage` represent one of possible stages during which execution
286+
a diagnostic was generated. This is a string enumeration, that currently has one of
287+
three values:
288+
- `check`
289+
- `transform`
290+
- `generate`
268291

269292
[`SourceNode`]: https://github.com/mozilla/source-map#sourcenode
270293
[source-map/444]: https://github.com/mozilla/source-map/issues/444
@@ -764,6 +787,10 @@ note: Step 3: call itself without input consumption - left recursion
764787
| ^^^^^
765788
```
766789

790+
A plugin may register additional passes that can generate `GrammarError`s to report
791+
about problems, but they shouldn't do that by throwing an instance of `GrammarError`.
792+
They should use a [session API](#session-api) instead.
793+
767794
## Locations
768795

769796
During the parsing you can access to the information of the current parse location,
@@ -858,12 +885,14 @@ method.
858885
the AST, add or remove nodes or their properties
859886
- `generate` — passes used for actual code generating
860887

861-
A plugin that implement a pass usually should push it to the end of one of that
862-
arrays. Pass is a simple function with signature `pass(ast, options)`:
888+
A plugin that implement a pass usually should push it to the end of the correct
889+
array. Pass is a simple function with signature `pass(ast, options, session)`:
863890
- `ast` — the AST created by the `config.parser.parse()` method
864891
- `options` — compilation options passed to the `peggy.compiler.compile()` method.
865892
If parser generation is started because `generate()` function was called that
866893
is also an options, passed to the `generate()` method
894+
- `session` — a [`Session`](#session-api) object that allows raising errors,
895+
warnings and informational messages
867896
- `reservedWords` — string array with a list of words that shouldn't be used as
868897
label names. This list can be modified by plugins. That property is not required
869898
to be sorted or not contain duplicates, but it is recommend to remove duplicates.
@@ -873,6 +902,57 @@ method.
873902
- `options` — build options passed to the `generate()` method. A best practice for
874903
a plugin would look for its own options under a `<plugin_name>` key.
875904

905+
### Session API
906+
907+
Each compilation request is represented by a `Session` instance. An object of this class
908+
is created by the compiler and passed to an each pass as a 3rd parameter. The session
909+
object gives access to the various compiler services. At the present time there is only
910+
one such service: reporting of diagnostics.
911+
912+
All diagnostics are divided into three groups: errors, warnings and informational
913+
messages. For each of them the `Session` object has a method, described below.
914+
915+
All reporting methods have an identical signature:
916+
917+
```typescript
918+
(message: string, location?: LocationRange, notes?: DiagnosticNote[]) => void;
919+
```
920+
921+
- `message`: a main diagnostic message
922+
- `location`: an optional location information if diagnostic is related to the grammar
923+
source code
924+
- `notes`: an array with additional details about diagnostic, pointing to the
925+
different places in the grammar. For example, each note could be a location of
926+
a duplicated rule definition
927+
928+
#### `error(...)`
929+
930+
Reports an error. Compilation process is subdivided into pieces called _stages_ and
931+
each stage consist of one or more _passes_. Within the one stage all errors, reported
932+
by different passes, are collected without interrupting the parsing process.
933+
934+
When all passes in the stage are completed, the stage is checked for errors. If one
935+
was registered, a `GrammarError` with all found problems in the `problems` property
936+
is thrown. If there are no errors, then the next stage is processed.
937+
938+
After processing all three stages (`check`, `transform` and `generate`) the compilation
939+
process is finished.
940+
941+
The process, described above, means that passes should be careful about what they do.
942+
For example, if you place your pass into the `check` stage there is no guarantee that
943+
all rules exists, because checking for existing rules is also performed during the
944+
`check` stage. On the contrary, passes in the `transform` and `generate` stages can be
945+
sure that all rules exists, because that precondition was checked on the `check` stage.
946+
947+
#### `warning(...)`
948+
949+
Reports a warning. Warnings are similar to errors, but they do not interrupt a compilation.
950+
951+
#### `info(...)`
952+
953+
Report an informational message. This method can be used to inform user about significant
954+
changes in the grammar, for example, replacing proxy rules.
955+
876956
## Compatibility
877957

878958
Both the parser generator and generated parsers should run well in the following

bin/peggy.js

+18-11
Original file line numberDiff line numberDiff line change
@@ -2897,13 +2897,10 @@ function abort(message) {
28972897
process.exit(1);
28982898
}
28992899

2900-
function showError(msg, error) {
2900+
function showError(msg, error, sources) {
29012901
console.error(msg);
29022902
if (typeof error.format === "function") {
2903-
console.error(error.format([{
2904-
source: testGrammarSource,
2905-
text: testText,
2906-
}]));
2903+
console.error(error.format(sources));
29072904
} else {
29082905
if (verbose) {
29092906
console.error(error);
@@ -3068,6 +3065,7 @@ const PROG_DEFAULTS = {
30683065
verbose: false,
30693066
};
30703067

3068+
/** @type BuildOptionsBase */
30713069
const options = Object.assign({}, PARSER_DEFAULTS);
30723070
const progOptions = Object.assign({}, PROG_DEFAULTS);
30733071

@@ -3222,7 +3220,9 @@ if (verbose) {
32223220
}));
32233221
console.error(`INPUT: "${inputFile}"`);
32243222
console.error(`OUTPUT: "${outputFile}"`);
3223+
options.info = (pass, msg) => console.error(`INFO(${pass}): ${msg}`);
32253224
}
3225+
options.warning = (pass, msg) => console.error(`WARN(${pass}): ${msg}`);
32263226

32273227
// Main
32283228

@@ -3247,7 +3247,11 @@ readStream(inputStream, input => {
32473247
try {
32483248
source = peggy__default["default"].generate(input, options);
32493249
} catch (e) {
3250-
abortError("Error parsing grammar:", e);
3250+
showError("Error parsing grammar:", e, [{
3251+
source: options.grammarSource,
3252+
text: input,
3253+
}]);
3254+
process.exit(1);
32513255
}
32523256

32533257
// If there is a valid outputFile, write the parser to it. Otherwise,
@@ -3266,7 +3270,7 @@ readStream(inputStream, input => {
32663270

32673271
if (progOptions.sourceMap) {
32683272
if (!outputStream) {
3269-
// outputStream is null if `--test/--test-file` is specified, but `--output` is not
3273+
// Null if `--test/--test-file` is specified, but `--output` is not
32703274
abort("Generation of the source map is useless if you don't store a generated parser code, perhaps you forgot to add an `-o/--output` option?");
32713275
}
32723276

@@ -3282,9 +3286,9 @@ readStream(inputStream, input => {
32823286
// relative to the map file. Compiler cannot generate right paths, because
32833287
// it is unaware of the source map location
32843288
const json = source3.map.toJSON();
3285-
json.sources = json.sources.map(src => {
3286-
return src === null ? null : path__default["default"].relative(mapDir, src);
3287-
});
3289+
json.sources = json.sources.map(src => (src === null)
3290+
? null
3291+
: path__default["default"].relative(mapDir, src));
32883292

32893293
const sourceMapStream = fs__default["default"].createWriteStream(progOptions.sourceMap);
32903294
sourceMapStream.on("error", () => {
@@ -3322,7 +3326,10 @@ readStream(inputStream, input => {
33223326
maxStringLength: Infinity,
33233327
}));
33243328
} catch (e) {
3325-
showError("Error parsing test:", e);
3329+
showError("Error parsing test:", e, [{
3330+
source: testGrammarSource,
3331+
text: testText,
3332+
}]);
33263333
// We want to wait until source code/source map will be written
33273334
process.exitCode = 2;
33283335
}

bin/peggy.mjs

+18-11
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,10 @@ function abort(message) {
1515
process.exit(1);
1616
}
1717

18-
function showError(msg, error) {
18+
function showError(msg, error, sources) {
1919
console.error(msg);
2020
if (typeof error.format === "function") {
21-
console.error(error.format([{
22-
source: testGrammarSource,
23-
text: testText,
24-
}]));
21+
console.error(error.format(sources));
2522
} else {
2623
if (verbose) {
2724
console.error(error);
@@ -186,6 +183,7 @@ const PROG_DEFAULTS = {
186183
verbose: false,
187184
};
188185

186+
/** @type BuildOptionsBase */
189187
const options = Object.assign({}, PARSER_DEFAULTS);
190188
const progOptions = Object.assign({}, PROG_DEFAULTS);
191189

@@ -340,7 +338,9 @@ if (verbose) {
340338
}));
341339
console.error(`INPUT: "${inputFile}"`);
342340
console.error(`OUTPUT: "${outputFile}"`);
341+
options.info = (pass, msg) => console.error(`INFO(${pass}): ${msg}`);
343342
}
343+
options.warning = (pass, msg) => console.error(`WARN(${pass}): ${msg}`);
344344

345345
// Main
346346

@@ -365,7 +365,11 @@ readStream(inputStream, input => {
365365
try {
366366
source = peggy.generate(input, options);
367367
} catch (e) {
368-
abortError("Error parsing grammar:", e);
368+
showError("Error parsing grammar:", e, [{
369+
source: options.grammarSource,
370+
text: input,
371+
}]);
372+
process.exit(1);
369373
}
370374

371375
// If there is a valid outputFile, write the parser to it. Otherwise,
@@ -384,7 +388,7 @@ readStream(inputStream, input => {
384388

385389
if (progOptions.sourceMap) {
386390
if (!outputStream) {
387-
// outputStream is null if `--test/--test-file` is specified, but `--output` is not
391+
// Null if `--test/--test-file` is specified, but `--output` is not
388392
abort("Generation of the source map is useless if you don't store a generated parser code, perhaps you forgot to add an `-o/--output` option?");
389393
}
390394

@@ -400,9 +404,9 @@ readStream(inputStream, input => {
400404
// relative to the map file. Compiler cannot generate right paths, because
401405
// it is unaware of the source map location
402406
const json = source3.map.toJSON();
403-
json.sources = json.sources.map(src => {
404-
return src === null ? null : path.relative(mapDir, src);
405-
});
407+
json.sources = json.sources.map(src => (src === null)
408+
? null
409+
: path.relative(mapDir, src));
406410

407411
const sourceMapStream = fs.createWriteStream(progOptions.sourceMap);
408412
sourceMapStream.on("error", () => {
@@ -440,7 +444,10 @@ readStream(inputStream, input => {
440444
maxStringLength: Infinity,
441445
}));
442446
} catch (e) {
443-
showError("Error parsing test:", e);
447+
showError("Error parsing test:", e, [{
448+
source: testGrammarSource,
449+
text: testText,
450+
}]);
444451
// We want to wait until source code/source map will be written
445452
process.exitCode = 2;
446453
}

0 commit comments

Comments
 (0)