Updated combinator

This commit is contained in:
thomashii@dukefirehawk.com 2021-05-07 12:42:02 +08:00
parent 7adeff9a63
commit c2c9e40836
21 changed files with 82 additions and 79 deletions

View file

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

View file

@ -8,7 +8,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 ParseResult<U>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,
@ -39,7 +39,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 ParseResult<dynamic>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,

View file

@ -5,7 +5,7 @@ part of lex.src.combinator;
/// If [failFast] is `true` (default), then the first failure to parse will abort the parse. /// If [failFast] is `true` (default), then the first failure to parse will abort the parse.
ListParser<T> chain<T>(Iterable<Parser<T>> parsers, ListParser<T> chain<T>(Iterable<Parser<T>> parsers,
{bool failFast: true, SyntaxErrorSeverity? severity}) { {bool failFast: true, SyntaxErrorSeverity? severity}) {
return new _Chain<T>( return _Chain<T>(
parsers, failFast != false, severity ?? SyntaxErrorSeverity.error); parsers, failFast != false, severity ?? SyntaxErrorSeverity.error);
} }
@ -22,7 +22,7 @@ class _Alt<T> extends Parser<T> {
return result.successful return result.successful
? result ? result
: result.addErrors([ : result.addErrors([
new SyntaxError( SyntaxError(
severity, errorMessage, result.span ?? args.scanner.emptySpan), severity, errorMessage, result.span ?? args.scanner.emptySpan),
]); ]);
} }
@ -43,8 +43,8 @@ class _Chain<T> extends ListParser<T> {
@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>[];
bool successful = true; bool successful = true;
for (var parser in parsers) { for (var parser in parsers) {
@ -54,32 +54,36 @@ class _Chain<T> extends ListParser<T> {
if (parser is _Alt) errors.addAll(result.errors); if (parser is _Alt) errors.addAll(result.errors);
if (failFast) { if (failFast) {
return new ParseResult( return ParseResult(
args.trampoline, args.scanner, this, false, result.errors); args.trampoline, args.scanner, this, false, result.errors);
} }
successful = false; successful = false;
} }
results.add(result.value); if (result.value != null) {
results.add(result.value!);
}
if (result.span != null) spans.add(result.span); if (result.span != null) {
spans.add(result.span!);
}
} }
FileSpan? span; FileSpan? span;
if (spans.isNotEmpty) { if (spans.isNotEmpty) {
span = spans.reduce((a, b) => a!.expand(b!)); 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,
successful, successful,
errors, errors,
span: span, span: span,
value: new List<T>.unmodifiable(results), value: List<T>.unmodifiable(results),
); );
} }

View file

@ -16,11 +16,10 @@ class _Check<T> extends Parser<T> {
return result; return result;
else if (!matcher.matches(result.value, matchState)) { else if (!matcher.matches(result.value, matchState)) {
return result.change(successful: false).addErrors([ return result.change(successful: false).addErrors([
new SyntaxError( SyntaxError(
severity, severity,
errorMessage ?? errorMessage ??
matcher.describe(new StringDescription('Expected ')).toString() + matcher.describe(StringDescription('Expected ')).toString() + '.',
'.',
result.span, result.span,
), ),
]); ]);
@ -30,7 +29,7 @@ class _Check<T> extends Parser<T> {
@override @override
void stringify(CodeBuffer buffer) { void stringify(CodeBuffer buffer) {
var d = matcher.describe(new StringDescription()); var d = matcher.describe(StringDescription());
buffer buffer
..writeln('check($d) (') ..writeln('check($d) (')
..indent(); ..indent();

View file

@ -249,7 +249,7 @@ abstract class Parser<T> {
ListParser<dynamic> then(Parser other) => chain<dynamic>([this, other]); ListParser<dynamic> then(Parser other) => chain<dynamic>([this, other]);
/// Casts this instance into a [ListParser]. /// Casts this instance into a [ListParser].
ListParser<T?> toList() => _ToList<T>(this); ListParser<T> toList() => _ToList<T>(this);
/// Consumes and ignores any trailing occurrences of [pattern]. /// Consumes and ignores any trailing occurrences of [pattern].
Parser<T> trail(Pattern pattern) => Parser<T> trail(Pattern pattern) =>
@ -371,7 +371,7 @@ class ParseResult<T> {
ParseResult<T> change( ParseResult<T> change(
{Parser<T>? parser, {Parser<T>? parser,
bool? successful, bool? successful,
Iterable<SyntaxError>? errors, Iterable<SyntaxError> errors = const [],
FileSpan? span, FileSpan? span,
T? value}) { T? value}) {
return ParseResult<T>( return ParseResult<T>(
@ -379,7 +379,7 @@ class ParseResult<T> {
scanner, scanner,
parser ?? this.parser, parser ?? this.parser,
successful ?? this.successful, successful ?? this.successful,
errors ?? this.errors, errors,
span: span ?? this.span, span: span ?? this.span,
value: value ?? this.value, value: value ?? this.value,
); );

View file

@ -13,8 +13,8 @@ class _Compare<T> extends ListParser<T> {
result = result.change( result = result.change(
value: result.value?.isNotEmpty == true ? result.value : []); value: result.value?.isNotEmpty == true ? result.value : []);
result = result.change(value: new List<T>.from(result.value!)); result = result.change(value: List<T>.from(result.value!));
return new ParseResult<List<T>>( return ParseResult<List<T>>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,

View file

@ -14,7 +14,7 @@ class _Index<T> extends Parser<T> {
if (result.successful) 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>( return ParseResult<T>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,

View file

@ -5,8 +5,7 @@ part of lex.src.combinator;
/// You can provide a custom [errorMessage]. /// You can provide a custom [errorMessage].
Parser<T> longest<T>(Iterable<Parser<T>> parsers, Parser<T> longest<T>(Iterable<Parser<T>> parsers,
{Object? errorMessage, SyntaxErrorSeverity? severity}) { {Object? errorMessage, SyntaxErrorSeverity? severity}) {
return new _Longest( return _Longest(parsers, errorMessage, severity ?? SyntaxErrorSeverity.error);
parsers, errorMessage, severity ?? SyntaxErrorSeverity.error);
} }
class _Longest<T> extends Parser<T> { class _Longest<T> extends Parser<T> {
@ -23,7 +22,7 @@ class _Longest<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, []);
} }
int replay = args.scanner.position; int replay = args.scanner.position;
@ -48,7 +47,7 @@ class _Longest<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)',
@ -56,7 +55,7 @@ class _Longest<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
@ -82,7 +81,7 @@ class _Longest<T> extends Parser<T> {
} }
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)',
@ -90,7 +89,7 @@ class _Longest<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

View file

@ -3,7 +3,7 @@ part of lex.src.combinator;
/// Expects to match a given [pattern]. If it is not matched, you can provide a custom [errorMessage]. /// Expects to match a given [pattern]. If it is not matched, you can provide a custom [errorMessage].
Parser<T> match<T>(Pattern pattern, Parser<T> match<T>(Pattern pattern,
{String? errorMessage, SyntaxErrorSeverity? severity}) => {String? errorMessage, SyntaxErrorSeverity? severity}) =>
new _Match<T>(pattern, errorMessage, severity ?? SyntaxErrorSeverity.error); _Match<T>(pattern, errorMessage, severity ?? SyntaxErrorSeverity.error);
class _Match<T> extends Parser<T> { class _Match<T> extends Parser<T> {
final Pattern pattern; final Pattern pattern;
@ -16,14 +16,14 @@ class _Match<T> extends Parser<T> {
ParseResult<T> __parse(ParseArgs args) { ParseResult<T> __parse(ParseArgs args) {
var scanner = args.scanner; var scanner = args.scanner;
if (!scanner.scan(pattern)) if (!scanner.scan(pattern))
return new ParseResult(args.trampoline, scanner, this, false, [ return ParseResult(args.trampoline, scanner, this, false, [
new SyntaxError( SyntaxError(
severity, severity,
errorMessage ?? 'Expected "$pattern".', errorMessage ?? 'Expected "$pattern".',
scanner.emptySpan, scanner.emptySpan,
), ),
]); ]);
return new ParseResult<T>( return ParseResult<T>(
args.trampoline, args.trampoline,
scanner, scanner,
this, this,

View file

@ -9,7 +9,7 @@ class _MaxDepth<T> extends Parser<T> {
@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 ParseResult<T>(args.trampoline, args.scanner, this, false, []);
} }
return parser._parse(args.increaseDepth()); return parser._parse(args.increaseDepth());

View file

@ -12,7 +12,7 @@ class _Negate<T> extends Parser<T> {
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 ParseResult<T>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,
@ -27,7 +27,7 @@ class _Negate<T> extends Parser<T> {
if (errorMessage != null) { if (errorMessage != null) {
result = result.addErrors([ result = result.addErrors([
new SyntaxError( SyntaxError(
severity, severity,
errorMessage, errorMessage,
result.span, result.span,

View file

@ -13,7 +13,7 @@ class Recursion<T> {
postfix ??= {}; postfix ??= {};
} }
Parser<T> precedence(int p) => new _Precedence(this, p); Parser<T> precedence(int p) => _Precedence(this, p);
void stringify(CodeBuffer buffer) { void stringify(CodeBuffer buffer) {
buffer buffer
@ -106,7 +106,7 @@ class _Precedence<T> extends Parser<T> {
// If we're not done scanning, then we need some sort of guard to ensure the // If we're not done scanning, then we need some sort of guard to ensure the
// that this exact parser does not run again in the exact position. // that this exact parser does not run again in the exact position.
} }
return new ParseResult( return ParseResult(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,
@ -118,7 +118,7 @@ class _Precedence<T> extends Parser<T> {
} }
} }
return new ParseResult( return ParseResult(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,

View file

@ -11,7 +11,7 @@ class _Reduce<T> extends Parser<T> {
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 ParseResult<T>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,
@ -21,7 +21,7 @@ class _Reduce<T> extends Parser<T> {
result = result.change( result = result.change(
value: result.value?.isNotEmpty == true ? result.value : []); value: result.value?.isNotEmpty == true ? result.value : []);
return new ParseResult<T>( return ParseResult<T>(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,

View file

@ -1,6 +1,6 @@
part of lex.src.combinator; part of lex.src.combinator;
Reference<T> reference<T>() => new Reference<T>._(); Reference<T> reference<T>() => Reference<T>._();
class Reference<T> extends Parser<T> { class Reference<T> extends Parser<T> {
Parser<T>? _parser; Parser<T>? _parser;
@ -10,22 +10,21 @@ class Reference<T> extends Parser<T> {
void set parser(Parser<T> value) { void set parser(Parser<T> value) {
if (_parser != null) if (_parser != null)
throw new StateError( throw StateError('There is already a parser assigned to this reference.');
'There is already a parser assigned to this reference.');
_parser = value; _parser = value;
} }
@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 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) if (_parser == null)
throw new StateError('There is no parser assigned to this reference.'); throw StateError('There is no parser assigned to this reference.');
return _parser!._parse(args); return _parser!._parse(args);
} }

View file

@ -22,15 +22,14 @@ class _Safe<T> extends Parser<T> {
var errors = <SyntaxError>[]; var errors = <SyntaxError>[];
errors.add( errors.add(
new SyntaxError( SyntaxError(
severity, severity,
errorMessage, errorMessage,
args.scanner.lastSpan ?? args.scanner.emptySpan, args.scanner.lastSpan ?? args.scanner.emptySpan,
), ),
); );
return new ParseResult<T>( return ParseResult<T>(args.trampoline, args.scanner, this, false, errors);
args.trampoline, args.scanner, this, false, errors);
} }
} }

View file

@ -1,26 +1,30 @@
part of lex.src.combinator; part of lex.src.combinator;
class _ToList<T> extends ListParser<T?> { class _ToList<T> extends ListParser<T> {
final Parser<T> parser; final Parser<T> parser;
_ToList(this.parser); _ToList(this.parser);
@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);
} }
return new ParseResult( List<T> values = [];
if (result.value != null) {
values.add(result.value!);
}
return ParseResult(
args.trampoline, args.trampoline,
args.scanner, args.scanner,
this, this,
result.successful, result.successful,
result.errors, result.errors,
span: result.span, span: result.span,
value: [result.value], value: values,
); );
} }

View file

@ -1,48 +1,48 @@
part of lex.src.combinator; part of lex.src.combinator;
/// A typed parser that parses a sequence of 2 values of different types. /// 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 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. /// 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 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. /// 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) { Parser<A> a, Parser<B> b, Parser<C> c, Parser<D> d) {
return chain([a, b, c, d]).map((r) { return chain([a, b, c, d]).map((r) {
return Tuple4( 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. /// 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) { 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 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?, 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?); r.value?[3] as D, r.value?[4] as E);
}); });
} }
/// A typed parser that parses a sequence of 6 values of different types. /// 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) { 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 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?, 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?); 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. /// 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<A> a,
Parser<B> b, Parser<B> b,
Parser<C> c, 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<F> f,
Parser<G> g) { Parser<G> g) {
return chain([a, b, c, d, e, f, g]).map((r) { 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?, 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?); r.value?[3] as D, r.value?[4] as E, r.value?[5] as F, r.value?[6] as G);
}); });
} }

View file

@ -1,3 +1,3 @@
import 'package:string_scanner/string_scanner.dart'; import 'package:string_scanner/string_scanner.dart';
SpanScanner scan(String text) => new SpanScanner(text); SpanScanner scan(String text) => SpanScanner(text);

View file

@ -4,7 +4,7 @@ import 'common.dart';
main() { main() {
var number = chain([ var number = chain([
match(new RegExp(r'[0-9]+')).value((r) => int.parse(r.span!.text)), match(RegExp(r'[0-9]+')).value((r) => int.parse(r.span!.text)),
match(',').opt(), match(',').opt(),
]).first().cast<int>(); ]).first().cast<int>();

View file

@ -19,7 +19,7 @@ main() {
}); });
test('check', () { test('check', () {
var parser = match<int>(new RegExp(r'[A-Za-z]+')) var parser = match<int>(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);
@ -27,8 +27,7 @@ main() {
}); });
test('map', () { test('map', () {
var parser = var parser = match(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);
}); });

View file

@ -6,12 +6,12 @@ void main() {}
/* /*
void main() { void main() {
var number = match(new RegExp(r'-?[0-9]+(\.[0-9]+)?')) var number = match( RegExp(r'-?[0-9]+(\.[0-9]+)?'))
.map<num>((r) => num.parse(r.span.text)); .map<num>((r) => num.parse(r.span.text));
var term = reference<num>(); var term = reference<num>();
var r = new Recursion<num>(); var r = Recursion<num>();
r.prefix = [number]; r.prefix = [number];
@ -32,7 +32,7 @@ void main() {
term.parser = r.precedence(0); term.parser = r.precedence(0);
num parse(String text) { num parse(String text) {
var scanner = new SpanScanner(text); var scanner = SpanScanner(text);
var result = term.parse(scanner); var result = term.parse(scanner);
print(result.span.highlight()); print(result.span.highlight());
return result.value; return result.value;