Updated to Nullsafety

This commit is contained in:
thomashii 2021-03-18 08:00:56 +08:00
parent 55beadd737
commit d62a4aa7ac
37 changed files with 231 additions and 229 deletions

View file

@ -10,7 +10,7 @@ import 'package:string_scanner/string_scanner.dart';
/// Namely, the `username` or `password` in `{username}:{password}`.
final Parser<String> string =
match<String>(new RegExp(r'[^:$]+'), errorMessage: 'Expected a string.')
.value((r) => r.span.text);
.value((r) => r.span!.text);
/// Transforms `{username}:{password}` to `{"username": username, "password": password}`.
final Parser<Map<String, String>> credentials = chain<String>([
@ -18,19 +18,19 @@ final Parser<Map<String, String>> credentials = chain<String>([
match<String>(':'),
string.opt(),
]).map<Map<String, String>>(
(r) => {'username': r.value[0], 'password': r.value[2]});
(r) => {'username': r.value![0], 'password': r.value![2]});
/// We can actually embed a parser within another parser.
///
/// This is used here to BASE64URL-decode a string, and then
/// parse the decoded string.
final Parser credentialString = match<Map<String, String>>(
final Parser credentialString = match<Map<String, String>?>(
new RegExp(r'([^\n$]+)'),
errorMessage: 'Expected a credential string.')
.value((r) {
var decoded = utf8.decode(base64Url.decode(r.span.text));
var decoded = utf8.decode(base64Url.decode(r.span!.text));
var scanner = new SpanScanner(decoded);
return credentials.parse(scanner).value;
return credentials.parse(scanner)!.value;
});
final Parser basic = match<Null>('Basic').space();
@ -40,14 +40,14 @@ final Parser basicAuth = basic.then(credentialString).index(1);
void main() {
while (true) {
stdout.write('Enter a basic auth value: ');
var line = stdin.readLineSync();
var line = stdin.readLineSync()!;
var scanner = new SpanScanner(line, sourceUrl: 'stdin');
var result = basicAuth.parse(scanner);
var result = basicAuth.parse(scanner)!;
if (!result.successful) {
for (var error in result.errors) {
print(error.toolString);
print(error.span.highlight(color: true));
print(error.span!.highlight(color: true));
}
} else
print(result.value);

View file

@ -8,13 +8,13 @@ Parser<num> calculatorGrammar() {
var expr = reference<num>();
var number = match<num>(new RegExp(r'-?[0-9]+(\.[0-9]+)?'))
.value((r) => num.parse(r.span.text));
.value((r) => num.parse(r.span!.text));
var hex = match<int>(new RegExp(r'0x([A-Fa-f0-9]+)'))
.map((r) => int.parse(r.scanner.lastMatch[1], radix: 16));
.map((r) => int.parse(r.scanner.lastMatch![1]!, radix: 16));
var binary = match<int>(new RegExp(r'([0-1]+)b'))
.map((r) => int.parse(r.scanner.lastMatch[1], radix: 2));
.map((r) => int.parse(r.scanner.lastMatch![1]!, radix: 2));
var alternatives = <Parser<num>>[];
@ -22,9 +22,9 @@ Parser<num> calculatorGrammar() {
alternatives.add(
chain<num>([
expr.space(),
match<Null>(op).space(),
match<Null>(op).space() as Parser<num>,
expr.space(),
]).map((r) => f(r.value[0], r.value[2])),
]).map((r) => f(r.value![0], r.value![2])),
);
}
@ -55,14 +55,14 @@ void main() {
while (true) {
stdout.write('Enter an expression: ');
var line = stdin.readLineSync();
var line = stdin.readLineSync()!;
var scanner = new SpanScanner(line, sourceUrl: 'stdin');
var result = calculator.parse(scanner);
var result = calculator.parse(scanner)!;
if (!result.successful) {
for (var error in result.errors) {
stderr.writeln(error.toolString);
stderr.writeln(error.span.highlight(color: true));
stderr.writeln(error.span!.highlight(color: true));
}
} else
print(result.value);

View file

@ -3,7 +3,7 @@ import 'package:combinator/combinator.dart';
import 'package:string_scanner/string_scanner.dart';
final Parser<String> id =
match<String>(RegExp(r'[A-Za-z]+')).value((r) => r.span.text);
match<String>(RegExp(r'[A-Za-z]+')).value((r) => r.span!.text);
// We can use `separatedBy` to easily construct parser
// that can be matched multiple times, separated by another
@ -13,14 +13,14 @@ final Parser<String> id =
main() {
while (true) {
stdout.write('Enter a string (ex "a,b,c"): ');
var line = stdin.readLineSync();
var line = stdin.readLineSync()!;
var scanner = new SpanScanner(line, sourceUrl: 'stdin');
var result = id.separatedBy(match(',').space()).parse(scanner);
var result = id.separatedBy(match(',').space()).parse(scanner)!;
if (!result.successful) {
for (var error in result.errors) {
print(error.toolString);
print(error.span.highlight(color: true));
print(error.span!.highlight(color: true));
}
} else
print(result.value);

View file

@ -9,13 +9,13 @@ Parser jsonGrammar() {
var number = match<num>(new RegExp(r'-?[0-9]+(\.[0-9]+)?'),
errorMessage: 'Expected a number.')
.value(
(r) => num.parse(r.span.text),
(r) => num.parse(r.span!.text),
);
// Parse a string (no escapes supported, because lazy).
var string =
match(new RegExp(r'"[^"]*"'), errorMessage: 'Expected a string.').value(
(r) => r.span.text.substring(1, r.span.text.length - 1),
(r) => r.span!.text.substring(1, r.span!.text.length - 1),
);
// Parse an array
@ -29,7 +29,7 @@ Parser jsonGrammar() {
string.space(),
match(':').space(),
expr.error(errorMessage: 'Missing expression.'),
]).castDynamic().cast<Map>().value((r) => {r.value[0]: r.value[2]});
]).castDynamic().cast<Map>().value((r) => {r.value![0]: r.value![2]});
// Parse an object.
var object = keyValuePair
@ -55,14 +55,14 @@ main() {
while (true) {
stdout.write('Enter some JSON: ');
var line = stdin.readLineSync();
var line = stdin.readLineSync()!;
var scanner = new SpanScanner(line, sourceUrl: 'stdin');
var result = JSON.parse(scanner);
var result = JSON.parse(scanner)!;
if (!result.successful) {
for (var error in result.errors) {
print(error.toolString);
print(error.span.highlight(color: true));
print(error.span!.highlight(color: true));
}
} else
print(result.value);

View file

@ -17,19 +17,19 @@ final Parser decimal = ( // digits, (dot, digits)?
final Parser number = //
(minus.opt() & decimal) // minus?, decimal
.map<num>((r) => num.parse(r.span.text));
.map<num>((r) => num.parse(r.span!.text));
main() {
while (true) {
stdout.write('Enter a number: ');
var line = stdin.readLineSync();
var line = stdin.readLineSync()!;
var scanner = new SpanScanner(line, sourceUrl: 'stdin');
var result = number.parse(scanner);
var result = number.parse(scanner)!;
if (!result.successful) {
for (var error in result.errors) {
stderr.writeln(error.toolString);
stderr.writeln(error.span.highlight(color: true));
stderr.writeln(error.span!.highlight(color: true));
}
} else
print(result.value);

View file

@ -6,9 +6,9 @@ import 'package:string_scanner/string_scanner.dart';
final Parser<String> key =
match<String>(RegExp(r'[^=&\n]+'), errorMessage: 'Missing k/v')
.value((r) => r.span.text);
.value((r) => r.span!.text);
final Parser value = key.map((r) => Uri.decodeQueryComponent(r.value));
final Parser value = key.map((r) => Uri.decodeQueryComponent(r.value!));
final Parser pair = chain([
key,
@ -16,27 +16,27 @@ final Parser pair = chain([
value,
]).map((r) {
return {
r.value[0]: r.value[2],
r.value![0]: r.value![2],
};
});
final Parser pairs = pair
.separatedBy(match(r'&'))
.map((r) => r.value.reduce((a, b) => a..addAll(b)));
.map((r) => r.value!.reduce((a, b) => a..addAll(b)));
final Parser queryString = pairs.opt();
main() {
while (true) {
stdout.write('Enter a query string: ');
var line = stdin.readLineSync();
var line = stdin.readLineSync()!;
var scanner = new SpanScanner(line, sourceUrl: 'stdin');
var result = pairs.parse(scanner);
var result = pairs.parse(scanner)!;
if (!result.successful) {
for (var error in result.errors) {
print(error.toolString);
print(error.span.highlight(color: true));
print(error.span!.highlight(color: true));
}
} else
print(result.value);

View file

@ -27,21 +27,21 @@ void main() {
var number = match(new RegExp(r'[0-9]+(\.[0-9]+)?'),
errorMessage: 'Expected a number.')
.map((r) => num.parse(r.span.text));
.map((r) => num.parse(r.span!.text));
var id = match(
new RegExp(
r'[A-Za-z_!\\$",\\+-\\./:;\\?<>%&\\*@\[\]\\{\}\\|`\\^~][A-Za-z0-9_!\\$",\\+-\\./:;\\?<>%&\*@\[\]\\{\}\\|`\\^~]*'),
errorMessage: 'Expected an ID')
.map((r) =>
symbols[r.span.text] ??= throw "Undefined symbol: '${r.span.text}'");
symbols[r.span!.text] ??= throw "Undefined symbol: '${r.span!.text}'");
var atom = number.castDynamic().or(id);
var list = expr.space().times(2, exact: false).map((r) {
try {
var out = [];
var q = new Queue.from(r.value.reversed);
var q = new Queue.from(r.value!.reversed);
while (q.isNotEmpty) {
var current = q.removeFirst();
@ -69,8 +69,8 @@ void main() {
while (true) {
stdout.write('> ');
var line = stdin.readLineSync();
var result = expr.parse(new SpanScanner(line));
var line = stdin.readLineSync()!;
var result = expr.parse(new SpanScanner(line))!;
if (result.errors.isNotEmpty) {
for (var error in result.errors) {

View file

@ -2,7 +2,7 @@ import 'package:combinator/combinator.dart';
import 'package:string_scanner/string_scanner.dart';
void main() {
var pub = match('pub').map((r) => r.span.text).space();
var pub = match('pub').map((r) => r.span!.text).space();
var dart = match('dart').map((r) => 24).space();
var lang = match('lang').map((r) => true).space();
@ -10,5 +10,5 @@ void main() {
var grammar = tuple3(pub, dart, lang);
var scanner = SpanScanner('pub dart lang');
print(grammar.parse(scanner).value);
print(grammar.parse(scanner)!.value);
}

View file

@ -8,7 +8,7 @@ class _Advance<T> extends Parser<T> {
@override
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;
return result;
}

View file

@ -7,7 +7,7 @@ part of lex.src.combinator;
/// You can provide a custom [errorMessage]. You can set it to `false` to not
/// generate any error at all.
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,
severity ?? SyntaxErrorSeverity.error);
}
@ -33,7 +33,7 @@ class _Any<T> extends Parser<T> {
int replay = args.scanner.position;
for (var parser in inactive) {
var result = parser._parse(args.increaseDepth());
var result = parser._parse(args.increaseDepth())!;
if (result.successful)
return result;
@ -58,7 +58,7 @@ class _Any<T> extends Parser<T> {
}
@override
ParseResult<T> __parse(ParseArgs args) {
ParseResult<T>? __parse(ParseArgs args) {
// Never called
return null;
}

View file

@ -1,7 +1,7 @@
part of lex.src.combinator;
class _Cache<T> extends Parser<T> {
final Map<int, ParseResult<T>> _cache = {};
final Map<int, ParseResult<T>?> _cache = {};
final Parser<T> parser;
_Cache(this.parser);
@ -10,7 +10,7 @@ class _Cache<T> extends Parser<T> {
ParseResult<T> __parse(ParseArgs args) {
return _cache.putIfAbsent(args.scanner.position, () {
return parser._parse(args.increaseDepth());
}).change(parser: this);
})!.change(parser: this);
}
@override

View file

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

View file

@ -4,21 +4,21 @@ part of lex.src.combinator;
///
/// If [failFast] is `true` (default), then the first failure to parse will abort the parse.
ListParser<T> chain<T>(Iterable<Parser<T>> parsers,
{bool failFast: true, SyntaxErrorSeverity severity}) {
{bool failFast: true, SyntaxErrorSeverity? severity}) {
return new _Chain<T>(
parsers, failFast != false, severity ?? SyntaxErrorSeverity.error);
}
class _Alt<T> extends Parser<T> {
final Parser<T> parser;
final String errorMessage;
final String? errorMessage;
final SyntaxErrorSeverity severity;
_Alt(this.parser, this.errorMessage, this.severity);
@override
ParseResult<T> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth());
var result = parser._parse(args.increaseDepth())!;
return result.successful
? result
: result.addErrors([
@ -43,12 +43,12 @@ class _Chain<T> extends ListParser<T> {
@override
ParseResult<List<T>> __parse(ParseArgs args) {
var errors = <SyntaxError>[];
var results = <T>[];
var spans = <FileSpan>[];
var results = <T?>[];
var spans = <FileSpan?>[];
bool successful = true;
for (var parser in parsers) {
var result = parser._parse(args.increaseDepth());
var result = parser._parse(args.increaseDepth())!;
if (!result.successful) {
if (parser is _Alt) errors.addAll(result.errors);
@ -66,10 +66,10 @@ class _Chain<T> extends ListParser<T> {
if (result.span != null) spans.add(result.span);
}
FileSpan span;
FileSpan? span;
if (spans.isNotEmpty) {
span = spans.reduce((a, b) => a.expand(b));
span = spans.reduce((a, b) => a!.expand(b!));
}
return new ParseResult<List<T>>(

View file

@ -3,7 +3,7 @@ part of lex.src.combinator;
class _Check<T> extends Parser<T> {
final Parser<T> parser;
final Matcher matcher;
final String errorMessage;
final String? errorMessage;
final SyntaxErrorSeverity severity;
_Check(this.parser, this.matcher, this.errorMessage, this.severity);
@ -11,7 +11,7 @@ class _Check<T> extends Parser<T> {
@override
ParseResult<T> __parse(ParseArgs args) {
var matchState = {};
var result = parser._parse(args.increaseDepth()).change(parser: this);
var result = parser._parse(args.increaseDepth())!.change(parser: this);
if (!result.successful)
return result;
else if (!matcher.matches(result.value, matchState)) {

View file

@ -62,21 +62,21 @@ class ParseArgs {
ParseArgs(this.trampoline, this.scanner, this.depth);
ParseArgs increaseDepth() => new ParseArgs(trampoline, scanner, depth + 1);
ParseArgs increaseDepth() => ParseArgs(trampoline, scanner, depth + 1);
}
/// A parser combinator, which can parse very complicated grammars in a manageable manner.
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;
if (args.trampoline.hasMemoized(this, pos))
return args.trampoline.getMemoized<T>(this, pos);
if (args.trampoline.isActive(this, pos))
return new ParseResult(args.trampoline, args.scanner, this, false, []);
return ParseResult(args.trampoline, args.scanner, this, false, []);
args.trampoline.enter(this, pos);
var result = __parse(args);
@ -86,79 +86,80 @@ abstract class Parser<T> {
}
/// Parses text from a [SpanScanner].
ParseResult<T> parse(SpanScanner scanner, [int depth = 1]) {
var args = new ParseArgs(new Trampoline(), scanner, depth);
ParseResult<T>? parse(SpanScanner scanner, [int depth = 1]) {
var args = ParseArgs(Trampoline(), scanner, depth);
return _parse(args);
}
/// Skips forward a certain amount of steps after parsing, if it was successful.
Parser<T> forward(int amount) => new _Advance<T>(this, amount);
Parser<T> forward(int amount) => _Advance<T>(this, amount);
/// Moves backward a certain amount of steps after parsing, if it was successful.
Parser<T> back(int amount) => new _Advance<T>(this, amount * -1);
Parser<T> back(int amount) => _Advance<T>(this, amount * -1);
/// Casts this parser to produce [U] objects.
Parser<U> cast<U extends T>() => new _Cast<T, U>(this);
Parser<U> cast<U extends T>() => _Cast<T, U>(this);
/// Casts this parser to produce [dynamic] objects.
Parser<dynamic> castDynamic() => new _CastDynamic<T>(this);
Parser<dynamic> castDynamic() => _CastDynamic<T>(this);
// TODO: Type issue
/// 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) {
return new _Change<T, U>(this, f);
Parser<U> change<U>(ParseResult<U> Function(ParseResult<T>?) f) {
return _Change<T, U>(this, f);
}
/// Validates the parse result against a [Matcher].
///
/// You can provide a custom [errorMessage].
Parser<T> check(Matcher matcher,
{String errorMessage, SyntaxErrorSeverity severity}) =>
new _Check<T>(
{String? errorMessage, SyntaxErrorSeverity? severity}) =>
_Check<T>(
this, matcher, errorMessage, severity ?? SyntaxErrorSeverity.error);
/// Binds an [errorMessage] to a copy of this parser.
Parser<T> error({String errorMessage, SyntaxErrorSeverity severity}) =>
new _Alt<T>(this, errorMessage, severity ?? SyntaxErrorSeverity.error);
Parser<T> error({String? errorMessage, SyntaxErrorSeverity? severity}) =>
_Alt<T>(this, errorMessage, severity ?? SyntaxErrorSeverity.error);
/// Removes multiple errors that occur in the same spot; this can reduce noise in parser output.
Parser<T> foldErrors({bool equal(SyntaxError a, SyntaxError b)}) {
equal ??= (b, e) => b.span.start.offset == e.span.start.offset;
return new _FoldErrors<T>(this, equal);
Parser<T> foldErrors({bool equal(SyntaxError a, SyntaxError b)?}) {
equal ??= (b, e) => b.span!.start.offset == e.span!.start.offset;
return _FoldErrors<T>(this, equal);
}
/// Transforms the parse result using a unary function.
Parser<U> map<U>(U Function(ParseResult<T>) f) {
return new _Map<T, U>(this, f);
return _Map<T, U>(this, f);
}
/// Prevents recursion past a certain [depth], preventing stack overflow errors.
Parser<T> maxDepth(int depth) => new _MaxDepth<T>(this, depth);
Parser<T> maxDepth(int depth) => _MaxDepth<T>(this, depth);
Parser<T> operator ~() => negate();
/// Ensures this pattern is not matched.
///
/// You can provide an [errorMessage].
Parser<T> negate({String errorMessage, SyntaxErrorSeverity severity}) =>
new _Negate<T>(this, errorMessage, severity ?? SyntaxErrorSeverity.error);
Parser<T> negate({String? errorMessage, SyntaxErrorSeverity? severity}) =>
_Negate<T>(this, errorMessage, severity ?? SyntaxErrorSeverity.error);
/// Caches the results of parse attempts at various locations within the source text.
///
/// Use this to prevent excessive recursion.
Parser<T> cache() => new _Cache<T>(this);
Parser<T> cache() => _Cache<T>(this);
Parser<T> operator &(Parser<T> other) => and(other);
/// Consumes `this` and another parser, but only considers the result of `this` parser.
Parser<T> and(Parser other) => then(other).change<T>((r) {
return new ParseResult(
r.trampoline,
return ParseResult<T>(
r!.trampoline,
r.scanner,
this,
r.successful,
r.errors,
span: r.span,
value: (r.value != null ? r.value[0] : r.value) as T,
value: (r.value != null ? r.value![0] : r.value) as T?,
);
});
@ -168,16 +169,16 @@ abstract class Parser<T> {
Parser<T> or<U>(Parser<T> other) => any<T>([this, other]);
/// Parses this sequence one or more times.
ListParser<T> plus() => times(1, exact: false);
ListParser<T?> plus() => times(1, exact: false);
/// Safely escapes this parser when an error occurs.
///
/// The generated parser only runs once; repeated uses always exit eagerly.
Parser<T> safe(
{bool backtrack: true,
String errorMessage,
SyntaxErrorSeverity severity}) =>
new _Safe<T>(
String? errorMessage,
SyntaxErrorSeverity? severity}) =>
_Safe<T>(
this, backtrack, errorMessage, severity ?? SyntaxErrorSeverity.error);
Parser<List<T>> separatedByComma() =>
@ -190,18 +191,18 @@ abstract class Parser<T> {
var suffix = other.then(this).index(1).cast<T>();
return this.then(suffix.star()).map((r) {
var preceding =
r.value.isEmpty ? [] : (r.value[0] == null ? [] : [r.value[0]]);
var out = new List<T>.from(preceding);
if (r.value[1] != null) out.addAll(r.value[1] as Iterable<T>);
r.value!.isEmpty ? [] : (r.value![0] == null ? [] : [r.value![0]]);
var out = List<T>.from(preceding);
if (r.value![1] != null) out.addAll(r.value![1] as Iterable<T>);
return out;
});
}
Parser<T> surroundedByCurlyBraces({T defaultValue}) => opt()
Parser<T?> surroundedByCurlyBraces({T? defaultValue}) => opt()
.surroundedBy(match('{').space(), match('}').space())
.map((r) => r.value ?? defaultValue);
Parser<T> surroundedBySquareBrackets({T defaultValue}) => opt()
Parser<T?> surroundedBySquareBrackets({T? defaultValue}) => opt()
.surroundedBy(match('[').space(), match(']').space())
.map((r) => r.value ?? defaultValue);
@ -209,7 +210,7 @@ abstract class Parser<T> {
///
/// If no [right] is provided, it expects to see the same pattern on both sides.
/// Use this parse things like parenthesized expressions, arrays, etc.
Parser<T> surroundedBy(Parser left, [Parser right]) {
Parser<T> surroundedBy(Parser left, [Parser? right]) {
return chain([
left,
this,
@ -227,17 +228,17 @@ abstract class Parser<T> {
surroundedBy(match('(').space(), match(')').space());
/// Consumes any trailing whitespace.
Parser<T> space() => trail(new RegExp(r'[ \n\r\t]+'));
Parser<T> space() => trail(RegExp(r'[ \n\r\t]+'));
/// 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();
/// Shortcut for [chain]-ing two parsers together.
ListParser<dynamic> then(Parser other) => chain<dynamic>([this, other]);
/// Casts this instance into a [ListParser].
ListParser<T> toList() => new _ToList<T>(this);
ListParser<T?> toList() => _ToList<T>(this);
/// Consumes and ignores any trailing occurrences of [pattern].
Parser<T> trail(Pattern pattern) =>
@ -249,13 +250,13 @@ abstract class Parser<T> {
/// an infinite amount of occurrences after the specified [count].
///
/// 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,
String tooFew,
String tooMany,
String? tooFew,
String? tooMany,
bool backtrack: true,
SyntaxErrorSeverity severity}) {
return new _Repeat<T>(this, count, exact, tooFew, tooMany, backtrack,
SyntaxErrorSeverity? severity}) {
return _Repeat<T>(this, count, exact, tooFew, tooMany, backtrack,
severity ?? SyntaxErrorSeverity.error);
}
@ -263,11 +264,11 @@ abstract class Parser<T> {
///
/// If [backtrack] is `true` (default), then a failed parse will not
/// modify the scanner state.
Parser<T> opt({bool backtrack: true}) => new _Opt(this, backtrack);
Parser<T> opt({bool backtrack: true}) => _Opt(this, backtrack);
/// Sets the value of the [ParseResult].
Parser<T> value(T Function(ParseResult<T>) f) {
return new _Value<T>(this, f);
return _Value<T>(this, f);
}
/// Prints a representation of this parser, ideally without causing a stack overflow.
@ -280,26 +281,26 @@ abstract class ListParser<T> extends Parser<List<T>> {
Parser<T> first() => index(0);
/// Modifies this parser to only return the value at the given index [i].
Parser<T> index(int i) => new _Index<T>(this, i);
Parser<T> index(int i) => _Index<T>(this, i);
/// Shortcut for calling [index] with the greatest-possible index.
Parser<T> last() => index(-1);
/// Modifies this parser to call `List.reduce` on the parsed values.
Parser<T> reduce(T Function(T, T) combine) => new _Reduce<T>(this, combine);
Parser<T> reduce(T Function(T, T) combine) => _Reduce<T>(this, combine);
/// Sorts the parsed values, using the given [Comparator].
ListParser<T> sort(Comparator<T> compare) => new _Compare(this, compare);
ListParser<T> sort(Comparator<T> compare) => _Compare(this, compare);
@override
ListParser<T> opt({bool backtrack: true}) => new _ListOpt(this, backtrack);
ListParser<T> opt({bool backtrack: true}) => _ListOpt(this, backtrack);
/// Modifies this parser, returning only the values that match a predicate.
Parser<List<T>> where(bool Function(T) f) =>
map<List<T>>((r) => r.value.where(f).toList());
map<List<T>>((r) => r.value!.where(f).toList());
/// Condenses a [ListParser] into having a value of the combined span's text.
Parser<String> flatten() => map<String>((r) => r.span.text);
Parser<String> flatten() => map<String>((r) => r.span!.text);
}
/// Prevents stack overflow in recursive parsers.
@ -313,32 +314,32 @@ class Trampoline {
}
ParseResult<T> getMemoized<T>(Parser parser, int position) {
return _memo[parser].firstWhere((t) => t.item1 == position).item2
return _memo[parser]!.firstWhere((t) => t.item1 == position).item2
as ParseResult<T>;
}
void memoize(Parser parser, int position, ParseResult result) {
void memoize(Parser parser, int position, ParseResult? result) {
if (result != null) {
var list = _memo.putIfAbsent(parser, () => []);
var tuple = new Tuple2(position, result);
var tuple = Tuple2(position, result);
if (!list.contains(tuple)) list.add(tuple);
}
}
bool isActive(Parser parser, int position) {
if (!_active.containsKey(parser)) return false;
var q = _active[parser];
var q = _active[parser]!;
if (q.isEmpty) return false;
//return q.contains(position);
return q.first == position;
}
void enter(Parser parser, int position) {
_active.putIfAbsent(parser, () => new Queue()).addFirst(position);
_active.putIfAbsent(parser, () => Queue()).addFirst(position);
}
void exit(Parser parser) {
if (_active.containsKey(parser)) _active[parser].removeFirst();
if (_active.containsKey(parser)) _active[parser]!.removeFirst();
}
}
@ -347,8 +348,8 @@ class ParseResult<T> {
final Parser<T> parser;
final bool successful;
final Iterable<SyntaxError> errors;
final FileSpan span;
final T value;
final FileSpan? span;
final T? value;
final SpanScanner scanner;
final Trampoline trampoline;
@ -357,12 +358,12 @@ class ParseResult<T> {
{this.span, this.value});
ParseResult<T> change(
{Parser<T> parser,
bool successful,
Iterable<SyntaxError> errors,
FileSpan span,
T value}) {
return new ParseResult<T>(
{Parser<T>? parser,
bool? successful,
Iterable<SyntaxError>? errors,
FileSpan? span,
T? value}) {
return ParseResult<T>(
trampoline,
scanner,
parser ?? this.parser,
@ -375,7 +376,7 @@ class ParseResult<T> {
ParseResult<T> addErrors(Iterable<SyntaxError> errors) {
return change(
errors: new List<SyntaxError>.from(this.errors)..addAll(errors),
errors: List<SyntaxError>.from(this.errors)..addAll(errors),
);
}
}

View file

@ -8,12 +8,12 @@ class _Compare<T> extends ListParser<T> {
@override
ParseResult<List<T>> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth());
ParseResult<List<T>> result = parser._parse(args.increaseDepth())!;
if (!result.successful) return result;
result = result.change(
value: result.value?.isNotEmpty == true ? result.value : []);
result = result.change(value: new List<T>.from(result.value));
result = result.change(value: new List<T>.from(result.value!));
return new ParseResult<List<T>>(
args.trampoline,
args.scanner,
@ -21,7 +21,7 @@ class _Compare<T> extends ListParser<T> {
true,
[],
span: result.span,
value: result.value..sort(compare),
value: result.value?..sort(compare),
);
}

View file

@ -8,7 +8,7 @@ class _FoldErrors<T> extends Parser<T> {
@override
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) {
if (!out.any((b) => equal(e, b))) out.add(e);
return out;

View file

@ -8,11 +8,11 @@ class _Index<T> extends Parser<T> {
@override
ParseResult<T> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth());
Object value;
ParseResult<List<T>> result = parser._parse(args.increaseDepth())!;
Object? value;
if (result.successful)
value = index == -1 ? result.value.last : result.value.elementAt(index);
value = index == -1 ? result.value!.last : result.value!.elementAt(index);
return new ParseResult<T>(
args.trampoline,
@ -21,7 +21,7 @@ class _Index<T> extends Parser<T> {
result.successful,
result.errors,
span: result.span,
value: value as T,
value: value as T?,
);
}

View file

@ -4,14 +4,14 @@ part of lex.src.combinator;
///
/// You can provide a custom [errorMessage].
Parser<T> longest<T>(Iterable<Parser<T>> parsers,
{Object errorMessage, SyntaxErrorSeverity severity}) {
{Object? errorMessage, SyntaxErrorSeverity? severity}) {
return new _Longest(
parsers, errorMessage, severity ?? SyntaxErrorSeverity.error);
}
class _Longest<T> extends Parser<T> {
final Iterable<Parser<T>> parsers;
final Object errorMessage;
final Object? errorMessage;
final SyntaxErrorSeverity severity;
_Longest(this.parsers, this.errorMessage, this.severity);
@ -31,7 +31,7 @@ class _Longest<T> extends Parser<T> {
var results = <ParseResult<T>>[];
for (var parser in inactive) {
var result = parser._parse(args.increaseDepth());
var result = parser._parse(args.increaseDepth())!;
if (result.successful && result.span != null)
results.add(result);
@ -41,8 +41,8 @@ class _Longest<T> extends Parser<T> {
}
if (results.isNotEmpty) {
results.sort((a, b) => b.span.length.compareTo(a.span.length));
args.scanner.scan(results.first.span.text);
results.sort((a, b) => b.span!.length.compareTo(a.span!.length));
args.scanner.scan(results.first.span!.text);
return results.first;
}
@ -66,7 +66,7 @@ class _Longest<T> extends Parser<T> {
var results = <ParseResult<T>>[];
for (var parser in parsers) {
var result = parser._parse(args.increaseDepth());
var result = parser._parse(args.increaseDepth())!;
if (result.successful)
results.add(result);
@ -76,8 +76,8 @@ class _Longest<T> extends Parser<T> {
}
if (results.isNotEmpty) {
results.sort((a, b) => b.span.length.compareTo(a.span.length));
args.scanner.scan(results.first.span.text);
results.sort((a, b) => b.span!.length.compareTo(a.span!.length));
args.scanner.scan(results.first.span!.text);
return results.first;
}

View file

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

View file

@ -2,12 +2,12 @@ part of lex.src.combinator;
/// Expects to match a given [pattern]. If it is not matched, you can provide a custom [errorMessage].
Parser<T> match<T>(Pattern pattern,
{String errorMessage, SyntaxErrorSeverity severity}) =>
{String? errorMessage, SyntaxErrorSeverity? severity}) =>
new _Match<T>(pattern, errorMessage, severity ?? SyntaxErrorSeverity.error);
class _Match<T> extends Parser<T> {
final Pattern pattern;
final String errorMessage;
final String? errorMessage;
final SyntaxErrorSeverity severity;
_Match(this.pattern, this.errorMessage, this.severity);

View file

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

View file

@ -2,14 +2,14 @@ part of lex.src.combinator;
class _Negate<T> extends Parser<T> {
final Parser<T> parser;
final String errorMessage;
final String? errorMessage;
final SyntaxErrorSeverity severity;
_Negate(this.parser, this.errorMessage, this.severity);
@override
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) {
return new ParseResult<T>(

View file

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

View file

@ -8,7 +8,7 @@ class _Reduce<T> extends Parser<T> {
@override
ParseResult<T> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth());
ParseResult<List<T>> result = parser._parse(args.increaseDepth())!;
if (!result.successful)
return new ParseResult<T>(
@ -28,7 +28,7 @@ class _Reduce<T> extends Parser<T> {
result.successful,
[],
span: result.span,
value: result.value.isEmpty ? null : result.value.reduce(combine),
value: result.value!.isEmpty ? null : result.value!.reduce(combine),
);
}

View file

@ -3,7 +3,7 @@ part of lex.src.combinator;
Reference<T> reference<T>() => new Reference<T>._();
class Reference<T> extends Parser<T> {
Parser<T> _parser;
Parser<T>? _parser;
bool printed = false;
Reference._();
@ -16,22 +16,22 @@ class Reference<T> extends Parser<T> {
}
@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
ParseResult<T> _parse(ParseArgs args) {
return _parser._parse(args);
ParseResult<T>? _parse(ParseArgs args) {
return _parser!._parse(args);
}
@override
void stringify(CodeBuffer buffer) {
if (_parser == null)
buffer.writeln('(undefined reference <$T>)');
else if (!printed) _parser.stringify(buffer);
else if (!printed) _parser!.stringify(buffer);
printed = true;
buffer.writeln('(previously printed reference)');
}

View file

@ -1,26 +1,26 @@
part of lex.src.combinator;
class _Repeat<T> extends ListParser<T> {
class _Repeat<T> extends ListParser<T?> {
final Parser<T> parser;
final int count;
final bool exact, backtrack;
final String tooFew, tooMany;
final String? tooFew, tooMany;
final SyntaxErrorSeverity severity;
_Repeat(this.parser, this.count, this.exact, this.tooFew, this.tooMany,
this.backtrack, this.severity);
@override
ParseResult<List<T>> __parse(ParseArgs args) {
ParseResult<List<T?>> __parse(ParseArgs args) {
var errors = <SyntaxError>[];
var results = <T>[];
var spans = <FileSpan>[];
var results = <T?>[];
var spans = <FileSpan?>[];
int success = 0, replay = args.scanner.position;
ParseResult<T> result;
ParseResult<T>? result;
do {
result = parser._parse(args.increaseDepth());
if (result.successful) {
if (result!.successful) {
success++;
results.add(result.value);
replay = args.scanner.position;
@ -41,12 +41,12 @@ class _Repeat<T> extends ListParser<T> {
if (backtrack) args.scanner.position = replay;
return new ParseResult<List<T>>(
return new ParseResult<List<T?>>(
args.trampoline, args.scanner, this, false, errors);
} else if (success > count && exact) {
if (backtrack) args.scanner.position = replay;
return new ParseResult<List<T>>(
return new ParseResult<List<T?>>(
args.trampoline, args.scanner, this, false, [
new SyntaxError(
severity,
@ -56,8 +56,8 @@ class _Repeat<T> extends ListParser<T> {
]);
}
var span = spans.reduce((a, b) => a.expand(b));
return new ParseResult<List<T>>(
var span = spans.reduce((a, b) => a!.expand(b!));
return new ParseResult<List<T?>>(
args.trampoline,
args.scanner,
this,

View file

@ -3,18 +3,18 @@ part of lex.src.combinator;
class _Safe<T> extends Parser<T> {
final Parser<T> parser;
final bool backtrack;
final String errorMessage;
final String? errorMessage;
final SyntaxErrorSeverity severity;
bool _triggered = false;
_Safe(this.parser, this.backtrack, this.errorMessage, this.severity);
@override
ParseResult<T> __parse(ParseArgs args) {
ParseResult<T>? __parse(ParseArgs args) {
var replay = args.scanner.position;
try {
if (_triggered) throw null;
if (_triggered) throw Exception();
return parser._parse(args.increaseDepth());
} catch (_) {
_triggered = true;

View file

@ -1,16 +1,16 @@
part of lex.src.combinator;
class _ToList<T> extends ListParser<T> {
class _ToList<T> extends ListParser<T?> {
final Parser<T> parser;
_ToList(this.parser);
@override
ParseResult<List<T>> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth());
ParseResult<List<T?>> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!;
if (result.value is List) {
return (result as ParseResult<List<T>>).change(parser: this);
return (result as ParseResult<List<T?>>).change(parser: this);
}
return new ParseResult(

View file

@ -1,48 +1,48 @@
part of lex.src.combinator;
/// A typed parser that parses a sequence of 2 values of different types.
Parser<Tuple2<A, B>> tuple2<A, B>(Parser<A> a, Parser<B> b) {
Parser<Tuple2<A?, B?>> tuple2<A, B>(Parser<A> a, Parser<B> b) {
return chain([a, b]).map((r) {
return Tuple2(r.value[0] as A, r.value[1] as B);
return Tuple2(r.value![0] as A?, r.value![1] as B?);
});
}
/// A typed parser that parses a sequence of 3 values of different types.
Parser<Tuple3<A, B, C>> tuple3<A, B, C>(Parser<A> a, Parser<B> b, Parser<C> c) {
Parser<Tuple3<A?, B?, C?>> tuple3<A, B, C>(Parser<A> a, Parser<B> b, Parser<C> c) {
return chain([a, b, c]).map((r) {
return Tuple3(r.value[0] as A, r.value[1] as B, r.value[2] as C);
return Tuple3(r.value![0] as A?, r.value![1] as B?, r.value![2] as C?);
});
}
/// A typed parser that parses a sequence of 4 values of different types.
Parser<Tuple4<A, B, C, D>> tuple4<A, B, C, D>(
Parser<Tuple4<A?, B?, C?, D?>> tuple4<A, B, C, D>(
Parser<A> a, Parser<B> b, Parser<C> c, Parser<D> d) {
return chain([a, b, c, d]).map((r) {
return Tuple4(
r.value[0] as A, r.value[1] as B, r.value[2] as C, r.value[3] as D);
r.value![0] as A?, r.value![1] as B?, r.value![2] as C?, r.value![3] as D?);
});
}
/// A typed parser that parses a sequence of 5 values of different types.
Parser<Tuple5<A, B, C, D, E>> tuple5<A, B, C, D, E>(
Parser<Tuple5<A?, B?, C?, D?, E?>> tuple5<A, B, C, D, E>(
Parser<A> a, Parser<B> b, Parser<C> c, Parser<D> d, Parser<E> e) {
return chain([a, b, c, d, e]).map((r) {
return Tuple5(r.value[0] as A, r.value[1] as B, r.value[2] as C,
r.value[3] as D, r.value[4] as E);
return Tuple5(r.value![0] as A?, r.value![1] as B?, r.value![2] as C?,
r.value![3] as D?, r.value![4] as E?);
});
}
/// A typed parser that parses a sequence of 6 values of different types.
Parser<Tuple6<A, B, C, D, E, F>> tuple6<A, B, C, D, E, F>(Parser<A> a,
Parser<Tuple6<A?, B?, C?, D?, E?, F?>> tuple6<A, B, C, D, E, F>(Parser<A> a,
Parser<B> b, Parser<C> c, Parser<D> d, Parser<E> e, Parser<F> f) {
return chain([a, b, c, d, e, f]).map((r) {
return Tuple6(r.value[0] as A, r.value[1] as B, r.value[2] as C,
r.value[3] as D, r.value[4] as E, r.value[5] as F);
return Tuple6(r.value![0] as A?, r.value![1] as B?, r.value![2] as C?,
r.value![3] as D?, r.value![4] as E?, r.value![5] as F?);
});
}
/// A typed parser that parses a sequence of 7 values of different types.
Parser<Tuple7<A, B, C, D, E, F, G>> tuple7<A, B, C, D, E, F, G>(
Parser<Tuple7<A?, B?, C?, D?, E?, F?, G?>> tuple7<A, B, C, D, E, F, G>(
Parser<A> a,
Parser<B> b,
Parser<C> c,
@ -51,7 +51,7 @@ Parser<Tuple7<A, B, C, D, E, F, G>> tuple7<A, B, C, D, E, F, G>(
Parser<F> f,
Parser<G> g) {
return chain([a, b, c, d, e, f, g]).map((r) {
return Tuple7(r.value[0] as A, r.value[1] as B, r.value[2] as C,
r.value[3] as D, r.value[4] as E, r.value[5] as F, r.value[6] as G);
return Tuple7(r.value![0] as A?, r.value![1] as B?, r.value![2] as C?,
r.value![3] as D?, r.value![4] as E?, r.value![5] as F?, r.value![6] as G?);
});
}

View file

@ -8,7 +8,7 @@ class _Value<T> extends Parser<T> {
@override
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;
}

View file

@ -2,16 +2,16 @@ import 'package:source_span/source_span.dart';
class SyntaxError implements Exception {
final SyntaxErrorSeverity severity;
final String message;
final FileSpan span;
String _toolString;
final String? message;
final FileSpan? span;
String? _toolString;
SyntaxError(this.severity, this.message, this.span);
String get toolString {
String? get toolString {
if (_toolString != null) return _toolString;
var type = severity == SyntaxErrorSeverity.warning ? 'warning' : 'error';
return _toolString = '$type: ${span.start.toolString}: $message';
return _toolString = '$type: ${span!.start.toolString}: $message';
}
}

View file

@ -5,7 +5,7 @@ author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/thosakwe/combinator.git
publish_to: none
environment:
sdk: ">=2.10.0 <3.0.0"
sdk: '>=2.12.0 <3.0.0'
dependencies:
code_buffer:
git:

View file

@ -4,19 +4,19 @@ import 'common.dart';
main() {
var number = chain([
match(new RegExp(r'[0-9]+')).value((r) => int.parse(r.span.text)),
match(new RegExp(r'[0-9]+')).value((r) => int.parse(r.span!.text)),
match(',').opt(),
]).first().cast<int>();
var numbers = number.plus();
test('sort', () {
var parser = numbers.sort((a, b) => a.compareTo(b));
expect(parser.parse(scan('21,2,3,34,20')).value, [2, 3, 20, 21, 34]);
var parser = numbers.sort((a, b) => a!.compareTo(b!));
expect(parser.parse(scan('21,2,3,34,20'))!.value, [2, 3, 20, 21, 34]);
});
test('reduce', () {
var parser = numbers.reduce((a, b) => a + b);
expect(parser.parse(scan('21,2,3,34,20')).value, 80);
expect(parser.parse(scan('not numbers')).value, isNull);
var parser = numbers.reduce((a, b) => a! + b!);
expect(parser.parse(scan('21,2,3,34,20'))!.value, 80);
expect(parser.parse(scan('not numbers'))!.value, isNull);
});
}

View file

@ -4,13 +4,13 @@ import 'common.dart';
main() {
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', () {
expect(match('hello').parse(scan('goodbye hello')).successful, isFalse);
expect(match('hello').parse(scan('goodbye hello'))!.successful, isFalse);
});
test('fail if no match', () {
expect(match('hello').parse(scan('world')).successful, isFalse);
expect(match('hello').parse(scan('world'))!.successful, isFalse);
});
}

View file

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

View file

@ -6,10 +6,10 @@ main() {
var parser = match('hello').value((r) => 'world');
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', () {
expect(parser.parse(scan('goodbye world')).value, isNull);
expect(parser.parse(scan('goodbye world'))!.value, isNull);
});
}