Skip to content

Commit 7f20170

Browse files
committed
Reserved: Improve error when reserved word used as a label name and add documentation for plugins API
Before this commit error looks like (for input `start = break:'a'`) > Expected "!", "$", "&", "(", "*", "+", ".", "/", "/*", "//", ";", "?", character class, code block, comment, end of line, identifier, literal, or whitespace but ":" found. After this error looks like > Expected identifier but reserved word "break" found.
1 parent cc04fa0 commit 7f20170

File tree

4 files changed

+134
-6
lines changed

4 files changed

+134
-6
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ Released: TBD
7979
- `zero_or_more`
8080
- `one_or_more`
8181
- `group`
82+
- Add a new option `config.reservedWords: string[]`, avalible for plugins in their
83+
`use()` method. Using this option, a plugin can change the list of words that
84+
cannot be used.
85+
86+
By default this new option contains an array with [reserved JavaScript words][reserved]
87+
[@Mingun](https://github.com/peggyjs/peggy/pull/150)
88+
89+
[reserved]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_keywords_as_of_ecmascript_2015
8290

8391
### Bug fixes
8492

README.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ object to `peg.generate`. The following options are supported:
176176
- `output` — if set to `"parser"`, the method will return generated parser
177177
object; if set to `"source"`, it will return parser source code as a string
178178
(default: `"parser"`)
179-
- `plugins` — plugins to use
179+
- `plugins` — plugins to use. See the [Plugins API](#plugins-api) section
180180
- `trace` — makes the parser trace its progress (default: `false`)
181181

182182
## Using the Parser
@@ -453,15 +453,20 @@ If you need to return the matched text in an action, use the
453453
#### _label_ : _expression_
454454

455455
Match the expression and remember its match result under given label. The label
456-
must be a JavaScript identifier.
456+
must be a JavaScript identifier, but not in the list of reserved words.
457+
By default this is a list of [JavaScript reserved words][reserved],
458+
but [plugins](#plugins-api) can change it.
457459

458460
Labeled expressions are useful together with actions, where saved match results
459461
can be accessed by action's JavaScript code.
460462

461463
#### _@_ ( _label_ : )? _expression_
462464

463465
Match the expression and if the label exists, remember its match result under
464-
given label. The label must be a JavaScript identifier if it exists.
466+
given label. The label must be a JavaScript identifier if it exists, but not
467+
in the list of reserved words. By default this is a list of
468+
[JavaScript reserved words][reserved], but [plugins](#plugins-api) can
469+
change it.
465470

466471
Return the value of this expression from the rule, or "pluck" it. You may not
467472
have an action for this rule. The expression must not be a semantic predicate
@@ -721,6 +726,39 @@ Discussions page][unicode].
721726
[BMP]: https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
722727
[unicode]: https://github.com/peggyjs/peggy/discussions/15
723728

729+
## Plugins API
730+
731+
A plugin is an object with the `use(config, options)` method. That method will be
732+
called for all plugins in the `options.plugins` array, supplied to the `generate()`
733+
method.
734+
735+
`use` accepts those parameters:
736+
- `config` — object with the following properties:
737+
- `parser``Parser` object, by default the `peggy.parser` instance. That object
738+
will be used to parse the grammar. Plugin can replace this object
739+
- `passes` — mapping `{ [stage: string]: Pass[] }` that represents compilation
740+
stages that would applied to the AST, returned by the `parser` object. That
741+
mapping will contain at least the following keys:
742+
- `check` — passes that check AST for correctness. They shouldn't change the AST
743+
- `transform` — passes that performs various optimizations. They can change
744+
the AST, add or remove nodes or their properties
745+
- `generate` — passes used for actual code generating
746+
747+
Plugin that implement a pass usually should push it to the end of one of that
748+
arrays. Pass is a simple function with signature `pass(ast, options)`:
749+
- `ast` — the AST created by the `config.parser.parse()` method
750+
- `options` — compilation options passed to the `peggy.compiler.compile()` method.
751+
If parser generation is started because `generate()` function was called that
752+
is also an options, passed to the `generate()` method
753+
- `reservedWords` — string array with a list of words that shouldn't be used as
754+
label names. This list can be modified by plugins. That property is not required
755+
to be sorted or not contain duplicates, but it is recommend to remove duplicates.
756+
757+
Default list contains [JavaScript reserved words][reserved], and can be found
758+
in the `peggy.RESERVED_WORDS` property.
759+
- `options` — build options passed to the `generate()` method. A best practice for
760+
a plugin would look for its own options under a `<plugin_name>` key.
761+
724762
## Compatibility
725763

726764
Both the parser generator and generated parsers should run well in the following
@@ -749,3 +787,5 @@ Peggy was originally developed by [David Majda](https://majda.cz/)
749787
You are welcome to contribute code. Unless your contribution is really trivial
750788
you should [get in touch with us](https://github.com/peggyjs/peggy/discussions)
751789
first — this can prevent wasted effort on both sides.
790+
791+
[reserved]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_keywords_as_of_ecmascript_2015

docs/documentation.html

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ <h2 id="table-of-contents">Table of Contents</h2>
6868
</li>
6969
<li><a href="#error-messages">Error Messages</a></li>
7070
<li><a href="#locations">Locations</a></li>
71+
<li><a href="#plugins-api">Plugins API</a></li>
7172
<li><a href="#compatibility">Compatibility</a></li>
7273
</ul>
7374

@@ -246,7 +247,7 @@ <h3 id="generating-a-parser-javascript-api">JavaScript API</h3>
246247
a string (default: <code>"parser"</code>).</dd>
247248

248249
<dt><code>plugins</code></dt>
249-
<dd>Plugins to use.</dd>
250+
<dd>Plugins to use. See the [Plugins API](#plugins-api) section.</dd>
250251

251252
<dt><code>trace</code></dt>
252253
<dd>Makes the parser trace its progress (default: <code>false</code>).</dd>
@@ -565,7 +566,9 @@ <h3 id="grammar-syntax-and-semantics-parsing-expression-types">Parsing Expressio
565566

566567
<dd>
567568
<p>Match the expression and remember its match result under given label.
568-
The label must be a JavaScript identifier.</p>
569+
The label must be a JavaScript identifier, but not in the list of reserved words.
570+
By default this is a list of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_keywords_as_of_ecmascript_2015">JavaScript reserved words</a>,
571+
but <a href="#plugins-api">plugins</a> can change it.</p>
569572

570573
<p>Labeled expressions are useful together with actions, where saved match
571574
results can be accessed by action's JavaScript code.</p>
@@ -576,7 +579,9 @@ <h3 id="grammar-syntax-and-semantics-parsing-expression-types">Parsing Expressio
576579
<dd>
577580
<p>Match the expression and if the label exists, remember its match result
578581
under given label. The label must be a JavaScript identifier if it
579-
exists.</p>
582+
exists, but not in the list of reserved words.
583+
By default this is a list of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_keywords_as_of_ecmascript_2015">JavaScript reserved words</a>,
584+
but <a href="#plugins-api">plugins</a> can change it.</p>
580585

581586
<p>Return the value of this expression from the rule, or "pluck" it. You
582587
may not have an action for this rule. The expression must not be a
@@ -807,6 +812,61 @@ <h2 id="locations">Locations</h2>
807812
<p>Changing this behavior may be a breaking change and will not to be done before
808813
Peggy 2.0. You can join to the discussion for this topic on the <a href="https://github.com/peggyjs/peggy/discussions/15">GitHub Discussions page</a>.</p>
809814

815+
<h2 id="plugins-api">Plugins API</h2>
816+
817+
<p>A plugin is an object with the <code>use(config, options)</code> method. That method will be
818+
called for all plugins in the <code>options.plugins</code> array, supplied to the <code>generate()</code>
819+
method.</p>
820+
821+
<p><code>use</code> accepts those parameters:</p>
822+
823+
<h3><code>config</code></h3>
824+
<p>Object with the following properties:</p>
825+
826+
<dl>
827+
<dt><code>parser</code></dt>
828+
<dd><code>Parser</code> object, by default the <code>peggy.parser</code> instance. That object
829+
will be used to parse the grammar. Plugin can replace this object</dd>
830+
831+
<dt><code>passes</code></dt>
832+
<dd>
833+
<p>Mapping <code>{ [stage: string]: Pass[] }</code> that represents compilation
834+
stages that would applied to the AST, returned by the <code>parser</code> object. That
835+
mapping will contain at least the following keys:</p>
836+
837+
<ul>
838+
<li><code>check</code> — passes that check AST for correctness. They shouldn't change the AST</li>
839+
<li><code>transform</code> — passes that performs various optimizations. They can change
840+
the AST, add or remove nodes or their properties</li>
841+
<li><code>generate</code> — passes used for actual code generating</li>
842+
</ul>
843+
844+
<p>Plugin that implement a pass usually should push it to the end of one of that
845+
arrays. Pass is a simple function with signature <code>pass(ast, options)</code>:</p>
846+
847+
<ul>
848+
<li><code>ast</code> — the AST created by the <code>config.parser.parse()</code> method</li>
849+
<li><code>options</code> — compilation options passed to the <code>peggy.compiler.compile()</code> method.
850+
If parser generation is started because <code>generate()</code> function was called that
851+
is also an options, passed to the <code>generate()</code> method</li>
852+
</ul>
853+
</dd>
854+
855+
<dt><code>reservedWords</code></dt>
856+
<dd>
857+
<p>String array with a list of words that shouldn't be used as
858+
label names. This list can be modified by plugins. That property is not required
859+
to be sorted or not contain duplicates, but it is recommend to remove duplicates.</p>
860+
861+
<p>Default list contains <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_keywords_as_of_ecmascript_2015">JavaScript reserved words</a>, and can be found
862+
in the <code>peggy.RESERVED_WORDS</code> property.</p>
863+
</dd>
864+
</dl>
865+
866+
<h3><code>options</code></h3>
867+
<dd>Build options passed to the <code>generate()</code> method. A best practice for
868+
a plugin would look for its own options under a <code>&lt;plugin_name&gt;</code> key.</dd>
869+
810870
<h2 id="compatibility">Compatibility</h2>
811871

812872
<p>Both the parser generator and generated parsers should run well in the

test/api/plugin-api.spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ describe("plugin API", function() {
4646
config.passes.generate.forEach(pass => {
4747
expect(pass).to.be.a("function");
4848
});
49+
50+
expect(config.reservedWords).to.be.an("array");
51+
config.reservedWords.forEach(word => {
52+
expect(word).to.be.a("string");
53+
});
4954
}
5055
};
5156

@@ -104,6 +109,21 @@ describe("plugin API", function() {
104109
expect(parser.parse("a")).to.equal(42);
105110
});
106111

112+
it("can change list of reserved words", function() {
113+
const plugin = {
114+
use(config) {
115+
config.reservedWords = [];
116+
}
117+
};
118+
119+
expect(() => {
120+
peg.generate(
121+
"start = " + peg.RESERVED_WORDS[0] + ":'a'",
122+
{ plugins: [plugin], output: "source" }
123+
);
124+
}).to.not.throw();
125+
});
126+
107127
it("can change options", function() {
108128
const grammar = [
109129
"a = 'x'",

0 commit comments

Comments
 (0)