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
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

@ -8,7 +8,7 @@ part of lex.src.combinator;
/// generate any error at all.
Parser<T> any<T>(Iterable<Parser<T>> parsers,
{bool backtrack: true, errorMessage, SyntaxErrorSeverity? severity}) {
return new _Any(parsers, backtrack != false, errorMessage,
return _Any(parsers, backtrack != false, errorMessage,
severity ?? SyntaxErrorSeverity.error);
}
@ -26,14 +26,14 @@ class _Any<T> extends Parser<T> {
.where((p) => !args.trampoline.isActive(p, args.scanner.position));
if (inactive.isEmpty) {
return new ParseResult(args.trampoline, args.scanner, this, false, []);
return ParseResult(args.trampoline, args.scanner, this, false, []);
}
var errors = <SyntaxError>[];
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;
@ -45,7 +45,7 @@ class _Any<T> extends Parser<T> {
if (errorMessage != false) {
errors.add(
new SyntaxError(
SyntaxError(
severity,
errorMessage?.toString() ??
'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
ParseResult<T>? __parse(ParseArgs args) {
ParseResult<T> __parse(ParseArgs args) {
// Never called
return null;
throw ArgumentError("[Combinator] Invalid method call");
}
@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,
@ -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

@ -18,7 +18,7 @@ class _Alt<T> extends Parser<T> {
@override
ParseResult<T> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!;
var result = parser._parse(args.increaseDepth());
return result.successful
? result
: result.addErrors([
@ -48,7 +48,7 @@ class _Chain<T> extends ListParser<T> {
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);

View file

@ -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

@ -67,9 +67,9 @@ class ParseArgs {
/// 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))
@ -86,7 +86,7 @@ abstract class Parser<T> {
}
/// 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);
return _parse(args);
}
@ -105,7 +105,7 @@ abstract class Parser<T> {
// 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) {
Parser<U> change<U>(ParseResult<U> Function(ParseResult<T>) f) {
return _Change<T, U>(this, f);
}
@ -140,8 +140,10 @@ abstract class Parser<T> {
/// Ensures this pattern is not matched.
///
/// You can provide an [errorMessage].
Parser<T> negate({String? errorMessage, SyntaxErrorSeverity? severity}) =>
_Negate<T>(this, errorMessage, severity ?? SyntaxErrorSeverity.error);
Parser<T> negate(
{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.
///
@ -153,7 +155,7 @@ abstract class Parser<T> {
/// Consumes `this` and another parser, but only considers the result of `this` parser.
Parser<T> and(Parser other) => then(other).change<T>((r) {
return ParseResult<T>(
r!.trampoline,
r.trampoline,
r.scanner,
this,
r.successful,
@ -176,7 +178,7 @@ abstract class Parser<T> {
/// The generated parser only runs once; repeated uses always exit eagerly.
Parser<T> safe(
{bool backtrack: true,
String? errorMessage,
String errorMessage = "error",
SyntaxErrorSeverity? severity}) =>
_Safe<T>(
this, backtrack, errorMessage, severity ?? SyntaxErrorSeverity.error);
@ -240,7 +242,7 @@ abstract class Parser<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.
@ -259,10 +261,10 @@ 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 = 'Too few',
String tooMany = 'Too many',
bool backtrack: true,
SyntaxErrorSeverity? severity}) {
return _Repeat<T>(this, count, exact, tooFew, tooMany, backtrack,

View file

@ -8,7 +8,7 @@ class _Compare<T> extends ListParser<T> {
@override
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;
result = result.change(

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

View file

@ -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);
@ -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);

View file

@ -8,8 +8,8 @@ class _Map<T, U> extends Parser<U> {
@override
ParseResult<U> __parse(ParseArgs args) {
var result = parser._parse(args.increaseDepth())!;
return new ParseResult<U>(
var result = parser._parse(args.increaseDepth());
return ParseResult<U>(
args.trampoline,
args.scanner,
this,
@ -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

@ -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

@ -9,7 +9,7 @@ class _Negate<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) {
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;
ParseResult<List<T>> 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) {
ParseResult<List<T>> result = parser._parse(args.increaseDepth())!;
ParseResult<List<T>> result = parser._parse(args.increaseDepth());
if (!result.successful)
return new ParseResult<T>(

View file

@ -16,22 +16,26 @@ 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);
}
@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);
}
@override
void stringify(CodeBuffer buffer) {
if (_parser == null)
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,63 +1,67 @@
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;
final String 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;
do {
result = parser._parse(args.increaseDepth());
if (result!.successful) {
if (result.successful) {
success++;
results.add(result.value);
if (result.value != null) {
results.add(result.value!);
}
replay = args.scanner.position;
} 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);
if (success < count) {
errors.addAll(result.errors);
errors.add(
new SyntaxError(
SyntaxError(
severity,
tooFew ?? 'Expected at least $count occurence(s).',
tooFew,
result.span ?? args.scanner.emptySpan,
),
);
if (backtrack) args.scanner.position = replay;
return new ParseResult<List<T?>>(
return 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?>>(
args.trampoline, args.scanner, this, false, [
new SyntaxError(
return ParseResult<List<T>>(args.trampoline, args.scanner, this, false, [
SyntaxError(
severity,
tooMany ?? 'Expected no more than $count occurence(s).',
tooMany,
result.span ?? args.scanner.emptySpan,
),
]);
}
var span = spans.reduce((a, b) => a!.expand(b!));
return new ParseResult<List<T?>>(
var span = spans.reduce((a, b) => a.expand(b));
return ParseResult<List<T>>(
args.trampoline,
args.scanner,
this,
@ -70,7 +74,7 @@ class _Repeat<T> extends ListParser<T?> {
@override
void stringify(CodeBuffer buffer) {
var r = new StringBuffer('{$count');
var r = StringBuffer('{$count');
if (!exact) r.write(',');
r.write('}');
buffer

View file

@ -3,14 +3,14 @@ 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 {
@ -21,8 +21,6 @@ class _Safe<T> extends Parser<T> {
if (backtrack) args.scanner.position = replay;
var errors = <SyntaxError>[];
if (errorMessage != null) {
// TODO: Custom severity for all errors?
errors.add(
new SyntaxError(
severity,
@ -30,7 +28,6 @@ class _Safe<T> extends Parser<T> {
args.scanner.lastSpan ?? args.scanner.emptySpan,
),
);
}
return new ParseResult<T>(
args.trampoline, args.scanner, this, false, errors);

View file

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

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

@ -12,11 +12,11 @@ main() {
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]);
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);
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,29 +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)
.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);
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', () {
@ -44,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);
});
}