Updated to Null Safety

This commit is contained in:
thomashii 2021-03-31 14:33:31 +08:00
parent cb56b31d10
commit 01171d8954
24 changed files with 101 additions and 94 deletions

View file

@ -8,7 +8,7 @@ class _Advance<T> extends Parser<T> {
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!.change(parser: this); var result = parser._parse(args.increaseDepth()).change(parser: this);
if (result.successful) args.scanner.position += amount; if (result.successful) args.scanner.position += amount;
return result; return result;
} }

View file

@ -8,7 +8,7 @@ part of lex.src.combinator;
/// generate any error at all. /// generate any error at all.
Parser<T> any<T>(Iterable<Parser<T>> parsers, Parser<T> any<T>(Iterable<Parser<T>> parsers,
{bool backtrack: true, errorMessage, SyntaxErrorSeverity? severity}) { {bool backtrack: true, errorMessage, SyntaxErrorSeverity? severity}) {
return new _Any(parsers, backtrack != false, errorMessage, return _Any(parsers, backtrack != false, errorMessage,
severity ?? SyntaxErrorSeverity.error); severity ?? SyntaxErrorSeverity.error);
} }
@ -26,14 +26,14 @@ class _Any<T> extends Parser<T> {
.where((p) => !args.trampoline.isActive(p, args.scanner.position)); .where((p) => !args.trampoline.isActive(p, args.scanner.position));
if (inactive.isEmpty) { if (inactive.isEmpty) {
return new ParseResult(args.trampoline, args.scanner, this, false, []); return ParseResult(args.trampoline, args.scanner, this, false, []);
} }
var errors = <SyntaxError>[]; var errors = <SyntaxError>[];
int replay = args.scanner.position; int replay = args.scanner.position;
for (var parser in inactive) { for (var parser in inactive) {
var result = parser._parse(args.increaseDepth())!; var result = parser._parse(args.increaseDepth());
if (result.successful) if (result.successful)
return result; return result;
@ -45,7 +45,7 @@ class _Any<T> extends Parser<T> {
if (errorMessage != false) { if (errorMessage != false) {
errors.add( errors.add(
new SyntaxError( SyntaxError(
severity, severity,
errorMessage?.toString() ?? errorMessage?.toString() ??
'No match found for ${parsers.length} alternative(s)', 'No match found for ${parsers.length} alternative(s)',
@ -54,13 +54,13 @@ class _Any<T> extends Parser<T> {
); );
} }
return new ParseResult(args.trampoline, args.scanner, this, false, errors); return ParseResult(args.trampoline, args.scanner, this, false, errors);
} }
@override @override
ParseResult<T>? __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
// Never called // Never called
return null; throw ArgumentError("[Combinator] Invalid method call");
} }
@override @override

View file

@ -7,7 +7,7 @@ class _Cast<T, U extends T> extends Parser<U> {
@override @override
ParseResult<U> __parse(ParseArgs args) { ParseResult<U> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!; var result = parser._parse(args.increaseDepth());
return new ParseResult<U>( return new ParseResult<U>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
@ -38,7 +38,7 @@ class _CastDynamic<T> extends Parser<dynamic> {
@override @override
ParseResult<dynamic> __parse(ParseArgs args) { ParseResult<dynamic> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!; var result = parser._parse(args.increaseDepth());
return new ParseResult<dynamic>( return new ParseResult<dynamic>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,

View file

@ -18,7 +18,7 @@ class _Alt<T> extends Parser<T> {
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!; var result = parser._parse(args.increaseDepth());
return result.successful return result.successful
? result ? result
: result.addErrors([ : result.addErrors([
@ -48,7 +48,7 @@ class _Chain<T> extends ListParser<T> {
bool successful = true; bool successful = true;
for (var parser in parsers) { for (var parser in parsers) {
var result = parser._parse(args.increaseDepth())!; var result = parser._parse(args.increaseDepth());
if (!result.successful) { if (!result.successful) {
if (parser is _Alt) errors.addAll(result.errors); if (parser is _Alt) errors.addAll(result.errors);

View file

@ -11,7 +11,7 @@ class _Check<T> extends Parser<T> {
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
var matchState = {}; var matchState = {};
var result = parser._parse(args.increaseDepth())!.change(parser: this); var result = parser._parse(args.increaseDepth()).change(parser: this);
if (!result.successful) if (!result.successful)
return result; return result;
else if (!matcher.matches(result.value, matchState)) { else if (!matcher.matches(result.value, matchState)) {

View file

@ -67,9 +67,9 @@ class ParseArgs {
/// A parser combinator, which can parse very complicated grammars in a manageable manner. /// A parser combinator, which can parse very complicated grammars in a manageable manner.
abstract class Parser<T> { abstract class Parser<T> {
ParseResult<T>? __parse(ParseArgs args); ParseResult<T> __parse(ParseArgs args);
ParseResult<T>? _parse(ParseArgs args) { ParseResult<T> _parse(ParseArgs args) {
var pos = args.scanner.position; var pos = args.scanner.position;
if (args.trampoline.hasMemoized(this, pos)) if (args.trampoline.hasMemoized(this, pos))
@ -86,7 +86,7 @@ abstract class Parser<T> {
} }
/// Parses text from a [SpanScanner]. /// Parses text from a [SpanScanner].
ParseResult<T>? parse(SpanScanner scanner, [int depth = 1]) { ParseResult<T> parse(SpanScanner scanner, [int depth = 1]) {
var args = ParseArgs(Trampoline(), scanner, depth); var args = ParseArgs(Trampoline(), scanner, depth);
return _parse(args); return _parse(args);
} }
@ -105,7 +105,7 @@ abstract class Parser<T> {
// TODO: Type issue // TODO: Type issue
/// Runs the given function, which changes the returned [ParseResult] into one relating to a [U] object. /// Runs the given function, which changes the returned [ParseResult] into one relating to a [U] object.
Parser<U> change<U>(ParseResult<U> Function(ParseResult<T>?) f) { Parser<U> change<U>(ParseResult<U> Function(ParseResult<T>) f) {
return _Change<T, U>(this, f); return _Change<T, U>(this, f);
} }
@ -140,8 +140,10 @@ abstract class Parser<T> {
/// Ensures this pattern is not matched. /// Ensures this pattern is not matched.
/// ///
/// You can provide an [errorMessage]. /// You can provide an [errorMessage].
Parser<T> negate({String? errorMessage, SyntaxErrorSeverity? severity}) => Parser<T> negate(
_Negate<T>(this, errorMessage, severity ?? SyntaxErrorSeverity.error); {String errorMessage = 'Negate error',
SyntaxErrorSeverity severity = SyntaxErrorSeverity.error}) =>
_Negate<T>(this, errorMessage, severity);
/// Caches the results of parse attempts at various locations within the source text. /// Caches the results of parse attempts at various locations within the source text.
/// ///
@ -153,7 +155,7 @@ abstract class Parser<T> {
/// Consumes `this` and another parser, but only considers the result of `this` parser. /// Consumes `this` and another parser, but only considers the result of `this` parser.
Parser<T> and(Parser other) => then(other).change<T>((r) { Parser<T> and(Parser other) => then(other).change<T>((r) {
return ParseResult<T>( return ParseResult<T>(
r!.trampoline, r.trampoline,
r.scanner, r.scanner,
this, this,
r.successful, r.successful,
@ -176,7 +178,7 @@ abstract class Parser<T> {
/// The generated parser only runs once; repeated uses always exit eagerly. /// The generated parser only runs once; repeated uses always exit eagerly.
Parser<T> safe( Parser<T> safe(
{bool backtrack: true, {bool backtrack: true,
String? errorMessage, String errorMessage = "error",
SyntaxErrorSeverity? severity}) => SyntaxErrorSeverity? severity}) =>
_Safe<T>( _Safe<T>(
this, backtrack, errorMessage, severity ?? SyntaxErrorSeverity.error); this, backtrack, errorMessage, severity ?? SyntaxErrorSeverity.error);
@ -240,7 +242,7 @@ abstract class Parser<T> {
Parser<T> space() => trail(RegExp(r'[ \n\r\t]+')); Parser<T> space() => trail(RegExp(r'[ \n\r\t]+'));
/// Consumes 0 or more instance(s) of this parser. /// Consumes 0 or more instance(s) of this parser.
ListParser<T?> star({bool backtrack: true}) => ListParser<T> star({bool backtrack: true}) =>
times(1, exact: false, backtrack: backtrack).opt(); times(1, exact: false, backtrack: backtrack).opt();
/// Shortcut for [chain]-ing two parsers together. /// Shortcut for [chain]-ing two parsers together.
@ -259,10 +261,10 @@ abstract class Parser<T> {
/// an infinite amount of occurrences after the specified [count]. /// an infinite amount of occurrences after the specified [count].
/// ///
/// You can provide custom error messages for when there are [tooFew] or [tooMany] occurrences. /// You can provide custom error messages for when there are [tooFew] or [tooMany] occurrences.
ListParser<T?> times(int count, ListParser<T> times(int count,
{bool exact: true, {bool exact: true,
String? tooFew, String tooFew = 'Too few',
String? tooMany, String tooMany = 'Too many',
bool backtrack: true, bool backtrack: true,
SyntaxErrorSeverity? severity}) { SyntaxErrorSeverity? severity}) {
return _Repeat<T>(this, count, exact, tooFew, tooMany, backtrack, return _Repeat<T>(this, count, exact, tooFew, tooMany, backtrack,

View file

@ -8,7 +8,7 @@ class _Compare<T> extends ListParser<T> {
@override @override
ParseResult<List<T>> __parse(ParseArgs args) { ParseResult<List<T>> __parse(ParseArgs args) {
ParseResult<List<T>> result = parser._parse(args.increaseDepth())!; ParseResult<List<T>> result = parser._parse(args.increaseDepth());
if (!result.successful) return result; if (!result.successful) return result;
result = result.change( result = result.change(

View file

@ -8,7 +8,7 @@ class _FoldErrors<T> extends Parser<T> {
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!.change(parser: this); var result = parser._parse(args.increaseDepth()).change(parser: this);
var errors = result.errors.fold<List<SyntaxError>>([], (out, e) { var errors = result.errors.fold<List<SyntaxError>>([], (out, e) {
if (!out.any((b) => equal(e, b))) out.add(e); if (!out.any((b) => equal(e, b))) out.add(e);
return out; return out;

View file

@ -8,7 +8,7 @@ class _Index<T> extends Parser<T> {
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
ParseResult<List<T>> result = parser._parse(args.increaseDepth())!; ParseResult<List<T>> result = parser._parse(args.increaseDepth());
Object? value; Object? value;
if (result.successful) if (result.successful)

View file

@ -31,7 +31,7 @@ class _Longest<T> extends Parser<T> {
var results = <ParseResult<T>>[]; var results = <ParseResult<T>>[];
for (var parser in inactive) { for (var parser in inactive) {
var result = parser._parse(args.increaseDepth())!; var result = parser._parse(args.increaseDepth());
if (result.successful && result.span != null) if (result.successful && result.span != null)
results.add(result); results.add(result);
@ -66,7 +66,7 @@ class _Longest<T> extends Parser<T> {
var results = <ParseResult<T>>[]; var results = <ParseResult<T>>[];
for (var parser in parsers) { for (var parser in parsers) {
var result = parser._parse(args.increaseDepth())!; var result = parser._parse(args.increaseDepth());
if (result.successful) if (result.successful)
results.add(result); results.add(result);

View file

@ -8,8 +8,8 @@ class _Map<T, U> extends Parser<U> {
@override @override
ParseResult<U> __parse(ParseArgs args) { ParseResult<U> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!; var result = parser._parse(args.increaseDepth());
return new ParseResult<U>( return ParseResult<U>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,
@ -34,7 +34,7 @@ class _Map<T, U> extends Parser<U> {
class _Change<T, U> extends Parser<U> { class _Change<T, U> extends Parser<U> {
final Parser<T> parser; final Parser<T> parser;
final ParseResult<U> Function(ParseResult<T>?) f; final ParseResult<U> Function(ParseResult<T>) f;
_Change(this.parser, this.f); _Change(this.parser, this.f);

View file

@ -7,7 +7,7 @@ class _MaxDepth<T> extends Parser<T> {
_MaxDepth(this.parser, this.cap); _MaxDepth(this.parser, this.cap);
@override @override
ParseResult<T>? __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
if (args.depth > cap) { if (args.depth > cap) {
return new ParseResult<T>(args.trampoline, args.scanner, this, false, []); return new ParseResult<T>(args.trampoline, args.scanner, this, false, []);
} }

View file

@ -9,7 +9,7 @@ class _Negate<T> extends Parser<T> {
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!.change(parser: this); var result = parser._parse(args.increaseDepth()).change(parser: this);
if (!result.successful) { if (!result.successful) {
return new ParseResult<T>( return new ParseResult<T>(

View file

@ -9,7 +9,7 @@ class _Opt<T> extends Parser<T> {
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
var replay = args.scanner.position; var replay = args.scanner.position;
var result = parser._parse(args.increaseDepth())!; var result = parser._parse(args.increaseDepth());
if (!result.successful) args.scanner.position = replay; if (!result.successful) args.scanner.position = replay;
@ -37,7 +37,7 @@ class _ListOpt<T> extends ListParser<T> {
@override @override
ParseResult<List<T>> __parse(ParseArgs args) { ParseResult<List<T>> __parse(ParseArgs args) {
var replay = args.scanner.position; var replay = args.scanner.position;
ParseResult<List<T>> result = parser._parse(args.increaseDepth())!; ParseResult<List<T>> result = parser._parse(args.increaseDepth());
if (!result.successful) args.scanner.position = replay; if (!result.successful) args.scanner.position = replay;

View file

@ -8,7 +8,7 @@ class _Reduce<T> extends Parser<T> {
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
ParseResult<List<T>> result = parser._parse(args.increaseDepth())!; ParseResult<List<T>> result = parser._parse(args.increaseDepth());
if (!result.successful) if (!result.successful)
return new ParseResult<T>( return new ParseResult<T>(

View file

@ -16,22 +16,26 @@ class Reference<T> extends Parser<T> {
} }
@override @override
ParseResult<T>? __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
if (_parser == null) if (_parser == null)
throw new StateError('There is no parser assigned to this reference.'); throw new StateError('There is no parser assigned to this reference.');
return _parser!._parse(args); return _parser!._parse(args);
} }
@override @override
ParseResult<T>? _parse(ParseArgs args) { ParseResult<T> _parse(ParseArgs args) {
if (_parser == null)
throw new StateError('There is no parser assigned to this reference.');
return _parser!._parse(args); return _parser!._parse(args);
} }
@override @override
void stringify(CodeBuffer buffer) { void stringify(CodeBuffer buffer) {
if (_parser == null) if (_parser == null) {
buffer.writeln('(undefined reference <$T>)'); buffer.writeln('(undefined reference <$T>)');
else if (!printed) _parser!.stringify(buffer); } else if (!printed) {
_parser!.stringify(buffer);
}
printed = true; printed = true;
buffer.writeln('(previously printed reference)'); buffer.writeln('(previously printed reference)');
} }

View file

@ -1,63 +1,67 @@
part of lex.src.combinator; part of lex.src.combinator;
class _Repeat<T> extends ListParser<T?> { class _Repeat<T> extends ListParser<T> {
final Parser<T> parser; final Parser<T> parser;
final int count; final int count;
final bool exact, backtrack; final bool exact, backtrack;
final String? tooFew, tooMany; final String tooFew;
final String tooMany;
final SyntaxErrorSeverity severity; final SyntaxErrorSeverity severity;
_Repeat(this.parser, this.count, this.exact, this.tooFew, this.tooMany, _Repeat(this.parser, this.count, this.exact, this.tooFew, this.tooMany,
this.backtrack, this.severity); this.backtrack, this.severity);
@override @override
ParseResult<List<T?>> __parse(ParseArgs args) { ParseResult<List<T>> __parse(ParseArgs args) {
var errors = <SyntaxError>[]; var errors = <SyntaxError>[];
var results = <T?>[]; var results = <T>[];
var spans = <FileSpan?>[]; var spans = <FileSpan>[];
int success = 0, replay = args.scanner.position; int success = 0, replay = args.scanner.position;
ParseResult<T>? result; ParseResult<T>? result;
do { do {
result = parser._parse(args.increaseDepth()); result = parser._parse(args.increaseDepth());
if (result!.successful) { if (result.successful) {
success++; success++;
results.add(result.value); if (result.value != null) {
results.add(result.value!);
}
replay = args.scanner.position; replay = args.scanner.position;
} else if (backtrack) args.scanner.position = replay; } else if (backtrack) args.scanner.position = replay;
if (result.span != null) spans.add(result.span); if (result.span != null) {
spans.add(result.span!);
}
} while (result.successful); } while (result.successful);
if (success < count) { if (success < count) {
errors.addAll(result.errors); errors.addAll(result.errors);
errors.add( errors.add(
new SyntaxError( SyntaxError(
severity, severity,
tooFew ?? 'Expected at least $count occurence(s).', tooFew,
result.span ?? args.scanner.emptySpan, result.span ?? args.scanner.emptySpan,
), ),
); );
if (backtrack) args.scanner.position = replay; if (backtrack) args.scanner.position = replay;
return new ParseResult<List<T?>>( return ParseResult<List<T>>(
args.trampoline, args.scanner, this, false, errors); args.trampoline, args.scanner, this, false, errors);
} else if (success > count && exact) { } else if (success > count && exact) {
if (backtrack) args.scanner.position = replay; if (backtrack) args.scanner.position = replay;
return new ParseResult<List<T?>>( return ParseResult<List<T>>(args.trampoline, args.scanner, this, false, [
args.trampoline, args.scanner, this, false, [ SyntaxError(
new SyntaxError(
severity, severity,
tooMany ?? 'Expected no more than $count occurence(s).', tooMany,
result.span ?? args.scanner.emptySpan, result.span ?? args.scanner.emptySpan,
), ),
]); ]);
} }
var span = spans.reduce((a, b) => a!.expand(b!)); var span = spans.reduce((a, b) => a.expand(b));
return new ParseResult<List<T?>>( return ParseResult<List<T>>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,
@ -70,7 +74,7 @@ class _Repeat<T> extends ListParser<T?> {
@override @override
void stringify(CodeBuffer buffer) { void stringify(CodeBuffer buffer) {
var r = new StringBuffer('{$count'); var r = StringBuffer('{$count');
if (!exact) r.write(','); if (!exact) r.write(',');
r.write('}'); r.write('}');
buffer buffer

View file

@ -3,14 +3,14 @@ part of lex.src.combinator;
class _Safe<T> extends Parser<T> { class _Safe<T> extends Parser<T> {
final Parser<T> parser; final Parser<T> parser;
final bool backtrack; final bool backtrack;
final String? errorMessage; final String errorMessage;
final SyntaxErrorSeverity severity; final SyntaxErrorSeverity severity;
bool _triggered = false; bool _triggered = false;
_Safe(this.parser, this.backtrack, this.errorMessage, this.severity); _Safe(this.parser, this.backtrack, this.errorMessage, this.severity);
@override @override
ParseResult<T>? __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
var replay = args.scanner.position; var replay = args.scanner.position;
try { try {
@ -21,16 +21,13 @@ class _Safe<T> extends Parser<T> {
if (backtrack) args.scanner.position = replay; if (backtrack) args.scanner.position = replay;
var errors = <SyntaxError>[]; var errors = <SyntaxError>[];
if (errorMessage != null) { errors.add(
// TODO: Custom severity for all errors? new SyntaxError(
errors.add( severity,
new SyntaxError( errorMessage,
severity, args.scanner.lastSpan ?? args.scanner.emptySpan,
errorMessage, ),
args.scanner.lastSpan ?? args.scanner.emptySpan, );
),
);
}
return new ParseResult<T>( return new ParseResult<T>(
args.trampoline, args.scanner, this, false, errors); args.trampoline, args.scanner, this, false, errors);

View file

@ -7,7 +7,7 @@ class _ToList<T> extends ListParser<T?> {
@override @override
ParseResult<List<T?>> __parse(ParseArgs args) { ParseResult<List<T?>> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!; var result = parser._parse(args.increaseDepth());
if (result.value is List) { if (result.value is List) {
return (result as ParseResult<List<T?>>).change(parser: this); return (result as ParseResult<List<T?>>).change(parser: this);

View file

@ -8,7 +8,7 @@ class _Value<T> extends Parser<T> {
@override @override
ParseResult<T> __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!.change(parser: this); var result = parser._parse(args.increaseDepth()).change(parser: this);
return result.successful ? result.change(value: f(result)) : result; return result.successful ? result.change(value: f(result)) : result;
} }

View file

@ -12,11 +12,11 @@ main() {
test('sort', () { test('sort', () {
var parser = numbers.sort((a, b) => a!.compareTo(b!)); var parser = numbers.sort((a, b) => a!.compareTo(b!));
expect(parser.parse(scan('21,2,3,34,20'))!.value, [2, 3, 20, 21, 34]); expect(parser.parse(scan('21,2,3,34,20')).value, [2, 3, 20, 21, 34]);
}); });
test('reduce', () { test('reduce', () {
var parser = numbers.reduce((a, b) => a! + b!); var parser = numbers.reduce((a, b) => a! + b!);
expect(parser.parse(scan('21,2,3,34,20'))!.value, 80); expect(parser.parse(scan('21,2,3,34,20')).value, 80);
expect(parser.parse(scan('not numbers'))!.value, isNull); expect(parser.parse(scan('not numbers')).value, isNull);
}); });
} }

View file

@ -4,13 +4,13 @@ import 'common.dart';
main() { main() {
test('match string', () { test('match string', () {
expect(match('hello').parse(scan('hello world'))!.successful, isTrue); expect(match('hello').parse(scan('hello world')).successful, isTrue);
}); });
test('match start only', () { test('match start only', () {
expect(match('hello').parse(scan('goodbye hello'))!.successful, isFalse); expect(match('hello').parse(scan('goodbye hello')).successful, isFalse);
}); });
test('fail if no match', () { test('fail if no match', () {
expect(match('hello').parse(scan('world'))!.successful, isFalse); expect(match('hello').parse(scan('world')).successful, isFalse);
}); });
} }

View file

@ -14,29 +14,29 @@ main() {
}); });
test('change', () { test('change', () {
var parser = match('hello').change((r) => r!.change(value: 23)); var parser = match('hello').change((r) => r.change(value: 23));
expect(parser.parse(scan('helloworld'))!.value, 23); expect(parser.parse(scan('helloworld')).value, 23);
}); });
test('check', () { test('check', () {
var parser = match<int>(new RegExp(r'[A-Za-z]+')) var parser = match<int>(new RegExp(r'[A-Za-z]+'))
.value((r) => r.span!.length) .value((r) => r.span!.length)
.check(greaterThan(3)); .check(greaterThan(3));
expect(parser.parse(scan('helloworld'))!.successful, isTrue); expect(parser.parse(scan('helloworld')).successful, isTrue);
expect(parser.parse(scan('yo'))!.successful, isFalse); expect(parser.parse(scan('yo')).successful, isFalse);
}); });
test('map', () { test('map', () {
var parser = var parser =
match(new RegExp(r'[A-Za-z]+')).map<int>((r) => r.span!.length); match(new RegExp(r'[A-Za-z]+')).map<int>((r) => r.span!.length);
expect(parser.parse(scan('hello'))!.value, 5); expect(parser.parse(scan('hello')).value, 5);
}); });
test('negate', () { test('negate', () {
var parser = match('hello').negate(errorMessage: 'world'); var parser = match('hello').negate(errorMessage: 'world');
expect(parser.parse(scan('goodbye world'))!.successful, isTrue); expect(parser.parse(scan('goodbye world')).successful, isTrue);
expect(parser.parse(scan('hello world'))!.successful, isFalse); expect(parser.parse(scan('hello world')).successful, isFalse);
expect(parser.parse(scan('hello world'))!.errors.first.message, 'world'); expect(parser.parse(scan('hello world')).errors.first.message, 'world');
}); });
group('opt', () { group('opt', () {
@ -44,13 +44,13 @@ main() {
var list = match('hel').then(match('lo')).opt(); var list = match('hel').then(match('lo')).opt();
test('succeeds if present', () { test('succeeds if present', () {
expect(single.parse(scan('hello'))!.successful, isTrue); expect(single.parse(scan('hello')).successful, isTrue);
expect(list.parse(scan('hello'))!.successful, isTrue); expect(list.parse(scan('hello')).successful, isTrue);
}); });
test('succeeds if not present', () { test('succeeds if not present', () {
expect(single.parse(scan('goodbye'))!.successful, isTrue); expect(single.parse(scan('goodbye')).successful, isTrue);
expect(list.parse(scan('goodbye'))!.successful, isTrue); expect(list.parse(scan('goodbye')).successful, isTrue);
}); });
test('backtracks if not present', () { test('backtracks if not present', () {

View file

@ -6,10 +6,10 @@ main() {
var parser = match('hello').value((r) => 'world'); var parser = match('hello').value((r) => 'world');
test('sets value', () { test('sets value', () {
expect(parser.parse(scan('hello world'))!.value, 'world'); expect(parser.parse(scan('hello world')).value, 'world');
}); });
test('no value if no match', () { test('no value if no match', () {
expect(parser.parse(scan('goodbye world'))!.value, isNull); expect(parser.parse(scan('goodbye world')).value, isNull);
}); });
} }