Skip to content

Commit b6aad02

Browse files
committed
tests for detailed text parse errors and much more correct errors
1 parent 62e0f57 commit b6aad02

10 files changed

+79
-14
lines changed

Diff for: src/rd_parser.js

+22-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ function get_last_error(state) {
66
var last_exp_position = state.lastExpectations.reduce(function(a, b){
77
return Math.max(a, b.position);
88
}, state.position);
9+
var dedupedExpectations = state.lastExpectations.filter(function(expectation) {
10+
return expectation.position === last_exp_position;
11+
}).filter(function(expectation, index, array) {
12+
for (var i = 0; i < array.length; i++) {
13+
if (array[i].rule === expectation.rule && i !== index) {
14+
return false;
15+
}
16+
}
17+
return true;
18+
});
919

1020
var last_position = 0;
1121
var line_of_error = '';
@@ -33,7 +43,7 @@ function get_last_error(state) {
3343
unexpected_char = state.text[last_exp_position];
3444
}
3545
var unexpected = 'Unexpected "' + unexpected_char + '"';
36-
var expected_rules = state.lastExpectations.map(function(exp){ return exp.rule });
46+
var expected_rules = dedupedExpectations.map(function(exp){ return exp.rule });
3747
var expected = ' expected (' + expected_rules.join(' or ') + ')';
3848
var pointer = (new Array(position_of_error + 3 + error_ln_length)).join('-') + '^';
3949
var extra = line_of_error + '\n' + pointer;
@@ -88,16 +98,20 @@ function regex_char(rule) {
8898
function sequence(parsers) {
8999
return function(state) {
90100
var asts = [];
101+
var expectations = [];
91102
var start_position = state.position;
92103
for (var i = 0; i < parsers.length; i++) {
93104
var ast = parsers[i](state);
105+
expectations = expectations.concat(state.lastExpectations);
94106
if (ast) {
95107
asts.push(ast);
96108
} else {
109+
state.lastExpectations = expectations;
97110
return false;
98111
}
99112
}
100113
var match = asts.reduce(function(r, n){ return r + (n.match || '') }, '');
114+
state.lastExpectations = expectations;
101115
return {
102116
type: 'sequence',
103117
match: match,
@@ -170,7 +184,7 @@ function one_or_more(parser) {
170184
var state_position = state.position;
171185
ast = parser(state);
172186
if (ast) {
173-
asts.push(ast);
187+
asts.push(ast);
174188
} else {
175189
state.position = state_position;
176190
}
@@ -197,6 +211,8 @@ function optional(parser) {
197211
var asts = [];
198212
if (ast) {
199213
asts.push(ast);
214+
} else {
215+
state.position = start_position;
200216
}
201217
var match = asts.reduce(function(r, n){ return r + (n.match || '') }, '');
202218
return {
@@ -247,7 +263,7 @@ function not_predicate(parser) {
247263
children: [ast],
248264
position: state.position
249265
}];
250-
return false;
266+
return false;
251267
} else {
252268
state.lastExpectations = [];
253269
return {
@@ -264,6 +280,7 @@ function not_predicate(parser) {
264280
function end_of_file() {
265281
return function(state) {
266282
if (state.text.length === state.position) {
283+
state.lastExpectations = [];
267284
return {
268285
type: 'end_of_file',
269286
match: null,
@@ -272,11 +289,11 @@ function end_of_file() {
272289
end_position: state.position
273290
}
274291
} else {
275-
state.lastExpectations.push({
292+
state.lastExpectations = [{
276293
type: 'end_of_file',
277294
rule: 'EOF',
278295
position: state.position
279-
});
296+
}];
280297
return false;
281298
}
282299
}

Diff for: src/speg_parser.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function parsing_sub_expression() {
102102
function tag() {
103103
return rd.action('noop', rd.sequence([
104104
rd.regex_char('[a-zA-Z_]'),
105-
rd.zero_or_more(rd.regex_char('[a-zA-Z_0-9]'))
105+
rd.zero_or_more(rd.regex_char('[a-zA-Z0-9_]'))
106106
]));
107107
}
108108

Diff for: test/exceptions.spec.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe('exceptions - ', function() {
3838
} catch (e) {
3939
expect(e.message).to.equal(
4040
'Failed to parse grammar: ' +
41-
'\n\nUnexpected "EOF" expected (; or [\\s])\n' +
41+
'\n\nUnexpected "EOF" expected (: or [\\s] or ;)\n' +
4242
'1: GRAMMAR t a->b\n' +
4343
'-----------------^'
4444
);
@@ -54,7 +54,7 @@ describe('exceptions - ', function() {
5454
} catch (e) {
5555
expect(e.message).to.equal(
5656
'Failed to parse grammar: ' +
57-
'\n\nUnexpected "EOF" expected (; or [\\s])\n' +
57+
'\n\nUnexpected "EOF" expected (;)\n' +
5858
'1: GRAMMAR t a->b c\n' +
5959
'-------------------^'
6060
);
@@ -70,7 +70,7 @@ describe('exceptions - ', function() {
7070
} catch (e) {
7171
expect(e.message).to.equal(
7272
'Failed to parse grammar: ' +
73-
'\n\nUnexpected "EOF" expected (; or [\\s])\n' +
73+
'\n\nUnexpected "EOF" expected (/ or ;)\n' +
7474
'1: GRAMMAR t a->b/c\n' +
7575
'-------------------^'
7676
);
@@ -86,7 +86,7 @@ describe('exceptions - ', function() {
8686
} catch (e) {
8787
expect(e.message).to.equal(
8888
'Failed to parse grammar: ' +
89-
'\n\nUnexpected "EOF" expected (; or [\\s] or EOF)\n' +
89+
'\n\nUnexpected "EOF" expected (: or [\\s] or ;)\n' +
9090
'1: GRAMMAR t a->b;b->c\n' +
9191
'----------------------^'
9292
);
@@ -102,7 +102,7 @@ describe('exceptions - ', function() {
102102
} catch (e) {
103103
expect(e.message).to.equal(
104104
'Failed to parse grammar: ' +
105-
'\n\nUnexpected "EOF" expected (; or [\\s] or EOF)\n' +
105+
'\n\nUnexpected "EOF" expected (: or [\\s] or ;)\n' +
106106
'1: GRAMMAR t a->b;b->c;c->d\n' +
107107
'---------------------------^'
108108
);

Diff for: test/speg.invalid.fixtures.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
var SPEG = require('./../src/speg').SPEG;
2+
var fs = require('fs');
3+
var expect = require('chai').expect;
4+
var path = require('path');
5+
var recursiveReadSync = require('recursive-readdir-sync');
6+
var valid_files = recursiveReadSync('./test/speg_invalid_fixtures');
7+
8+
describe('speg - invalid fixtures - ', function() {
9+
valid_files = valid_files.filter(function(filename) {
10+
return /\.peg$/.test(filename);
11+
})
12+
for (var i = 0, len = valid_files.length; i < len; i++) {
13+
it('should parse - ' + valid_files[i], (function (filename) {
14+
return function () {
15+
var name = path.basename(filename);
16+
var grammar = fs.readFileSync(filename, "utf8");
17+
var text = fs.readFileSync(path.join('./test/speg_invalid_fixtures/', name + '.txt'), "utf8");
18+
var result = fs.readFileSync(path.join('./test/speg_invalid_fixtures/', name + '.error'), "utf8");
19+
var speg = new SPEG();
20+
expect(function() {
21+
var ast = speg.parse(grammar, text);
22+
}).to.throw(Error);
23+
try {
24+
var ast = speg.parse(grammar, text);
25+
} catch (e) {
26+
e.message.should.equal(result);
27+
}
28+
}
29+
}(valid_files[i])));
30+
}
31+
});

Diff for: test/speg_invalid_fixtures/optional-negation.peg

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
GRAMMAR optional_genation
22

33
exp -> negation? term EOF;
4-
negation -> "NOT" _;
5-
term -> "test";
6-
_ -> [ ]+;
4+
negation -> "NOT" " ";
5+
term -> "1";
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Failed to parse text:
2+
3+
Unexpected "1" expected ( )
4+
1: NOT1
5+
------^

Diff for: test/speg_invalid_fixtures/optional-negation.peg.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
NOT1

Diff for: test/speg_invalid_fixtures/sequence.peg

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
GRAMMAR sequence
2+
3+
Test -> Expression EOF;
4+
Expression -> Term ("+" Term)*;
5+
Term -> Group / "1";
6+
Group -> "(" Expression ")";

Diff for: test/speg_invalid_fixtures/sequence.peg.error

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Failed to parse text:
2+
3+
Unexpected "#" expected (( or 1)
4+
1: (1+#
5+
------^

Diff for: test/speg_invalid_fixtures/sequence.peg.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(1+#

0 commit comments

Comments
 (0)