Permalink
Browse files

Throw an error when parse() fails, rather than returning null.

  • Loading branch information...
1 parent 8d56dc5 commit 95364ba971ad7857e26e84b779e0edd83b662a1c @jcoglan committed May 1, 2012
View
@@ -38,11 +38,11 @@ Canopy.extend(Canopy, {
},
formatError: function(error) {
- var lines = error.input.split(/\n/g),
- lineNo = 0,
- offset = 0;
+ var lines = error.input.split(/\n/g),
+ lineNo = 0,
+ offset = 0;
- while (offset < error.offset) {
+ while (offset < error.offset + 1) {
offset += lines[lineNo].length + 1;
lineNo += 1;
}
View
@@ -102,11 +102,11 @@ Canopy.extend(Canopy.Builder.prototype, {
this.line_(address + ' = null');
var input = this.input_(), of = this.offset_(), slice = this.slice_(1);
var error = 'this.error = this.constructor.lastError';
+ expected = expected.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
this.if_('!this.error || this.error.offset <= ' + of, function(builder) {
builder.line_(error + ' = {input: ' + input +
', offset: ' + of +
- ', expected: "' + (expected || '').replace(/"/g, '\\"') +
- '", actual: ' + slice + ' || "<EOF>"}');
+ ', expected: "' + expected + '"}');
});
},
@@ -14,7 +14,7 @@ Canopy.Compiler.CIString = {
});
builder.else_(function(builder) {
builder.failure_(address, this.textValue);
- });
+ }, this);
},
stringValue: function() {
@@ -38,8 +38,20 @@ Canopy.Compiler.Grammar = {
builder.ivar_('nodeCache', '{}');
});
builder.function_('Parser.prototype.parse', [], function(builder) {
+ var input = builder.input_(),
+ of = builder.offset_();
+
builder.var_('result', 'this.__consume__' + root + '()');
- builder.return_(builder.offset_() + ' === ' + builder.input_() + '.length ? result : null');
+
+ builder.if_('result && ' + of + ' === ' + input + '.length', function(builder) {
+ builder.return_('result');
+ });
+ builder.unless_('this.error', function(builder) {
+ builder.line_('this.error = {input: ' + input + ', offset :' + of + ', expected: "<EOF>"}');
+ });
+ builder.var_('message', 'formatError(this.error)');
+ builder.var_('error', 'new Error(message)');
+ builder.line_('throw error');
});
builder.function_('Parser.parse', ['input'], function(builder) {
builder.var_('parser', 'new Parser(input)');
@@ -12,7 +12,7 @@ Canopy.Compiler.String = {
});
builder.else_(function(builder) {
builder.failure_(address, this.textValue);
- });
+ }, this);
}
};
Oops, something went wrong.
@@ -12,12 +12,11 @@ function() { with(this) {
}})
it('does not parse the empty string', function() { with(this) {
- assertNull( AnyCharTestParser.parse('') )
+ assertThrows(Error, function() { AnyCharTestParser.parse('') })
assertEqual({
input: '',
offset: 0,
- expected: '<any char>',
- actual: '<EOF>'
+ expected: '<any char>'
}, AnyCharTestParser.lastError)
}})
}})
@@ -13,22 +13,20 @@ function() { with(this) {
}})
it('does not parse characters outside the class', function() { with(this) {
- assertNull( PositiveCharClassTestParser.parse('7') )
+ assertThrows(Error, function() { PositiveCharClassTestParser.parse('7') })
assertEqual({
input: '7',
offset: 0,
- expected: '[a-z]',
- actual: '7'
+ expected: '[a-z]'
}, PositiveCharClassTestParser.lastError)
}})
it('does not parse characters within the class appearing too late', function() { with(this) {
- assertNull( PositiveCharClassTestParser.parse('7a') )
+ assertThrows(Error, function() { PositiveCharClassTestParser.parse('7a') })
assertEqual({
input: '7a',
offset: 0,
- expected: '[a-z]',
- actual: '7'
+ expected: '[a-z]'
}, PositiveCharClassTestParser.lastError)
}})
}})
@@ -44,11 +42,11 @@ function() { with(this) {
}})
it('does not parse characters outside the class', function() { with(this) {
- assertNull( NegativeCharClassTestParser.parse('a') )
+ assertThrows(Error, function() { NegativeCharClassTestParser.parse('a') })
}})
it('does not parse characters within the class appearing too late', function() { with(this) {
- assertNull( NegativeCharClassTestParser.parse('a7') )
+ assertThrows(Error, function() { NegativeCharClassTestParser.parse('a7') })
}})
}})
@@ -70,17 +68,16 @@ function() { with(this) {
}})
it('does not parse floats', function() { with(this) {
- assertNull( RepeatCharClassTestParser.parse('7.4') )
+ assertThrows(Error, function() { RepeatCharClassTestParser.parse('7.4') })
assertEqual({
input: '7.4',
offset: 1,
- expected: '[0-9]',
- actual: '.'
+ expected: '[0-9]'
}, RepeatCharClassTestParser.lastError)
}})
it('does not parse octal', function() { with(this) {
- assertNull( RepeatCharClassTestParser.parse('0644') )
+ assertThrows(Error, function() { RepeatCharClassTestParser.parse('0644') })
}})
}})
}})
@@ -20,13 +20,13 @@ function() { with(this) {
}})
it('does not parse two choices together', function() { with(this) {
- assertNull( ChoiceTestParser.parse('foobar') )
+ assertThrows(Error, function() { ChoiceTestParser.parse('foobar') })
}})
it('does not parse a superstring of any choice', function() { with(this) {
- assertNull( ChoiceTestParser.parse('foob') )
- assertNull( ChoiceTestParser.parse('barb') )
- assertNull( ChoiceTestParser.parse('bazb') )
+ assertThrows(Error, function() { ChoiceTestParser.parse('foob') })
+ assertThrows(Error, function() { ChoiceTestParser.parse('barb') })
+ assertThrows(Error, function() { ChoiceTestParser.parse('bazb') })
}})
describe('when the choices are ambiguous', function() { with(this) {
@@ -23,7 +23,7 @@ function() { with(this) {
}})
it('does not parse text that does not begin with the expected pattern', function() { with(this) {
- assertNull( AndTestParser.parse('foobar') )
+ assertThrows(Error, function() { AndTestParser.parse('foobar') })
}})
}})
@@ -42,7 +42,7 @@ function() { with(this) {
}})
it('does not parse text beginning with the negated pattern', function() { with(this) {
- assertNull( NotTestParser.parse('foobar') )
+ assertThrows(Error, function() { NotTestParser.parse('foobar') })
}})
describe('combined with repetition', function() { with(this) {
@@ -69,11 +69,11 @@ function() { with(this) {
}})
it('does not match a word with no space', function() { with(this) {
- assertNull( RepeatNotTestParser.parse('chunky') )
+ assertThrows(Error, function() { RepeatNotTestParser.parse('chunky') })
}})
it('does not match multiple words', function() { with(this) {
- assertNull( RepeatNotTestParser.parse('chunky bacon ') )
+ assertThrows(Error, function() { RepeatNotTestParser.parse('chunky bacon ') })
}})
}})
}})
@@ -15,7 +15,7 @@ function() { with(this) {
}})
it('does not parse strings that do not match the referenced rule', function() { with(this) {
- assertNull( OneRefTestParser.parse('foo') )
+ assertThrows(Error, function() { OneRefTestParser.parse('foo') })
}})
}})
@@ -17,7 +17,7 @@ function() { with(this) {
}})
it('does not parse if different input is given', function() { with(this) {
- assertNull( MaybeTestParser.parse('gc') )
+ assertThrows(Error, function() { MaybeTestParser.parse('gc') })
}})
}})
@@ -48,7 +48,7 @@ function() { with(this) {
}})
it('does not match superstrings of the repeated pattern', function() { with(this) {
- assertNull( ZeroOrMoreTestParser.parse('foofood') )
+ assertThrows(Error, function() { ZeroOrMoreTestParser.parse('foofood') })
}})
describe('followed by more of the repeated pattern', function() { with(this) {
@@ -58,9 +58,9 @@ function() { with(this) {
}})
it('does not parse any number of occurences', function() { with(this) {
- assertNull( ZeroOrUnparsableParser.parse('') )
- assertNull( ZeroOrUnparsableParser.parse('foo') )
- assertNull( ZeroOrUnparsableParser.parse('foofoo') )
+ assertThrows(Error, function() { ZeroOrUnparsableParser.parse('') })
+ assertThrows(Error, function() { ZeroOrUnparsableParser.parse('foo') })
+ assertThrows(Error, function() { ZeroOrUnparsableParser.parse('foofoo') })
}})
}})
}})
@@ -72,7 +72,7 @@ function() { with(this) {
}})
it('does not match zero occurences of the pattern', function() { with(this) {
- assertNull( OneOrMoreTestParser.parse('') )
+ assertThrows(Error, function() { OneOrMoreTestParser.parse('') })
}})
it('matches one occurence of the pattern', function() { with(this) {
@@ -92,7 +92,7 @@ function() { with(this) {
}})
it('does not match superstrings of the repeated pattern', function() { with(this) {
- assertNull( OneOrMoreTestParser.parse('foofood') )
+ assertThrows(Error, function() { OneOrMoreTestParser.parse('foofood') })
}})
describe('followed by more of the repeated pattern', function() { with(this) {
@@ -102,9 +102,9 @@ function() { with(this) {
}})
it('does not parse any number of occurences', function() { with(this) {
- assertNull( OneOrUnparsableParser.parse('') )
- assertNull( OneOrUnparsableParser.parse('foo') )
- assertNull( OneOrUnparsableParser.parse('foofoo') )
+ assertThrows(Error, function() { OneOrUnparsableParser.parse('') })
+ assertThrows(Error, function() { OneOrUnparsableParser.parse('foo') })
+ assertThrows(Error, function() { OneOrUnparsableParser.parse('foofoo') })
}})
}})
}})
@@ -16,16 +16,16 @@ function() { with(this) {
}})
it('does not parse nonmatching sequences', function() { with(this) {
- assertNull( SequenceTestParser.parse('foobaz') )
- assertNull( SequenceTestParser.parse('doobar') )
+ assertThrows(Error, function() { SequenceTestParser.parse('foobaz') })
+ assertThrows(Error, function() { SequenceTestParser.parse('doobar') })
}})
it('does not parse if the first term is missing', function() { with(this) {
- assertNull( SequenceTestParser.parse('bar') )
+ assertThrows(Error, function() { SequenceTestParser.parse('bar') })
}})
it('does not parse superstrings of itself', function() { with(this) {
- assertNull( SequenceTestParser.parse('foobart') )
+ assertThrows(Error, function() { SequenceTestParser.parse('foobart') })
}})
describe('labelling', function() { with(this) {
@@ -69,7 +69,7 @@ function() { with(this) {
}})
it('does not parse if the expression it labels does not parse', function() { with(this) {
- assertNull( LabelTestBParser.parse('firstthird') )
+ assertThrows(Error, function() { LabelTestBParser.parse('firstthird') })
}})
}})
@@ -12,16 +12,16 @@ function() { with(this) {
}})
it('does not parse other strings', function() { with(this) {
- assertNull( StringTestParser.parse('FOO') )
- assertNull( StringTestParser.parse('bar') )
+ assertThrows(Error, function() { StringTestParser.parse('FOO') })
+ assertThrows(Error, function() { StringTestParser.parse('bar') })
}})
it('does not parse superstrings of itself', function() { with(this) {
- assertNull( StringTestParser.parse('food') )
+ assertThrows(Error, function() { StringTestParser.parse('food') })
}})
it('does not parse the empty string', function() { with(this) {
- assertNull( StringTestParser.parse('') )
+ assertThrows(Error, function() { StringTestParser.parse('') })
}})
describe('case-insensitive strings', function() { with(this) {

0 comments on commit 95364ba

Please sign in to comment.