Skip to content

Commit 930acf4

Browse files
committed
Add new output type "ast"
1 parent b7f7723 commit 930acf4

File tree

10 files changed

+113
-12
lines changed

10 files changed

+113
-12
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ Released: TBD
2323
- New CLI [@hildjj](https://github.com/peggyjs/peggy/pull/167)
2424
- Backward compatible with the previous
2525
- New -t/--test and -T/--testfile flags to directly test the generated grammar
26+
- New -a/--ast flag flags to output a grammar AST for exploration
2627
- Check allowedStartRules for validity [@hildjj](https://github.com/peggyjs/peggy/pull/175)
28+
- New output type `ast` to get an internal grammar AST for investigation (can be
29+
useful for plugin writers) [@Mingun](https://github.com/peggyjs/peggy/pull/175)
2730

2831
### Bug fixes
2932

README.md

+10-5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ You can tweak the generated parser with several options:
103103
- `--allowed-start-rules <rules>` — comma-separated list of rules the parser
104104
will be allowed to start parsing from (default: only the first rule in the
105105
grammar)
106+
- `-a`, `--ast` — outputting an internal AST representation of the grammar after
107+
all optimizations instead of the parser source code. Useful for plugin authors
108+
to see how their plugin changes the AST. This option cannot be mixed with the
109+
`-t/--test` and `-T/--test-file` options
106110
- `--cache` — makes the parser cache results, avoiding exponential parsing
107111
time in pathological cases but making the parser slower
108112
- `-d`, `--dependency <[name:]module>` — makes the parser require a specified
@@ -117,14 +121,14 @@ You can tweak the generated parser with several options:
117121
or commonjs module format, as an object) to pass to `peg.generate`
118122
- `--format <format>` — format of the generated parser: `amd`, `es`, `commonjs`,
119123
`globals`, `umd` (default: `commonjs`)
120-
- `-o`, `--output <file>` - file to send output to. Defaults to input file
124+
- `-o`, `--output <file>` file to send output to. Defaults to input file
121125
name with extension changed to `.js`, or stdout if no input file is given.
122126
- `--plugin <module>` — makes Peggy use a specified plugin (can be specified
123127
multiple times)
124-
- `-t`, `--test <text>`Test the parser with the given text, outputting the
128+
- `-t`, `--test <text>`test the parser with the given text, outputting the
125129
result of running the parser against this input.
126130
If the input to be tested is not parsed, the CLI will exit with code 2
127-
- `-T`, `--test-file <filename>`Test the parser with the contents of the
131+
- `-T`, `--test-file <filename>`test the parser with the contents of the
128132
given file, outputting the result of running the parser against this input.
129133
If the input to be tested is not parsed, the CLI will exit with code 2
130134
- `--source-map` — generate a source map file with an optionally specified name
@@ -156,7 +160,7 @@ module.exports = {
156160

157161
You can test generated parser immediately if you specify the `-t/--test` or `-T/--test-file`
158162
option. This option conflicts with the option `-m/--source-map` unless `-o/--output` is
159-
also specified.
163+
also specified. This option conflicts with the `-a/--ast` option.
160164

161165
The CLI will exit with the code:
162166
- `0` if all was success
@@ -260,7 +264,8 @@ object to `peg.generate`. The following options are supported:
260264
object; if set to `"source"`, it will return parser source code as a string.
261265
If set to `"source-and-map"`, it will return a [`SourceNode`] object; you can
262266
get source code by calling `toString()` method or source code and mapping by
263-
calling `toStringWithSourceMap()` method, see the [`SourceNode`] documentation
267+
calling `toStringWithSourceMap()` method, see the [`SourceNode`] documentation.
268+
If set to `"ast"` then an internal grammar AST representation will be returned
264269
(default: `"parser"`)
265270

266271
> **Note**: because of bug [source-map/444] you should also set `grammarSource` to

bin/peggy.js

+23
Original file line numberDiff line numberDiff line change
@@ -3014,6 +3014,11 @@ const cliOptions = program
30143014
"-m, --source-map [mapfile]",
30153015
"Generate a source map. If name is not specified, the source map will be named \"<input_file>.map\" if input is a file and \"source.map\" if input is a standard input. This option conflicts with the `-t/--test` and `-T/--test-file` options unless `-o/--output` is also specified"
30163016
)
3017+
.option(
3018+
"-a, --ast",
3019+
"Output a grammar AST instead of a parser code",
3020+
false
3021+
)
30173022
.option(
30183023
"-t, --test <text>",
30193024
"Test the parser with the given text, outputting the result of running the parser instead of the parser itself. If the input to be tested is not parsed, the CLI will exit with code 2"
@@ -3057,6 +3062,7 @@ const PARSER_DEFAULTS = {
30573062

30583063
// Default values for CLI arguments
30593064
const PROG_DEFAULTS = {
3065+
ast: false,
30603066
input: undefined,
30613067
output: undefined,
30623068
sourceMap: undefined,
@@ -3148,6 +3154,16 @@ if (options.exportVar !== undefined) {
31483154
}
31493155
}
31503156

3157+
if (progOptions.ast && progOptions.sourceMap) {
3158+
abort("Can't use the -a/--ast option with the -m/--source-map option.");
3159+
}
3160+
if (progOptions.ast && progOptions.test) {
3161+
abort("Can't use the -a/--ast option with the -t/--test option.");
3162+
}
3163+
if (progOptions.ast && progOptions.testFile) {
3164+
abort("Can't use the -a/--ast option with the -T/--test-file option.");
3165+
}
3166+
31513167
verbose = progOptions.verbose;
31523168
let inputFile = progOptions.input;
31533169
if (verbose) {
@@ -3184,6 +3200,9 @@ if (progOptions.test && progOptions.testFile) {
31843200
abort("The -t/--test and -T/--test-file options are mutually exclusive.");
31853201
}
31863202

3203+
if (progOptions.ast) {
3204+
options.output = "ast";
3205+
} else
31873206
// If CLI parameter was defined, enable source map generation
31883207
if (progOptions.sourceMap !== undefined) {
31893208
options.output = "source-and-map";
@@ -3254,6 +3273,10 @@ readStream(inputStream, input => {
32543273
process.exit(1);
32553274
}
32563275

3276+
if (progOptions.ast) {
3277+
source = JSON.stringify(source, null, 2);
3278+
}
3279+
32573280
// If there is a valid outputFile, write the parser to it. Otherwise,
32583281
// if no test and no outputFile, write to stdout.
32593282
let outputStream = null;

bin/peggy.mjs

+23
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ const cliOptions = program
132132
"-m, --source-map [mapfile]",
133133
"Generate a source map. If name is not specified, the source map will be named \"<input_file>.map\" if input is a file and \"source.map\" if input is a standard input. This option conflicts with the `-t/--test` and `-T/--test-file` options unless `-o/--output` is also specified"
134134
)
135+
.option(
136+
"-a, --ast",
137+
"Output a grammar AST instead of a parser code",
138+
false
139+
)
135140
.option(
136141
"-t, --test <text>",
137142
"Test the parser with the given text, outputting the result of running the parser instead of the parser itself. If the input to be tested is not parsed, the CLI will exit with code 2"
@@ -175,6 +180,7 @@ const PARSER_DEFAULTS = {
175180

176181
// Default values for CLI arguments
177182
const PROG_DEFAULTS = {
183+
ast: false,
178184
input: undefined,
179185
output: undefined,
180186
sourceMap: undefined,
@@ -266,6 +272,16 @@ if (options.exportVar !== undefined) {
266272
}
267273
}
268274

275+
if (progOptions.ast && progOptions.sourceMap) {
276+
abort("Can't use the -a/--ast option with the -m/--source-map option.");
277+
}
278+
if (progOptions.ast && progOptions.test) {
279+
abort("Can't use the -a/--ast option with the -t/--test option.");
280+
}
281+
if (progOptions.ast && progOptions.testFile) {
282+
abort("Can't use the -a/--ast option with the -T/--test-file option.");
283+
}
284+
269285
verbose = progOptions.verbose;
270286
let inputFile = progOptions.input;
271287
if (verbose) {
@@ -302,6 +318,9 @@ if (progOptions.test && progOptions.testFile) {
302318
abort("The -t/--test and -T/--test-file options are mutually exclusive.");
303319
}
304320

321+
if (progOptions.ast) {
322+
options.output = "ast";
323+
} else
305324
// If CLI parameter was defined, enable source map generation
306325
if (progOptions.sourceMap !== undefined) {
307326
options.output = "source-and-map";
@@ -372,6 +391,10 @@ readStream(inputStream, input => {
372391
process.exit(1);
373392
}
374393

394+
if (progOptions.ast) {
395+
source = JSON.stringify(source, null, 2);
396+
}
397+
375398
// If there is a valid outputFile, write the parser to it. Otherwise,
376399
// if no test and no outputFile, write to stdout.
377400
let outputStream = null;

docs/documentation.html

+13-6
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ <h3 id="generating-a-parser-command-line">Command Line</h3>
150150
<dd>Comma-separated list of rules the parser will be allowed to start parsing
151151
from (default: only the first rule in the grammar).</dd>
152152

153+
<dt><code>-a</code>, <code>--ast</code></dt>
154+
<dd>Outputting an internal AST representation of the grammar after
155+
all optimizations instead of the parser source code. Useful for plugin authors
156+
to see how their plugin changes the AST. This option cannot be mixed with the
157+
<code>-t/--test</code> and <code>-T/--test-file</code> options.</dd>
158+
153159
<dt><code>--cache</code></dt>
154160
<dd>Makes the parser cache results, avoiding exponential parsing time in
155161
pathological cases but making the parser slower.</dd>
@@ -190,21 +196,21 @@ <h3 id="generating-a-parser-command-line">Command Line</h3>
190196
<dt><code>-t</code>, <code>--test &lt;text&gt;</code></dt>
191197
<dd>Test the parser with the given text, outputting the result of running
192198
the parser against this input.
193-
If the input to be tested is not parsed, the CLI will exit with code 2</dd>
199+
If the input to be tested is not parsed, the CLI will exit with code 2.</dd>
194200

195201
<dt><code>-T</code>, <code>--test-file &lt;text&gt;</code></dt>
196202
<dd>Test the parser with the contents of the given file, outputting the
197203
result of running the parser against this input.
198-
If the input to be tested is not parsed, the CLI will exit with code 2</dd>
204+
If the input to be tested is not parsed, the CLI will exit with code 2.</dd>
199205

200206
<dt><code>--trace</code></dt>
201207
<dd>Makes the parser trace its progress.</dd>
202208

203209
<dt><code>-v</code>, <code>--version</code></dt>
204-
<dd>Output the version number</dd>
210+
<dd>Output the version number.</dd>
205211

206212
<dt><code>-h</code>, <code>--help</code></dt>
207-
<dd>Display help for the command</dd>
213+
<dd>Display help for the command.</dd>
208214

209215
</dl>
210216

@@ -232,7 +238,7 @@ <h3 id="generating-a-parser-command-line">Command Line</h3>
232238
<p>
233239
You can test generated parser immediately if you specify the <code>-t/--test</code> or <code>-T/--test-file</code>
234240
option. This option conflicts with the option <code>-m/--source-map</code> unless <code>-o/--output</code> is
235-
also specified.
241+
also specified. This option conflicts with the <code>-a/--ast</code> option.
236242
</p>
237243

238244
<p>The CLI will exit with the code:</p>
@@ -358,7 +364,8 @@ <h3 id="generating-a-parser-javascript-api">JavaScript API</h3>
358364
a string (default: <code>"parser"</code>).
359365
If set to <code>"source-and-map"</code>, it will return a <a href="https://github.com/mozilla/source-map#sourcenode"><code>SourceNode</code></a> object; you can
360366
get source code by calling <code>toString()</code> method or source code and mapping by
361-
calling <code>toStringWithSourceMap()</code> method, see the <a href="https://github.com/mozilla/source-map#sourcenode"><code>SourceNode</code></a> documentation
367+
calling <code>toStringWithSourceMap()</code> method, see the <a href="https://github.com/mozilla/source-map#sourcenode"><code>SourceNode</code></a> documentation.
368+
If set to <code>"ast"</code> then an internal grammar AST representation will be returned
362369
(default: <code>"parser"</code>)</p>
363370
<blockquote>
364371
<p><strong>Note</strong>: because of bug <a href="https://github.com/mozilla/source-map/issues/444">source-map/444</a> you should also set <code>grammarSource</code> to

lib/compiler/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ const compiler = {
113113
case "source-and-map":
114114
return ast.code;
115115

116+
case "ast":
117+
return ast;
118+
116119
default:
117120
throw new Error("Invalid output format: " + options.output + ".");
118121
}

lib/peg.d.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,7 @@ export interface ParserBuildOptions extends BuildOptionsBase {
10821082
export type SourceOutputs = "source" | "source-and-map";
10831083

10841084
/** Base options for all source-generating formats. */
1085-
interface SourceOptionsBase<Output extends SourceOutputs>
1085+
interface SourceOptionsBase<Output>
10861086
extends BuildOptionsBase {
10871087
/**
10881088
* If set to `"parser"`, the method will return generated parser object;
@@ -1225,5 +1225,26 @@ export function generate(
12251225
options: SourceBuildOptions<SourceOutputs>
12261226
): string | SourceNode;
12271227

1228+
/**
1229+
* Returns the generated AST for the grammar. Unlike result of the
1230+
* `peggy.compiler.compile(...)` an AST returned by this method is augmented
1231+
* with data from passes. In other words, the compiler gives you the raw AST,
1232+
* and this method provides the final AST after all optimizations and
1233+
* transformations.
1234+
*
1235+
* @param grammar String in the format described by the meta-grammar in the
1236+
* `parser.pegjs` file
1237+
* @param options Options that allow you to customize returned AST
1238+
*
1239+
* @throws {SyntaxError} If the grammar contains a syntax error, for example,
1240+
* an unclosed brace
1241+
* @throws {GrammarError} If the grammar contains a semantic error, for example,
1242+
* duplicated labels
1243+
*/
1244+
export function generate(
1245+
grammar: string,
1246+
options: SourceOptionsBase<"ast">
1247+
): ast.Grammar;
1248+
12281249
// Export all exported stuff under a global variable PEG in non-module environments
12291250
export as namespace PEG;

test/api/pegjs-api.spec.js

+11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const sinon = require("sinon");
66
const pkg = require("../../package.json");
77
const { SourceMapConsumer } = require("source-map");
88

9+
chai.use(require("chai-like"));
10+
911
beforeEach(() => {
1012
// In the browser, initialize SourceMapConsumer's wasm bits.
1113
// This is *async*, so make sure to return the promise to make
@@ -169,6 +171,15 @@ describe("Peggy API", () => {
169171
expect(eval(source).parse("a")).to.equal("a");
170172
});
171173
});
174+
175+
describe("when |output| is set to |\"ast\"|", () => {
176+
it("returns generated parser AST", () => {
177+
const ast = peg.generate(grammar, { output: "ast" });
178+
179+
expect(ast).to.be.an("object");
180+
expect(ast).to.be.like(peg.parser.parse(grammar));
181+
});
182+
});
172183
});
173184

174185
// The |format|, |exportVars|, and |dependencies| options are not tested

test/cli/run.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ Options:
170170
This option conflicts with the \`-t/--test\`
171171
and \`-T/--test-file\` options unless
172172
\`-o/--output\` is also specified
173+
-a, --ast Output a grammar AST instead of a parser
174+
code (default: false)
173175
-t, --test <text> Test the parser with the given text,
174176
outputting the result of running the parser
175177
instead of the parser itself. If the input

test/types/peg.test-d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ describe("peg.d.ts", () => {
5757

5858
const p2 = peggy.generate(src, { output: "source", grammarSource: { foo: "src" } });
5959
expectType<string>(p2);
60+
61+
const p3 = peggy.generate(src, { output: "ast", grammarSource: { foo: "src" } });
62+
expectType<peggy.ast.Grammar>(p3);
6063
});
6164

6265
it("generates a source map", () => {

0 commit comments

Comments
 (0)