-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathpeg-play.mjs
executable file
·241 lines (172 loc) · 6.98 KB
/
peg-play.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
#!/usr/bin/env node
const doco = `
# Peg Play
This is a little command line tool to play and test pPEG grammars.
It reads a text (.txt) file starting with a pPEG grammar, followed by
one or more input text tests, each separated by a line starting
with four or more dashes ---- (see: format below)
If successful the parse tree is printed, if not errors are reported.
This makes it easy to play with a pPEG grammar.
To give it a try run it with node.js:
> node peg-play.mjs play/date.txt
Or to print the parse tree in json format:
> node peg-play.mjs -j play/date.txt
This assumes peg-play.mjs is in the same directory as pPEG.mjs,
if not then read how to install as a command below.
Multiple grammars with tests can be combined into a single file.
Also used for regression testing of multiple files in a tests directory.
> node peg-play.mjs tests
## Test format
The test file stars with a pPEG grammar,
followed by a separator line:<br>
-----------------------<br>
with at least 4 ---- dashes.
Multiple input tests are separated in the same way.
The separator before an input test that should fail can be negated:<br>
------------------- not
Multiple grammars with their tests can be separated with:<br>
========================<br>
a line with at least 4 ==== chars.
WHen reading a grammar any intial comment lines (starting with #)
will be stripped off, and if there are no grammar rules then this
grammar block is skipped over as comments in the test file.
## To install as a command
1. Edit this peg-play.mjs file to import your local copy of pPEG.mjs
import peg from './pPEG.mjs' // <== EDIT.ME to relocate
2. This command line tool can be used with node:
> node peg-play.mjs my-test.txt
But this requires the peg-play.mjs file to be in the same directory as
the my-test.txt file(s), or the use of absolute path name(s).
3. Optional: to make peg-play.mjs into a command that can be used directly.
Copy this file into: <your-command-path>/peg-play.mjs
For example: /usr/local/bin/peg-play.mjs (or similar on your $PATH)
> chmod +x /usr/local/bin/peg-play.mjs
Usage:
> peg-play.mjs my-test.txt
`; // doco
import peg from './pPEG.mjs' // <== EDIT.ME to relocate
import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs'
// check command line args ----------------------------
let json = false // -j json, default pretty print ptree
let bad_opt = false; // if -x undefined
let path_arg = 2 // argv first cmd arg
const args = process.argv.length - 2
if (args > 0) { // check for option...
const arg1 = process.argv[2]
if (arg1.startsWith('-')) {
path_arg += 1;
if (arg1.startsWith('-h')) { // -help doco ....
console.log(doco);
process.exit(1);
}
if (arg1.startsWith('-j')) json = true;
else bad_opt = true;
}
}
if (args < 1 || bad_opt) {
console.log("Useage: -option? path-name (file or directory)\n"+
" option:\n"+
" -j, -json for json format ptree\n"+
" -h, -help for more info.\n");
process.exit(1);
}
// OK run tests ---------------------------------------
for (let path = process.argv[path_arg];
path_arg < process.argv.length;
path_arg += 1) {
if (!existsSync(path)) {
console.log("**** Can't find: '"+path+"' in "+process.cwd())
continue; }
const ptype = lstatSync(path)
if (ptype.isDirectory()) {
const files = readdirSync(path, 'utf8');
for (const file of files) {
test_file(path+'/'+file, json, true); // silent
}
} else if (ptype.isFile()) {
test_file(path, json);
}
} // all args done..
// read and compile the grammar -----------------------------------------
function test_file(file, json, silent) {
if (!file.endsWith('.txt')) {
say("**** Skip '"+file+"' this is not a .txt file...");
return;
};
let f1 = readFileSync(file, 'utf8');
if (f1.startsWith("====")) f1 = '\n'+f1; // skips empty grammar
const grammars = f1.split(/[\n\r]*====+[ \t]*([^ \t\n\r]*)[^\n\r]*\r?\n/);
let peg_ok = 0, peg_err = 0; // pPEG grammars
let test_ok = 0, test_err = 0; // input tests
for (let i=0; i<grammars.length; i+=2) {
const tests = grammars[i].split(/\r?\n----+[ \t]*([^ \t\n\r]*)[^\n\r]*\r?\n/);
const px = tests[0]; // pPEG grammar source
const ps = strip_leading_comments(px); // # lines prior to rules
if (ps === '') continue; // skip grammar that is all comment lines
let peg_not = false
if (grammars[i-1] === "not") {
peg_not = true
say("==================================================== not") }
else {
say("========================================================") }
say(px) // pPEG grammar text
const pp = peg.compile(ps)
if (!pp.ok) { // bad grammar
say(pp.show_err())
say("********************* grammar failed, skip tests....")
peg_err += peg_not? 0 : 1 // don't count if expected to fail
continue
}
if (peg_not) { // was expected to fail, but didn't
say("********************* grammar was expected to fail ...")
peg_err += 1
}
if (tests[1] === "not") {
say("---------------------------------------------------- not") }
else {
say("--------------------------------------------------------") }
// parse the input tests -------------------------------------------
let ok = 0, err = 0;
for (let i=2; i<tests.length; i+=2) {
const neg = tests[i-1] === 'not';
const s = tests[i];
say(s);
if (neg) {
say(">>>> not");
} else {
say(">>>>");
}
const tp = pp.parse(s);
if (tp.ok) {
say(tp.show_ptree(json));
} else { // parse failed ...
say(tp.show_err());
}
if (tp.ok && !neg || !tp.ok && neg) {
ok += 1;
say("----------------------------- ok "+ok);
} else {
err += 1;
say("***************************** err "+err+" ********");
}
}
test_ok += ok;
test_err += err;
peg_ok += 1;
} // grammars
if (peg_err === 0 && test_err === 0) {
console.log("OK "+file+": all "+test_ok+" test(s), "+peg_ok+" grammar(s) .....");
} else {
console.log("**** Error "+file+": Failed "+test_err+" test(s), passed ok "+
test_ok+" test(s), failed "+peg_err+" grammar(s)");
}
function say(msg) {
if (!silent) console.log(msg);
}
function strip_leading_comments(str) {
if (str === "") return str;
let rx = str.match(/^((?:[ \t\n\r]*#[^\n\r]*[\n\r]*)*)[ \t\n\r]*(.*)/s);
return rx[2];
}
} // test_file
process.exit(0);