mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 08:20:31 +00:00
029b1cb948
R=kevmoo@google.com BUG= Review-Url: https://codereview.chromium.org/2957593002 .
887 lines
22 KiB
Dart
887 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 provideds 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 = new List<bool>(len);
|
|
for (int i = 0; i < len; ++i) flags[i] = 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. */
|
|
_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, {_Rule whitespace}) {
|
|
_end = this._text.length;
|
|
whitespaceRule = whitespace;
|
|
max_rule = [];
|
|
}
|
|
|
|
String _text;
|
|
int _end;
|
|
|
|
//
|
|
bool inWhitespaceMode = false;
|
|
_Rule whitespaceRule = null;
|
|
|
|
// 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(String this._name, _Rule 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;
|
|
|
|
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 = new List<_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 = new 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;
|
|
}
|