d259edfa28
This code is opted in to the latest language version but was not ever migrated. It could possibly be just deleted, as it hasn't been runnable for some time, but I have no idea really. Change-Id: I7ccda121dcd56ed9388b48c70e3b3c0b2d087e47 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/292042 Commit-Queue: Jake Macdonald <jakemac@google.com> Auto-Submit: Jake Macdonald <jakemac@google.com> Reviewed-by: Nate Bosch <nbosch@google.com>
889 lines
22 KiB
Dart
889 lines
22 KiB
Dart
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
library PegParser;
|
|
|
|
/*
|
|
* The following functions are combinators for building Rules.
|
|
*
|
|
* A rule is one of the following
|
|
* - A String which matches the string literally.
|
|
* - A Symbol which matches the symbol's definition.
|
|
* - A list of rules with an optional reducing function, which matches a sequence.
|
|
* - The result of calling one of the combinators.
|
|
*
|
|
* Some rules are 'value-generating' rules, they return an 'abstract syntax
|
|
* tree' with the match. If a rule is not value-generating [:null:] is the
|
|
* value.
|
|
*
|
|
* A Symbol is always a value-generating rule. If the value is not required, use
|
|
* [:SKIP(aSymbol):] in place of [:aSymbol:].
|
|
*
|
|
* A String is not a value-generating rule but can be converted into one by
|
|
* using [:TEXT('string'):] in place of [:'string':].
|
|
*
|
|
* A list or sequence is value-generating depending on the subrules. The
|
|
* sequence is value-generating if any of the subrules are value-generating or
|
|
* if there is a reducing function. If no reducing function is given, the value
|
|
* returned depends on the number of value-generating subrules. If there is
|
|
* only one value generating subrule, that provides the value for the sequence.
|
|
* If there are more, then the value is a list of the values of the
|
|
* value-generating subrules.
|
|
*/
|
|
|
|
/**
|
|
* Matches one character by a predicate on the character code.
|
|
* If [spec] is an int, that character is matched.
|
|
* If [spec] is a function it is used
|
|
*
|
|
* Example [: CHARCODE((code) => 48 <= code && code <= 57) :] recognizes an
|
|
* ASCII digit.
|
|
*
|
|
* CHARCODE does not generate a value.
|
|
*/
|
|
_Rule CHARCODE(spec, [name]) {
|
|
if (spec is int)
|
|
return new _CharCodeRule((code) => code == spec, name);
|
|
else
|
|
return new _CharCodeRule(spec, name);
|
|
}
|
|
|
|
/**
|
|
* Matches one of the [characters].
|
|
*
|
|
* CHAR does not generate a value.
|
|
*/
|
|
_Rule CHAR([characters]) {
|
|
if (characters == null) return const _AnyCharRule();
|
|
if (characters is int) return CHARCODE(characters);
|
|
|
|
// Find the range of character codes and construct an array of flags for codes
|
|
// within the range.
|
|
List<int> codes = characters.codeUnits.toList();
|
|
codes.sort((a, b) => a < b
|
|
? -1
|
|
: a > b
|
|
? 1
|
|
: 0);
|
|
int lo = codes[0];
|
|
int hi = codes[codes.length - 1];
|
|
if (lo == hi) return CHARCODE(lo);
|
|
int len = hi - lo + 1;
|
|
var flags = List<bool>.filled(len, false);
|
|
for (int code in codes) flags[code - lo] = true;
|
|
|
|
return CHARCODE((code) => code >= lo && code <= hi && flags[code - lo]);
|
|
}
|
|
|
|
/**
|
|
* Matches the end of the input.
|
|
*
|
|
* END does not generate a value.
|
|
*/
|
|
_Rule get END => new _EndOfInputRule();
|
|
|
|
/**
|
|
* Throws an exception.
|
|
*/
|
|
_Rule ERROR(String message) => new _ErrorRule(message);
|
|
|
|
/**
|
|
* Matches [rule] but does not consume the input. Useful for matching a right
|
|
* context.
|
|
*
|
|
* AT does not generate a value.
|
|
*/
|
|
_Rule AT(rule) => new _ContextRule(_compile(rule));
|
|
|
|
/**
|
|
* Matches when [rule] does not match. No input is consumed.
|
|
*
|
|
* NOT does not generate a value.
|
|
*/
|
|
_Rule NOT(rule) => new _NegativeContextRule(_compile(rule));
|
|
|
|
/**
|
|
* Matches [rule] but generates no value even if [rule] generates a value.
|
|
*
|
|
* SKIP never generates a value.
|
|
*/
|
|
_Rule SKIP(rule) => new _SkipRule(_compile(rule));
|
|
|
|
/**
|
|
* Matches [rule] in a lexical context where whitespace is not automatically
|
|
* skipped. Useful for matching what would normally be considered to be tokens.
|
|
* [name] is a user-friendly description of what is being matched and is used in
|
|
* error messages.
|
|
*
|
|
* LEX(rule)
|
|
* LEX(name, rule)
|
|
*
|
|
* LEX does not generate a value. If a value is required, wrap LEX with TEXT.
|
|
*/
|
|
_Rule LEX(arg1, [arg2]) {
|
|
if (arg2 == null)
|
|
return new _LexicalRule(arg1 is String ? arg1 : null, _compile(arg1));
|
|
else
|
|
return new _LexicalRule(arg1, _compile(arg2));
|
|
}
|
|
|
|
/**
|
|
* Matches [rule] and generates a value from the matched text. If the [rule]
|
|
* matches, then TEXT(rule) matches and has a value derived from the string
|
|
* fragment that was matched. The default derived value is the string fragment.
|
|
*
|
|
* TEXT always generates a value.
|
|
*/
|
|
_Rule TEXT(rule, [extractor]) => new _TextValueRule(
|
|
_compile(rule),
|
|
extractor == null
|
|
? (string, start, end) => string.substring(start, end)
|
|
: extractor);
|
|
|
|
/**
|
|
* Matches an optional rule.
|
|
*
|
|
* MAYBE is a value generating matcher.
|
|
*
|
|
* If [rule] is value generating then the value is the value generated by [rule]
|
|
* if it matches, and [:null:] if it does not.
|
|
*
|
|
* If [rule] is not value generating then the value is [:true:] if [rule]
|
|
* matches and [:false:] if it does not.
|
|
*/
|
|
_Rule MAYBE(rule) => new _OptionalRule(_compile(rule));
|
|
|
|
/**
|
|
* MANY(rule) matches [rule] [min] or more times.
|
|
* [min] must be 0 or 1.
|
|
* If [separator] is provided it is used to match a separator between matches of
|
|
* [rule].
|
|
*
|
|
* MANY is a value generating matcher. The value is a list of the matches of
|
|
* [rule]. The list may be empty if [:min == 0:].
|
|
*/
|
|
_Rule MANY(rule, {separator = null, int min = 1}) {
|
|
assert(0 <= min && min <= 1);
|
|
return new _RepeatRule(_compile(rule), _compileOptional(separator), min);
|
|
}
|
|
|
|
/**
|
|
* Matches [rule] zero or more times. Shorthand for [:MANY(rule, min:0):]
|
|
* TODO: retire min: parameter?
|
|
*
|
|
* MANY0 is a value generating matcher.
|
|
*/
|
|
_Rule MANY0(rule, [separator = null]) {
|
|
return new _RepeatRule(_compile(rule), _compileOptional(separator), 0);
|
|
}
|
|
|
|
/**
|
|
* Matches [rules] in order until one succeeds.
|
|
*
|
|
* OR is value-generating.
|
|
*/
|
|
_Rule OR(
|
|
[a,
|
|
b,
|
|
c,
|
|
d,
|
|
e,
|
|
f,
|
|
g,
|
|
h,
|
|
i,
|
|
j,
|
|
k,
|
|
l,
|
|
m,
|
|
n,
|
|
o,
|
|
p,
|
|
q,
|
|
r,
|
|
s,
|
|
t,
|
|
u,
|
|
v,
|
|
w,
|
|
x,
|
|
y,
|
|
z]) =>
|
|
_compileMultiRule(
|
|
(a is List && b == null) // Backward compat. OR([a, b]) => OR(a, b).
|
|
? a
|
|
: _unspread(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s,
|
|
t, u, v, w, x, y, z),
|
|
false,
|
|
(compiledRules, valueCount, reducer) => new _ChoiceRule(compiledRules));
|
|
|
|
_Rule SEQ(
|
|
[a,
|
|
b,
|
|
c,
|
|
d,
|
|
e,
|
|
f,
|
|
g,
|
|
h,
|
|
i,
|
|
j,
|
|
k,
|
|
l,
|
|
m,
|
|
n,
|
|
o,
|
|
p,
|
|
q,
|
|
r,
|
|
s,
|
|
t,
|
|
u,
|
|
v,
|
|
w,
|
|
x,
|
|
y,
|
|
z]) =>
|
|
_compile(_unspread(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s,
|
|
t, u, v, w, x, y, z));
|
|
|
|
/**
|
|
* Matches [rule]
|
|
*/
|
|
_Rule MEMO(rule) => new _MemoRule(_compile(rule));
|
|
|
|
_Rule TAG(tag, rule) => _compile([
|
|
rule,
|
|
(ast) => [tag, ast]
|
|
]);
|
|
|
|
class ParseError implements Exception {
|
|
const ParseError(String this._message);
|
|
String toString() => _message;
|
|
final String _message;
|
|
}
|
|
|
|
/**
|
|
* A grammar is a collection of symbols and rules that may be used to parse an
|
|
* input.
|
|
*/
|
|
class Grammar {
|
|
Map<String, Symbol> _symbols;
|
|
|
|
/** This rule may be set by the user to define whitespace. */
|
|
late _Rule _whitespace;
|
|
|
|
_Rule get whitespace => _whitespace;
|
|
void set whitespace(rule) {
|
|
_whitespace = _compile(rule);
|
|
}
|
|
|
|
Grammar() : _symbols = new Map<String, Symbol>() {
|
|
whitespace = CHAR(' \t\r\n');
|
|
}
|
|
|
|
/**
|
|
* operator [] is used to find or create symbols. Symbols may appear in rules
|
|
* to define recursive rules.
|
|
*/
|
|
Symbol operator [](String name) {
|
|
if (_symbols.containsKey(name)) return _symbols[name]!;
|
|
Symbol s = new Symbol(name, this);
|
|
_symbols[name] = s;
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* Parses the input string and returns the parsed AST, or throws an exception
|
|
* if the input can't be parsed.
|
|
*/
|
|
parse(root, String text) {
|
|
for (var symbol in _symbols.values)
|
|
if (symbol._rule == null) print('${symbol.name} is undefined');
|
|
|
|
var state = new _ParserState(text, whitespace: whitespace);
|
|
var match = _compile(root).match(state, 0);
|
|
if (match == null) return diagnose(state);
|
|
var pos = match[0];
|
|
pos = _skip_whitespace(state, pos);
|
|
if (pos == state._end) return match[1];
|
|
// TODO: Make this complain about expecting end of file.
|
|
return diagnose(state);
|
|
}
|
|
|
|
diagnose(state) {
|
|
var message = 'unexpected error';
|
|
if (!state.max_rule.isEmpty) {
|
|
var s = new Set();
|
|
for (var rule in state.max_rule) s.add(rule.description());
|
|
var tokens = new List<String>.from(s);
|
|
tokens.sort((a, b) => a.startsWith("'") == b.startsWith("'")
|
|
? a.compareTo(b)
|
|
: a.startsWith("'")
|
|
? 1
|
|
: -1);
|
|
var expected = tokens.join(' or ');
|
|
var found = state.max_pos == state._end
|
|
? 'end of file'
|
|
: "'${state._text[state.max_pos]}'";
|
|
message = 'Expected $expected but found $found';
|
|
}
|
|
int start = state.max_pos;
|
|
int end = start;
|
|
while (start >= 1 && state._text[start - 1] != '\n') --start;
|
|
while (end < state._text.length && state._text[end] != '\n') ++end;
|
|
var line = state._text.substring(start, end);
|
|
var indicator = '';
|
|
for (var i = 0; i < line.length && start + i < state.max_pos; i++)
|
|
indicator = ' $indicator';
|
|
indicator = '$indicator^';
|
|
// TODO: Convert to an exception.
|
|
print(message);
|
|
print(line);
|
|
print(indicator);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class Symbol {
|
|
final String name;
|
|
final Grammar grammar;
|
|
_Rule? _rule;
|
|
|
|
Symbol(this.name, this.grammar);
|
|
|
|
void set def(rule) {
|
|
assert(_rule == null); // Assign once.
|
|
_rule = _compile(rule);
|
|
}
|
|
|
|
toString() => _rule == null ? '<$name>' : '<$name = $_rule>';
|
|
}
|
|
|
|
class _ParserState {
|
|
_ParserState(this._text, {required _Rule whitespace})
|
|
: _end = _text.length,
|
|
whitespaceRule = whitespace {
|
|
max_rule = [];
|
|
}
|
|
|
|
String _text;
|
|
int _end;
|
|
|
|
//
|
|
bool inWhitespaceMode = false;
|
|
_Rule whitespaceRule;
|
|
|
|
// Used for constructing an error message.
|
|
int inhibitExpectedTrackingDepth = 0;
|
|
int max_pos = 0;
|
|
var max_rule;
|
|
}
|
|
|
|
/**
|
|
* An interface tag for rules. If this tag is on a rule, then the description()
|
|
* of the rule is something sensible to put in a message.
|
|
*/
|
|
abstract class _Expectable {
|
|
String description();
|
|
}
|
|
|
|
class _Rule {
|
|
const _Rule();
|
|
// Returns null for a match failure or [pos, ast] for success.
|
|
match(_ParserState state, int pos) {
|
|
if (!state.inWhitespaceMode) {
|
|
pos = _skip_whitespace(state, pos);
|
|
}
|
|
return matchAfterWS(state, pos);
|
|
}
|
|
|
|
// Faster entry point for matching a sub-rule that is matched to the start
|
|
// position of the super-rule. Whitespace has already been skipped so no need
|
|
// to try to skip it again.
|
|
matchAfterWS(_ParserState state, int pos) {
|
|
if (state.inhibitExpectedTrackingDepth == 0) {
|
|
// Track position for possible error messaging
|
|
if (pos > state.max_pos) {
|
|
// Store position and the rule.
|
|
state.max_pos = pos;
|
|
if (this is _Expectable) {
|
|
state.max_rule = [this];
|
|
} else {
|
|
state.max_rule = [];
|
|
}
|
|
} else if (pos == state.max_pos) {
|
|
if (this is _Expectable) {
|
|
state.max_rule.add(this);
|
|
}
|
|
}
|
|
}
|
|
// Delegate the matching logic to the specialized function.
|
|
return _match(state, pos);
|
|
}
|
|
|
|
// Overridden in subclasses to match the rule.
|
|
_match(_ParserState state, int pos) => null;
|
|
|
|
// Does the rule generate a value (AST) with the match?
|
|
bool get generatesValue => false;
|
|
|
|
get defaultValue => null;
|
|
}
|
|
|
|
int _skip_whitespace(state, pos) {
|
|
// Returns the next non-whitespace position.
|
|
// This is done by matching the optional whitespaceRule with the current text.
|
|
if (state.whitespaceRule == null) return pos;
|
|
state.inWhitespaceMode = true;
|
|
state.inhibitExpectedTrackingDepth++;
|
|
while (true) {
|
|
var match = state.whitespaceRule.match(state, pos);
|
|
if (match == null) break;
|
|
pos = match[0];
|
|
}
|
|
state.inWhitespaceMode = false;
|
|
state.inhibitExpectedTrackingDepth--;
|
|
return pos;
|
|
}
|
|
|
|
_Rule? _compileOptional(rule) {
|
|
return rule == null ? null : _compile(rule);
|
|
}
|
|
|
|
_Rule _compile(rule) {
|
|
if (rule is _Rule) return rule;
|
|
if (rule is String) return new _StringRule(rule);
|
|
if (rule is Symbol) return new _SymbolRule(rule);
|
|
if (rule is RegExp) return new _RegExpRule(rule);
|
|
if (rule is List) {
|
|
return _compileMultiRule(
|
|
rule,
|
|
true,
|
|
(compiledRules, valueCount, reducer) =>
|
|
new _SequenceRule(compiledRules, valueCount, reducer));
|
|
}
|
|
throw new Exception('Cannot compile rule: $rule');
|
|
}
|
|
|
|
class _EndOfInputRule extends _Rule {
|
|
_match(_ParserState state, int pos) {
|
|
if (pos == state._end) return [pos, null];
|
|
return null;
|
|
}
|
|
|
|
toString() => 'END';
|
|
}
|
|
|
|
class _ErrorRule extends _Rule {
|
|
String message;
|
|
_ErrorRule(String this.message);
|
|
_match(_ParserState state, int pos) {
|
|
throw new ParseError(message);
|
|
}
|
|
|
|
toString() => 'ERROR($message)';
|
|
}
|
|
|
|
class _CharCodeRule extends _Rule {
|
|
Function _predicate;
|
|
var _name;
|
|
_CharCodeRule(this._predicate, this._name);
|
|
_match(_ParserState state, int pos) {
|
|
if (pos == state._end) return null;
|
|
int code = state._text.codeUnitAt(pos);
|
|
if (_predicate(code)) return [pos + 1, null];
|
|
return null;
|
|
}
|
|
|
|
toString() => _name == null ? 'CHARCODE($_predicate)' : 'CHARCODE($_name)';
|
|
}
|
|
|
|
class _AnyCharRule extends _Rule {
|
|
const _AnyCharRule();
|
|
_match(_ParserState state, int pos) {
|
|
if (pos == state._end) return null;
|
|
return [pos + 1, null];
|
|
}
|
|
|
|
toString() => 'CHAR()';
|
|
}
|
|
|
|
class _SymbolRule extends _Rule {
|
|
final Symbol _symbol;
|
|
_SymbolRule(Symbol this._symbol);
|
|
_match(_ParserState state, int pos) {
|
|
if (_symbol._rule == null)
|
|
throw new Exception("Symbol '${_symbol.name}' is undefined");
|
|
return _symbol._rule!.match(state, pos);
|
|
}
|
|
|
|
bool get generatesValue => true;
|
|
|
|
toString() => '<${_symbol.name}>';
|
|
}
|
|
|
|
class _SkipRule extends _Rule {
|
|
// A rule that has no value.
|
|
_Rule _rule;
|
|
_SkipRule(_Rule this._rule);
|
|
_match(_ParserState state, int pos) {
|
|
var match = _rule.matchAfterWS(state, pos);
|
|
if (match == null) return null;
|
|
return [match[0], null];
|
|
}
|
|
|
|
toString() => 'TOKEN($_rule)';
|
|
}
|
|
|
|
class _StringRule extends _Rule implements _Expectable {
|
|
final String _string;
|
|
int _len;
|
|
_StringRule(this._string) : _len = _string.length;
|
|
|
|
_match(_ParserState state, int pos) {
|
|
if (pos + _len > state._end) return null;
|
|
for (int i = 0; i < _len; i++) {
|
|
if (state._text.codeUnitAt(pos + i) != _string.codeUnitAt(i)) return null;
|
|
}
|
|
return [pos + _len, null];
|
|
}
|
|
|
|
//get defaultValue => _string;
|
|
|
|
toString() => '"$_string"';
|
|
|
|
description() => "'$_string'";
|
|
}
|
|
|
|
class _RegExpRule extends _Rule {
|
|
RegExp _re;
|
|
_RegExpRule(this._re) {
|
|
// There is no convenient way to match an anchored substring.
|
|
throw new Exception('RegExp matching not supported');
|
|
}
|
|
|
|
toString() => '"$_re"';
|
|
}
|
|
|
|
class _LexicalRule extends _Rule implements _Expectable {
|
|
final String? _name;
|
|
final _Rule _rule;
|
|
|
|
_LexicalRule(this._name, this._rule);
|
|
|
|
_match(_ParserState state, int pos) {
|
|
state.inWhitespaceMode = true;
|
|
state.inhibitExpectedTrackingDepth++;
|
|
var match = _rule.matchAfterWS(state, pos);
|
|
state.inhibitExpectedTrackingDepth--;
|
|
state.inWhitespaceMode = false;
|
|
return match;
|
|
}
|
|
|
|
toString() => _name.toString();
|
|
|
|
description() => _name == null ? '?' : _name!;
|
|
}
|
|
|
|
class _TextValueRule extends _Rule {
|
|
final _Rule _rule;
|
|
final _extract; // Function
|
|
|
|
_TextValueRule(_Rule this._rule, Function this._extract);
|
|
|
|
_match(_ParserState state, int pos) {
|
|
var match = _rule.matchAfterWS(state, pos);
|
|
if (match == null) {
|
|
return null;
|
|
}
|
|
var endPos = match[0];
|
|
return [endPos, _extract(state._text, pos, endPos)];
|
|
}
|
|
|
|
bool get generatesValue => true;
|
|
|
|
toString() => 'TEXT($_rule)';
|
|
}
|
|
|
|
_Rule _compileMultiRule(
|
|
List rules, bool allowReducer, finish(compiledRules, valueCount, reducer)) {
|
|
int valueCount = 0;
|
|
List compiledRules = <_Rule>[];
|
|
Function? reducer;
|
|
for (var rule in rules) {
|
|
if (reducer != null)
|
|
throw new Exception('Reducer must be last in sequence: $rule');
|
|
if (rule is Function) {
|
|
if (allowReducer)
|
|
reducer = rule;
|
|
else
|
|
throw new Exception('Bad rule: "$rule"');
|
|
} else {
|
|
_Rule compiledRule = _compile(rule);
|
|
if (compiledRule.generatesValue) ++valueCount;
|
|
compiledRules.add(compiledRule);
|
|
}
|
|
}
|
|
return finish(compiledRules, valueCount, reducer);
|
|
}
|
|
|
|
String _formatMultiRule(String functor, List rules) {
|
|
var sb = new StringBuffer(functor);
|
|
sb.write('(');
|
|
var separator = '';
|
|
for (var rule in rules) {
|
|
sb.write(separator);
|
|
sb.write(rule);
|
|
separator = ',';
|
|
}
|
|
sb.write(')');
|
|
return sb.toString();
|
|
}
|
|
|
|
class _SequenceRule extends _Rule {
|
|
// This rule matches the component rules in order.
|
|
final List<_Rule> _rules;
|
|
final int _generatingSubRules;
|
|
final Function? _reducer;
|
|
bool _generatesValue;
|
|
_SequenceRule(List<_Rule> this._rules, int this._generatingSubRules,
|
|
Function? this._reducer)
|
|
: _generatesValue = _generatingSubRules > 0 || _reducer != null;
|
|
|
|
_match(state, pos) {
|
|
var sequence = [];
|
|
for (var rule in _rules) {
|
|
var match = rule.match(state, pos);
|
|
if (match == null) return null;
|
|
if (rule.generatesValue) {
|
|
var ast = match[1];
|
|
sequence.add(ast);
|
|
}
|
|
pos = match[0];
|
|
}
|
|
if (_reducer == null) {
|
|
if (_generatingSubRules == 0) return [pos, null];
|
|
if (_generatingSubRules == 1) return [pos, sequence[0]];
|
|
return [pos, sequence];
|
|
} else {
|
|
return [pos, _apply(_reducer, sequence)];
|
|
}
|
|
}
|
|
|
|
bool get generatesValue => _generatesValue;
|
|
|
|
toString() => _formatMultiRule('SEQ', _rules);
|
|
}
|
|
|
|
class _ChoiceRule extends _Rule {
|
|
// This rule matches the first component rule that matches.
|
|
List<_Rule> _rules;
|
|
_ChoiceRule(List<_Rule> this._rules);
|
|
|
|
_match(state, pos) {
|
|
for (var rule in _rules) {
|
|
var match = rule.match(state, pos);
|
|
if (match != null) {
|
|
/*
|
|
if (!rule.generatesValue) {
|
|
var value = rule.defaultValue;
|
|
if (value != null)
|
|
return [match[0], value];
|
|
}
|
|
*/
|
|
return match;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
bool get generatesValue => true;
|
|
|
|
toString() => _formatMultiRule('OR', _rules);
|
|
}
|
|
|
|
class _OptionalRule extends _Rule {
|
|
_Rule _rule;
|
|
_OptionalRule(_Rule this._rule);
|
|
_match(_ParserState state, int pos) {
|
|
var match = _rule.match(state, pos);
|
|
if (_rule.generatesValue) return match == null ? [pos, null] : match;
|
|
return match == null ? [pos, false] : [match[0], true];
|
|
}
|
|
|
|
bool get generatesValue => true;
|
|
|
|
toString() => 'MAYBE($_rule)';
|
|
}
|
|
|
|
class _ContextRule extends _Rule {
|
|
_Rule _rule;
|
|
_ContextRule(_Rule this._rule);
|
|
_match(_ParserState state, int pos) {
|
|
// TODO: protect error state.
|
|
var match = _rule._match(state, pos);
|
|
if (match == null) return null;
|
|
return [pos, null];
|
|
}
|
|
|
|
toString() => 'AT($_rule)';
|
|
}
|
|
|
|
class _NegativeContextRule extends _Rule {
|
|
_Rule _rule;
|
|
_NegativeContextRule(_Rule this._rule);
|
|
_match(_ParserState state, int pos) {
|
|
// TODO: protect error state.
|
|
var match = _rule._match(state, pos);
|
|
if (match == null) return [pos, null];
|
|
return null;
|
|
}
|
|
|
|
toString() => 'NOT($_rule)';
|
|
}
|
|
|
|
class _RepeatRule extends _Rule {
|
|
// Matches zero, one or more items.
|
|
_Rule _rule;
|
|
_Rule? _separator;
|
|
int _min;
|
|
|
|
_RepeatRule(this._rule, this._separator, this._min);
|
|
|
|
_match(state, pos) {
|
|
// First match.
|
|
var match = _rule.match(state, pos);
|
|
if (match == null) if (_min == 0)
|
|
return [pos, []];
|
|
else
|
|
return null;
|
|
pos = match[0];
|
|
var result = [match[1]];
|
|
|
|
// Subsequent matches:
|
|
while (true) {
|
|
var newPos = pos;
|
|
if (_separator != null) {
|
|
match = _separator!.match(state, pos);
|
|
if (match == null) return [pos, result];
|
|
newPos = match[0];
|
|
}
|
|
match = _rule.match(state, newPos);
|
|
if (match == null) return [pos, result];
|
|
pos = match[0];
|
|
result.add(match[1]);
|
|
}
|
|
}
|
|
|
|
bool get generatesValue => true;
|
|
|
|
toString() =>
|
|
'MANY(min:$_min, $_rule${_separator == null ? '' : ", sep: $_separator"})';
|
|
}
|
|
|
|
class _MemoRule extends _Rule {
|
|
final _Rule _rule;
|
|
|
|
var parseInstance;
|
|
|
|
// A map from position to result. Can this be replaced with something
|
|
// smaller?
|
|
// TODO: figure out how to discard the map and parseInstance after parsing.
|
|
Map<int, Object> map = {};
|
|
|
|
_MemoRule(this._rule);
|
|
|
|
_match(state, pos) {
|
|
// See if we are still parsing the same input. Relies on the fact that the
|
|
// input is a string and strings are immutable.
|
|
if (!identical(parseInstance, state._text)) {
|
|
map = new Map<int, Object>();
|
|
parseInstance = state._text;
|
|
}
|
|
// TODO: does this have to check or preserve parse state (like
|
|
// inWhitespaceMode, error position info etc?)
|
|
// Stored result can be null (memoized failure).
|
|
if (map.containsKey(pos)) {
|
|
return map[pos];
|
|
}
|
|
var match = _rule.match(state, pos);
|
|
map[pos] = match;
|
|
return match;
|
|
}
|
|
|
|
bool get generatesValue => _rule.generatesValue;
|
|
|
|
toString() => 'MEMO($_rule)';
|
|
}
|
|
|
|
_apply(fn, List args) {
|
|
switch (args.length) {
|
|
case 0:
|
|
return fn();
|
|
case 1:
|
|
return fn(args[0]);
|
|
case 2:
|
|
return fn(args[0], args[1]);
|
|
case 3:
|
|
return fn(args[0], args[1], args[2]);
|
|
case 4:
|
|
return fn(args[0], args[1], args[2], args[3]);
|
|
case 5:
|
|
return fn(args[0], args[1], args[2], args[3], args[4]);
|
|
case 6:
|
|
return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
|
|
case 7:
|
|
return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
|
|
case 8:
|
|
return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6],
|
|
args[7]);
|
|
case 9:
|
|
return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6],
|
|
args[7], args[8]);
|
|
case 10:
|
|
return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6],
|
|
args[7], args[8], args[9]);
|
|
|
|
default:
|
|
throw new Exception('Too many arguments in _apply: $args');
|
|
}
|
|
}
|
|
|
|
List _unspread(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v,
|
|
w, x, y, z) {
|
|
List list = [];
|
|
add(element) {
|
|
if (element != null) list.add(element);
|
|
}
|
|
|
|
add(a);
|
|
add(b);
|
|
add(c);
|
|
add(d);
|
|
add(e);
|
|
add(f);
|
|
add(g);
|
|
add(h);
|
|
add(i);
|
|
add(j);
|
|
add(k);
|
|
add(l);
|
|
add(m);
|
|
add(n);
|
|
add(o);
|
|
add(p);
|
|
add(q);
|
|
add(r);
|
|
add(s);
|
|
add(t);
|
|
add(u);
|
|
add(v);
|
|
add(w);
|
|
add(x);
|
|
add(y);
|
|
add(z);
|
|
return list;
|
|
}
|