parent
347e26f6cf
commit
f4fc58fa17
@ -0,0 +1,24 @@
|
||||
Copyright (c) 2018, Rik Bellens.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the <organization> nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,4 @@
|
||||
library expressions;
|
||||
|
||||
export 'src/expressions.dart';
|
||||
export 'src/evaluator.dart';
|
@ -0,0 +1,22 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2006-2021 Lukas Renggli.
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
@ -0,0 +1,4 @@
|
||||
/// This package contains the core classes of the framework.
|
||||
export 'src/core/exception.dart';
|
||||
export 'src/core/parser.dart';
|
||||
export 'src/core/token.dart';
|
@ -0,0 +1,45 @@
|
||||
/// This package contains the standard parser implementations.
|
||||
export 'src/parser/action/cast.dart';
|
||||
export 'src/parser/action/cast_list.dart';
|
||||
export 'src/parser/action/continuation.dart';
|
||||
export 'src/parser/action/flatten.dart';
|
||||
export 'src/parser/action/map.dart';
|
||||
export 'src/parser/action/permute.dart';
|
||||
export 'src/parser/action/pick.dart';
|
||||
export 'src/parser/action/token.dart';
|
||||
export 'src/parser/action/trimming.dart';
|
||||
export 'src/parser/character/any_of.dart';
|
||||
export 'src/parser/character/char.dart';
|
||||
export 'src/parser/character/digit.dart';
|
||||
export 'src/parser/character/letter.dart';
|
||||
export 'src/parser/character/lowercase.dart';
|
||||
export 'src/parser/character/none_of.dart';
|
||||
export 'src/parser/character/parser.dart';
|
||||
export 'src/parser/character/pattern.dart';
|
||||
export 'src/parser/character/predicate.dart';
|
||||
export 'src/parser/character/range.dart';
|
||||
export 'src/parser/character/uppercase.dart';
|
||||
export 'src/parser/character/whitespace.dart';
|
||||
export 'src/parser/character/word.dart';
|
||||
export 'src/parser/combinator/and.dart';
|
||||
export 'src/parser/combinator/choice.dart';
|
||||
export 'src/parser/combinator/delegate.dart';
|
||||
export 'src/parser/combinator/list.dart';
|
||||
export 'src/parser/combinator/not.dart';
|
||||
export 'src/parser/combinator/optional.dart';
|
||||
export 'src/parser/combinator/sequence.dart';
|
||||
export 'src/parser/combinator/settable.dart';
|
||||
export 'src/parser/misc/eof.dart';
|
||||
export 'src/parser/misc/epsilon.dart';
|
||||
export 'src/parser/misc/failure.dart';
|
||||
export 'src/parser/misc/position.dart';
|
||||
export 'src/parser/predicate/any.dart';
|
||||
export 'src/parser/predicate/predicate.dart';
|
||||
export 'src/parser/predicate/string.dart';
|
||||
export 'src/parser/repeater/greedy.dart';
|
||||
export 'src/parser/repeater/lazy.dart';
|
||||
export 'src/parser/repeater/limited.dart';
|
||||
export 'src/parser/repeater/possessive.dart';
|
||||
export 'src/parser/repeater/repeating.dart';
|
||||
export 'src/parser/repeater/separated_by.dart';
|
||||
export 'src/parser/repeater/unbounded.dart';
|
@ -0,0 +1,5 @@
|
||||
/// This package exports the core library of PetitParser, a dynamic parser
|
||||
/// combinator framework.
|
||||
|
||||
export 'core.dart';
|
||||
export 'parser.dart';
|
@ -0,0 +1,32 @@
|
||||
import '../meta/lib/meta.dart';
|
||||
|
||||
import '../core/token.dart';
|
||||
import 'failure.dart';
|
||||
import 'result.dart';
|
||||
import 'success.dart';
|
||||
|
||||
/// An immutable parse context.
|
||||
@immutable
|
||||
class Context {
|
||||
const Context(this.buffer, this.position);
|
||||
|
||||
/// The buffer we are working on.
|
||||
final String buffer;
|
||||
|
||||
/// The current position in the [buffer].
|
||||
final int position;
|
||||
|
||||
/// Returns a result indicating a parse success.
|
||||
Result<R> success<R>(R result, [int? position]) =>
|
||||
Success<R>(buffer, position ?? this.position, result);
|
||||
|
||||
/// Returns a result indicating a parse failure.
|
||||
Result<R> failure<R>(String message, [int? position]) =>
|
||||
Failure<R>(buffer, position ?? this.position, message);
|
||||
|
||||
/// Returns the current line:column position in the [buffer].
|
||||
String toPositionString() => Token.positionString(buffer, position);
|
||||
|
||||
@override
|
||||
String toString() => 'Context[${toPositionString()}]';
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import '../core/exception.dart';
|
||||
import 'result.dart';
|
||||
|
||||
/// An immutable parse result in case of a failed parse.
|
||||
class Failure<R> extends Result<R> {
|
||||
const Failure(String buffer, int position, this.message)
|
||||
: super(buffer, position);
|
||||
|
||||
@override
|
||||
bool get isFailure => true;
|
||||
|
||||
@override
|
||||
R get value => throw ParserException(this);
|
||||
|
||||
@override
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Result<T> map<T>(T Function(R element) callback) => failure(message);
|
||||
|
||||
@override
|
||||
String toString() => 'Failure[${toPositionString()}]: $message';
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import '../core/exception.dart';
|
||||
import 'context.dart';
|
||||
|
||||
/// An immutable parse result.
|
||||
abstract class Result<R> extends Context {
|
||||
const Result(String buffer, int position) : super(buffer, position);
|
||||
|
||||
/// Returns `true` if this result indicates a parse success.
|
||||
bool get isSuccess => false;
|
||||
|
||||
/// Returns `true` if this result indicates a parse failure.
|
||||
bool get isFailure => false;
|
||||
|
||||
/// Returns the parsed value of this result, or throws a [ParserException]
|
||||
/// if this is a parse failure.
|
||||
R get value;
|
||||
|
||||
/// Returns the error message of this result, or throws an [UnsupportedError]
|
||||
/// if this is a parse success.
|
||||
String get message;
|
||||
|
||||
/// Transforms the result with a [callback].
|
||||
Result<T> map<T>(T Function(R element) callback);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import 'result.dart';
|
||||
|
||||
/// An immutable parse result in case of a successful parse.
|
||||
class Success<R> extends Result<R> {
|
||||
const Success(String buffer, int position, this.value)
|
||||
: super(buffer, position);
|
||||
|
||||
@override
|
||||
bool get isSuccess => true;
|
||||
|
||||
@override
|
||||
final R value;
|
||||
|
||||
@override
|
||||
String get message =>
|
||||
throw UnsupportedError('Successful parse results do not have a message.');
|
||||
|
||||
@override
|
||||
Result<T> map<T>(T Function(R element) callback) => success(callback(value));
|
||||
|
||||
@override
|
||||
String toString() => 'Success[${toPositionString()}]: $value';
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import '../context/failure.dart';
|
||||
|
||||
/// An exception raised in case of a parse error.
|
||||
class ParserException implements FormatException {
|
||||
final Failure failure;
|
||||
|
||||
ParserException(this.failure);
|
||||
|
||||
@override
|
||||
String get message => failure.message;
|
||||
|
||||
@override
|
||||
int get offset => failure.position;
|
||||
|
||||
@override
|
||||
String get source => failure.buffer;
|
||||
|
||||
@override
|
||||
String toString() => '${failure.message} at ${failure.toPositionString()}';
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
import '../meta/lib/meta.dart';
|
||||
|
||||
import '../context/context.dart';
|
||||
import '../context/failure.dart';
|
||||
import '../context/result.dart';
|
||||
import '../context/success.dart';
|
||||
|
||||
/// Abstract base class of all parsers.
|
||||
@optionalTypeArgs
|
||||
abstract class Parser<T> {
|
||||
Parser();
|
||||
|
||||
/// Primitive method doing the actual parsing.
|
||||
///
|
||||
/// The method is overridden in concrete subclasses to implement the
|
||||
/// parser specific logic. The methods takes a parse [context] and
|
||||
/// returns the resulting context, which is either a [Success] or
|
||||
/// [Failure] context.
|
||||
Result<T> parseOn(Context context);
|
||||
|
||||
/// Primitive method doing the actual parsing.
|
||||
///
|
||||
/// This method is an optimized version of [Parser.parseOn] that is getting
|
||||
/// its speed advantage by avoiding any unnecessary memory allocations.
|
||||
///
|
||||
/// The method is overridden in most concrete subclasses to implement the
|
||||
/// optimized logic. As an input the method takes a [buffer] and the current
|
||||
/// [position] in that buffer. It returns a new (positive) position in case
|
||||
/// of a successful parse, or `-1` in case of a failure.
|
||||
///
|
||||
/// Subclasses don't necessarily have to override this method, since it is
|
||||
/// emulated using its slower brother.
|
||||
int fastParseOn(String buffer, int position) {
|
||||
final result = parseOn(Context(buffer, position));
|
||||
return result.isSuccess ? result.position : -1;
|
||||
}
|
||||
|
||||
/// Returns the parse result of the [input].
|
||||
///
|
||||
/// The implementation creates a default parse context on the input and calls
|
||||
/// the internal parsing logic of the receiving parser.
|
||||
///
|
||||
/// For example, `letter().plus().parse('abc')` results in an instance of
|
||||
/// [Success], where [Context.position] is `3` and [Success.value] is
|
||||
/// `[a, b, c]`.
|
||||
///
|
||||
/// Similarly, `letter().plus().parse('123')` results in an instance of
|
||||
/// [Failure], where [Context.position] is `0` and [Failure.message] is
|
||||
/// ['letter expected'].
|
||||
Result<T> parse(String input) => parseOn(Context(input, 0));
|
||||
|
||||
/// Returns a shallow copy of the receiver.
|
||||
///
|
||||
/// Override this method in all subclasses, return its own type.
|
||||
Parser<T> copy();
|
||||
|
||||
/// Recursively tests for structural equality of two parsers.
|
||||
///
|
||||
/// The code automatically deals with recursive parsers and parsers that
|
||||
/// refer to other parsers. Do not override this method, instead customize
|
||||
/// [Parser.hasEqualProperties] and [Parser.children].
|
||||
@nonVirtual
|
||||
bool isEqualTo(Parser other, [Set<Parser>? seen]) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (runtimeType != other.runtimeType || !hasEqualProperties(other)) {
|
||||
return false;
|
||||
}
|
||||
seen ??= {};
|
||||
return !seen.add(this) || hasEqualChildren(other, seen);
|
||||
}
|
||||
|
||||
/// Compare the properties of two parsers.
|
||||
///
|
||||
/// Override this method in all subclasses that add new state.
|
||||
@protected
|
||||
@mustCallSuper
|
||||
bool hasEqualProperties(covariant Parser other) => true;
|
||||
|
||||
/// Compare the children of two parsers.
|
||||
///
|
||||
/// Normally this method does not need to be overridden, as this method works
|
||||
/// generically on the returned [Parser.children].
|
||||
@protected
|
||||
@mustCallSuper
|
||||
bool hasEqualChildren(covariant Parser other, Set<Parser> seen) {
|
||||
final thisChildren = children, otherChildren = other.children;
|
||||
if (thisChildren.length != otherChildren.length) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < thisChildren.length; i++) {
|
||||
if (!thisChildren[i].isEqualTo(otherChildren[i], seen)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns a list of directly referenced parsers.
|
||||
///
|
||||
/// For example, `letter().children` returns the empty collection `[]`,
|
||||
/// because the letter parser is a primitive or leaf parser that does not
|
||||
/// depend or call any other parser.
|
||||
///
|
||||
/// In contrast, `letter().or(digit()).children` returns a collection
|
||||
/// containing both the `letter()` and `digit()` parser.
|
||||
///
|
||||
/// Override this method and [Parser.replace] in all subclasses that
|
||||
/// reference other parsers.
|
||||
List<Parser> get children => const [];
|
||||
|
||||
/// Changes the receiver by replacing [source] with [target]. Does nothing
|
||||
/// if [source] does not exist in [Parser.children].
|
||||
///
|
||||
/// The following example creates a letter parser and then defines a parser
|
||||
/// called `example` that accepts one or more letters. Eventually the parser
|
||||
/// `example` is modified by replacing the `letter` parser with a new
|
||||
/// parser that accepts a digit. The resulting `example` parser accepts one
|
||||
/// or more digits.
|
||||
///
|
||||
/// final letter = letter();
|
||||
/// final example = letter.plus();
|
||||
/// example.replace(letter, digit());
|
||||
///
|
||||
/// Override this method and [Parser.children] in all subclasses that
|
||||
/// reference other parsers.
|
||||
void replace(Parser source, Parser target) {
|
||||
// no children, nothing to do
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import '../meta/lib/meta.dart';
|
||||
|
||||
import '../matcher/matches_skipping.dart';
|
||||
import '../parser/action/token.dart';
|
||||
import '../parser/character/char.dart';
|
||||
import '../parser/combinator/choice.dart';
|
||||
import '../parser/combinator/optional.dart';
|
||||
import '../parser/combinator/sequence.dart';
|
||||
import 'parser.dart';
|
||||
|
||||
/// A token represents a parsed part of the input stream.
|
||||
///
|
||||
/// The token holds the resulting value of the input, the input buffer,
|
||||
/// and the start and stop position in the input buffer. It provides many
|
||||
/// convenience methods to access the state of the token.
|
||||
@immutable
|
||||
class Token<T> {
|
||||
/// Constructs a token from the parsed value, the input buffer, and the
|
||||
/// start and stop position in the input buffer.
|
||||
const Token(this.value, this.buffer, this.start, this.stop);
|
||||
|
||||
/// The parsed value of the token.
|
||||
final T value;
|
||||
|
||||
/// The parsed buffer of the token.
|
||||
final String buffer;
|
||||
|
||||
/// The start position of the token in the buffer.
|
||||
final int start;
|
||||
|
||||
/// The stop position of the token in the buffer.
|
||||
final int stop;
|
||||
|
||||
/// The consumed input of the token.
|
||||
String get input => buffer.substring(start, stop);
|
||||
|
||||
/// The length of the token.
|
||||
int get length => stop - start;
|
||||
|
||||
/// The line number of the token (only works for [String] buffers).
|
||||
int get line => Token.lineAndColumnOf(buffer, start)[0];
|
||||
|
||||
/// The column number of this token (only works for [String] buffers).
|
||||
int get column => Token.lineAndColumnOf(buffer, start)[1];
|
||||
|
||||
@override
|
||||
String toString() => 'Token[${positionString(buffer, start)}]: $value';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is Token &&
|
||||
value == other.value &&
|
||||
start == other.start &&
|
||||
stop == other.stop;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => value.hashCode + start.hashCode + stop.hashCode;
|
||||
|
||||
/// Returns a parser for that detects newlines platform independently.
|
||||
static Parser newlineParser() => _newlineParser;
|
||||
|
||||
static final Parser _newlineParser =
|
||||
char('\n') | (char('\r') & char('\n').optional());
|
||||
|
||||
/// Converts the [position] index in a [buffer] to a line and column tuple.
|
||||
static List<int> lineAndColumnOf(String buffer, int position) {
|
||||
var line = 1, offset = 0;
|
||||
for (final token in newlineParser().token().matchesSkipping(buffer)) {
|
||||
if (position < token.stop) {
|
||||
return [line, position - offset + 1];
|
||||
}
|
||||
line++;
|
||||
offset = token.stop;
|
||||
}
|
||||
return [line, position - offset + 1];
|
||||
}
|
||||
|
||||
/// Returns a human readable string representing the [position] index in a
|
||||
/// [buffer].
|
||||
static String positionString(String buffer, int position) {
|
||||
final lineAndColumn = lineAndColumnOf(buffer, position);
|
||||
return '${lineAndColumn[0]}:${lineAndColumn[1]}';
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import '../core/parser.dart';
|
||||
|
||||
extension AcceptParser<T> on Parser<T> {
|
||||
/// Tests if the [input] can be successfully parsed.
|
||||
///
|
||||
/// For example, `letter().plus().accept('abc')` returns `true`, and
|
||||
/// `letter().plus().accept('123')` returns `false`.
|
||||
bool accept(String input) => fastParseOn(input, 0) >= 0;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import '../core/parser.dart';
|
||||
import '../parser/action/map.dart';
|
||||
import '../parser/combinator/and.dart';
|
||||
import '../parser/combinator/choice.dart';
|
||||
import '../parser/combinator/sequence.dart';
|
||||
import '../parser/predicate/any.dart';
|
||||
import '../parser/repeater/possessive.dart';
|
||||
import 'matches_skipping.dart';
|
||||
|
||||
extension MatchesParser<T> on Parser<T> {
|
||||
/// Returns a list of all successful overlapping parses of the [input].
|
||||
///
|
||||
/// For example, `letter().plus().matches('abc de')` results in the list
|
||||
/// `[['a', 'b', 'c'], ['b', 'c'], ['c'], ['d', 'e'], ['e']]`. See
|
||||
/// [matchesSkipping] to retrieve non-overlapping parse results.
|
||||
List<T> matches(String input) {
|
||||
final list = <T>[];
|
||||
and()
|
||||
.map(list.add, hasSideEffects: true)
|
||||
.seq(any())
|
||||
.or(any())
|
||||
.star()
|
||||
.fastParseOn(input, 0);
|
||||
return list;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import '../core/parser.dart';
|
||||
import '../parser/action/map.dart';
|
||||
import '../parser/combinator/choice.dart';
|
||||
import '../parser/predicate/any.dart';
|
||||
import '../parser/repeater/possessive.dart';
|
||||
import 'matches.dart';
|
||||
|
||||
extension MatchesSkippingParser<T> on Parser<T> {
|
||||
/// Returns a list of all successful non-overlapping parses of the input.
|
||||
///
|
||||
/// For example, `letter().plus().matchesSkipping('abc de')` results in the
|
||||
/// list `[['a', 'b', 'c'], ['d', 'e']]`. See [matches] to retrieve
|
||||
/// overlapping parse results.
|
||||
List<T> matchesSkipping(String input) {
|
||||
final list = <T>[];
|
||||
map(list.add, hasSideEffects: true).or(any()).star().fastParseOn(input, 0);
|
||||
return list;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import '../core/parser.dart';
|
||||
import 'pattern/parser_pattern.dart';
|
||||
|
||||
extension PatternParser<T> on Parser<T> {
|
||||
/// Converts this [Parser] into a [Pattern] for basic searches within strings.
|
||||
Pattern toPattern() => ParserPattern(this);
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import '../../meta/lib/meta.dart';
|
||||
import 'parser_pattern.dart';
|
||||
|
||||
@immutable
|
||||
class ParserMatch implements Match {
|
||||
@override
|
||||
final ParserPattern pattern;
|
||||
|
||||
@override
|
||||
final String input;
|
||||
|
||||
@override
|
||||
final int start;
|
||||
|
||||
@override
|
||||
final int end;
|
||||
|
||||
const ParserMatch(this.pattern, this.input, this.start, this.end);
|
||||
|
||||
@override
|
||||
String? group(int group) => this[group];
|
||||
|
||||
@override
|
||||
String? operator [](int group) =>
|
||||
group == 0 ? input.substring(start, end) : null;
|
||||
|
||||
@override
|
||||
List<String?> groups(List<int> groupIndices) =>
|
||||
groupIndices.map(group).toList(growable: false);
|
||||
|
||||
@override
|
||||
int get groupCount => 0;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import '../../meta/lib/meta.dart';
|
||||
|
||||
import '../../core/parser.dart';
|
||||
import 'parser_match.dart';
|
||||
import 'pattern_iterable.dart';
|
||||
|
||||
@immutable
|
||||
class ParserPattern implements Pattern {
|
||||
final Parser parser;
|
||||
|
||||
const ParserPattern(this.parser);
|
||||
|
||||
/// Matches this parser against [string] repeatedly.
|
||||
///
|
||||
/// If [start] is provided, matching will start at that index. The returned
|
||||
/// iterable lazily computes all the non-overlapping matches of the parser on
|
||||
/// the string, ordered by start index.
|
||||
///
|
||||
/// If the pattern matches the empty string at some point, the next match is
|
||||
/// found by starting at the previous match's end plus one.
|
||||
@override
|
||||
Iterable<ParserMatch> allMatches(String string, [int start = 0]) =>
|
||||
PatternIterable(this, string, start);
|
||||
|
||||
/// Match this pattern against the start of [string].
|
||||
///
|
||||
/// If [start] is provided, this parser is tested against the string at the
|
||||
/// [start] position. That is, a [Match] is returned if the pattern can match
|
||||
/// a part of the string starting from position [start].
|
||||
///
|
||||
/// Returns `null` if the pattern doesn't match.
|
||||
@override
|
||||
ParserMatch? matchAsPrefix(String string, [int start = 0]) {
|
||||
final end = parser.fastParseOn(string, start);
|
||||
return end < 0 ? null : ParserMatch(this, string, start, end);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import '../../meta/lib/meta.dart';
|
||||
|
||||
import 'parser_match.dart';
|
||||
import 'parser_pattern.dart';
|
||||
import 'pattern_iterator.dart';
|
||||
|
||||
@immutable
|
||||
class PatternIterable extends IterableBase<ParserMatch> {
|
||||
final ParserPattern pattern;
|
||||
final String input;
|
||||
final int start;
|
||||
|
||||
const PatternIterable(this.pattern, this.input, this.start);
|
||||
|
||||
@override
|
||||
Iterator<ParserMatch> get iterator =>
|
||||
PatternIterator(pattern, pattern.parser, input, start);
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'parser_match.dart';
|
||||
import 'parser_pattern.dart';
|
||||
|
||||
class PatternIterator extends Iterator<ParserMatch> {
|
||||
final ParserPattern pattern;
|
||||
final Parser parser;
|
||||
final String input;
|
||||
int start;
|
||||
|
||||
PatternIterator(this.pattern, this.parser, this.input, this.start);
|
||||
|
||||
@override
|
||||
late ParserMatch current;
|
||||
|
||||
@override
|
||||
bool moveNext() {
|
||||
while (start <= input.length) {
|
||||
final end = parser.fastParseOn(input, start);
|
||||
if (end < 0) {
|
||||
start++;
|
||||
} else {
|
||||
current = ParserMatch(pattern, input, start, end);
|
||||
if (start == end) {
|
||||
start++;
|
||||
} else {
|
||||
start = end;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
Copyright 2016, the Dart project authors. All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,390 @@
|
||||
// Copyright (c) 2016, 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.
|
||||
|
||||
/// Annotations that developers can use to express the intentions that otherwise
|
||||
/// can't be deduced by statically analyzing the source code.
|
||||
///
|
||||
/// See also `@deprecated` and `@override` in the `dart:core` library.
|
||||
///
|
||||
/// Annotations provide semantic information that tools can use to provide a
|
||||
/// better user experience. For example, an IDE might not autocomplete the name
|
||||
/// of a function that's been marked `@deprecated`, or it might display the
|
||||
/// function's name differently.
|
||||
///
|
||||
/// For information on installing and importing this library, see the [meta
|
||||
/// package on pub.dev](https://pub.dev/packages/meta). For examples of using
|
||||
/// annotations, see
|
||||
/// [Metadata](https://dart.dev/guides/language/language-tour#metadata) in the
|
||||
/// language tour.
|
||||
library meta;
|
||||
|
||||
import 'meta_meta.dart';
|
||||
|
||||
/// Used to annotate a function `f`. Indicates that `f` always throws an
|
||||
/// exception. Any functions that override `f`, in class inheritance, are also
|
||||
/// expected to conform to this contract.
|
||||
///
|
||||
/// Tools, such as the analyzer, can use this to understand whether a block of
|
||||
/// code "exits". For example:
|
||||
///
|
||||
/// ```dart
|
||||
/// @alwaysThrows toss() { throw 'Thrown'; }
|
||||
///
|
||||
/// int fn(bool b) {
|
||||
/// if (b) {
|
||||
/// return 0;
|
||||
/// } else {
|
||||
/// toss();
|
||||
/// print("Hello.");
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Without the annotation on `toss`, it would look as though `fn` doesn't
|
||||
/// always return a value. The annotation shows that `fn` does always exit. In
|
||||
/// addition, the annotation reveals that any statements following a call to
|
||||
/// `toss` (like the `print` call) are dead code.
|
||||
///
|
||||
/// Tools, such as the analyzer, can also expect this contract to be enforced;
|
||||
/// that is, tools may emit warnings if a function with this annotation
|
||||
/// _doesn't_ always throw.
|
||||
const _AlwaysThrows alwaysThrows = _AlwaysThrows();
|
||||
|
||||
/// Used to annotate a parameter of an instance method that overrides another
|
||||
/// method.
|
||||
///
|
||||
/// Indicates that this parameter may have a tighter type than the parameter on
|
||||
/// its superclass. The actual argument will be checked at runtime to ensure it
|
||||
/// is a subtype of the overridden parameter type.
|
||||
///
|
||||
@Deprecated('Use the `covariant` modifier instead')
|
||||
const _Checked checked = _Checked();
|
||||
|
||||
/// Used to annotate a method, getter or top-level getter or function to
|
||||
/// indicate that the value obtained by invoking it should not be stored in a
|
||||
/// field or top-level variable. The annotation can also be applied to a class
|
||||
/// to implicitly annotate all of the valid members of the class, or applied to
|
||||
/// a library to annotate all of the valid members of the library, including
|
||||
/// classes. If a value returned by an element marked as `doNotStore` is returned
|
||||
/// from a function or getter, that function or getter should be similarly
|
||||
/// annotated.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with anything other than a library, class,
|
||||
/// method or getter, top-level getter or function, or
|
||||
/// * an invocation of a member that has this annotation is returned by a method,
|
||||
/// getter or function that is not similarly annotated as `doNotStore`, or
|
||||
/// * an invocation of a member that has this annotation is assigned to a field
|
||||
/// or top-level variable.
|
||||
const _DoNotStore doNotStore = _DoNotStore();
|
||||
|
||||
/// Used to annotate a library, or any declaration that is part of the public
|
||||
/// interface of a library (such as top-level members, class members, and
|
||||
/// function parameters) to indicate that the annotated API is experimental and
|
||||
/// may be removed or changed at any-time without updating the version of the
|
||||
/// containing package, despite the fact that it would otherwise be a breaking
|
||||
/// change.
|
||||
///
|
||||
/// If the annotation is applied to a library then it is equivalent to applying
|
||||
/// the annotation to all of the top-level members of the library. Applying the
|
||||
/// annotation to a class does *not* apply the annotation to subclasses, but
|
||||
/// does apply the annotation to members of the class.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with a declaration that is not part of the
|
||||
/// public interface of a library (such as a local variable or a declaration
|
||||
/// that is private) or a directive other than the first directive in the
|
||||
/// library, or
|
||||
/// * the declaration is referenced by a package that has not explicitly
|
||||
/// indicated its intention to use experimental APIs (details TBD).
|
||||
const _Experimental experimental = _Experimental();
|
||||
|
||||
/// Used to annotate an instance or static method `m`. Indicates that `m` must
|
||||
/// either be abstract or must return a newly allocated object or `null`. In
|
||||
/// addition, every method that either implements or overrides `m` is implicitly
|
||||
/// annotated with this same annotation.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with anything other than a method, or
|
||||
/// * a method that has this annotation can return anything other than a newly
|
||||
/// allocated object or `null`.
|
||||
const _Factory factory = _Factory();
|
||||
|
||||
/// Used to annotate a class `C`. Indicates that `C` and all subtypes of `C`
|
||||
/// must be immutable.
|
||||
///
|
||||
/// A class is immutable if all of the instance fields of the class, whether
|
||||
/// defined directly or inherited, are `final`.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
/// * the annotation is associated with anything other than a class, or
|
||||
/// * a class that has this annotation or extends, implements or mixes in a
|
||||
/// class that has this annotation is not immutable.
|
||||
const Immutable immutable = Immutable();
|
||||
|
||||
/// Used to annotate a declaration which should only be used from within the
|
||||
/// package in which it is declared, and which should not be exposed from said
|
||||
/// package's public API.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the declaration is declared in a package's public API, or is exposed from
|
||||
/// a package's public API, or
|
||||
/// * the declaration is private, an unnamed extension, a static member of a
|
||||
/// private class, mixin, or extension, a value of a private enum, or a
|
||||
/// constructor of a private class, or
|
||||
/// * the declaration is referenced outside the package in which it is declared.
|
||||
const _Internal internal = _Internal();
|
||||
|
||||
/// Used to annotate a test framework function that runs a single test.
|
||||
///
|
||||
/// Tools, such as IDEs, can show invocations of such function in a file
|
||||
/// structure view to help the user navigating in large test files.
|
||||
///
|
||||
/// The first parameter of the function must be the description of the test.
|
||||
const _IsTest isTest = _IsTest();
|
||||
|
||||
/// Used to annotate a test framework function that runs a group of tests.
|
||||
///
|
||||
/// Tools, such as IDEs, can show invocations of such function in a file
|
||||
/// structure view to help the user navigating in large test files.
|
||||
///
|
||||
/// The first parameter of the function must be the description of the group.
|
||||
const _IsTestGroup isTestGroup = _IsTestGroup();
|
||||
|
||||
/// Used to annotate a const constructor `c`. Indicates that any invocation of
|
||||
/// the constructor must use the keyword `const` unless one or more of the
|
||||
/// arguments to the constructor is not a compile-time constant.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with anything other than a const constructor,
|
||||
/// or
|
||||
/// * an invocation of a constructor that has this annotation is not invoked
|
||||
/// using the `const` keyword unless one or more of the arguments to the
|
||||
/// constructor is not a compile-time constant.
|
||||
const _Literal literal = _Literal();
|
||||
|
||||
/// Used to annotate an instance method `m`. Indicates that every invocation of
|
||||
/// a method that overrides `m` must also invoke `m`. In addition, every method
|
||||
/// that overrides `m` is implicitly annotated with this same annotation.
|
||||
///
|
||||
/// Note that private methods with this annotation cannot be validly overridden
|
||||
/// outside of the library that defines the annotated method.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with anything other than an instance method,
|
||||
/// or
|
||||
/// * a method that overrides a method that has this annotation can return
|
||||
/// without invoking the overridden method.
|
||||
const _MustCallSuper mustCallSuper = _MustCallSuper();
|
||||
|
||||
/// Used to annotate an instance member (method, getter, setter, operator, or
|
||||
/// field) `m` in a class `C` or mixin `M`. Indicates that `m` should not be
|
||||
/// overridden in any classes that extend or mixin `C` or `M`.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with anything other than an instance member,
|
||||
/// * the annotation is associated with an abstract member (because subclasses
|
||||
/// are required to override the member),
|
||||
/// * the annotation is associated with an extension method,
|
||||
/// * the annotation is associated with a member `m` in class `C`, and there is
|
||||
/// a class `D` or mixin `M`, that extends or mixes in `C`, that declares an
|
||||
/// overriding member `m`.
|
||||
const _NonVirtual nonVirtual = _NonVirtual();
|
||||
|
||||
/// Used to annotate a class, mixin, or extension declaration `C`. Indicates
|
||||
/// that any type arguments declared on `C` are to be treated as optional.
|
||||
/// Tools such as the analyzer and linter can use this information to suppress
|
||||
/// warnings that would otherwise require type arguments on `C` to be provided.
|
||||
const _OptionalTypeArgs optionalTypeArgs = _OptionalTypeArgs();
|
||||
|
||||
/// Used to annotate an instance member (method, getter, setter, operator, or
|
||||
/// field) `m` in a class `C`. If the annotation is on a field it applies to the
|
||||
/// getter, and setter if appropriate, that are induced by the field. Indicates
|
||||
/// that `m` should only be invoked from instance methods of `C` or classes that
|
||||
/// extend, implement or mix in `C`, either directly or indirectly. Additionally
|
||||
/// indicates that `m` should only be invoked on `this`, whether explicitly or
|
||||
/// implicitly.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with anything other than an instance member,
|
||||
/// or
|
||||
/// * an invocation of a member that has this annotation is used outside of an
|
||||
/// instance member defined on a class that extends or mixes in (or a mixin
|
||||
/// constrained to) the class in which the protected member is defined.
|
||||
/// * an invocation of a member that has this annotation is used within an
|
||||
/// instance method, but the receiver is something other than `this`.
|
||||
const _Protected protected = _Protected();
|
||||
|
||||
/// Used to annotate a named parameter `p` in a method or function `f`.
|
||||
/// Indicates that every invocation of `f` must include an argument
|
||||
/// corresponding to `p`, despite the fact that `p` would otherwise be an
|
||||
/// optional parameter.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with anything other than a named parameter,
|
||||
/// * the annotation is associated with a named parameter in a method `m1` that
|
||||
/// overrides a method `m0` and `m0` defines a named parameter with the same
|
||||
/// name that does not have this annotation, or
|
||||
/// * an invocation of a method or function does not include an argument
|
||||
/// corresponding to a named parameter that has this annotation.
|
||||
const Required required = Required();
|
||||
|
||||
/// Annotation marking a class as not allowed as a super-type.
|
||||
///
|
||||
/// Classes in the same package as the marked class may extend, implement or
|
||||
/// mix-in the annotated class.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with anything other than a class,
|
||||
/// * the annotation is associated with a class `C`, and there is a class or
|
||||
/// mixin `D`, which extends, implements, mixes in, or constrains to `C`, and
|
||||
/// `C` and `D` are declared in different packages.
|
||||
const _Sealed sealed = _Sealed();
|
||||
|
||||
/// Used to annotate a field that is allowed to be overridden in Strong Mode.
|
||||
///
|
||||
/// Deprecated: Most of strong mode is now the default in 2.0, but the notion of
|
||||
/// virtual fields was dropped, so this annotation no longer has any meaning.
|
||||
/// Uses of the annotation should be removed.
|
||||
@Deprecated('No longer has meaning')
|
||||
const _Virtual virtual = _Virtual();
|
||||
|
||||
/// Used to annotate an instance member that was made public so that it could be
|
||||
/// overridden but that is not intended to be referenced from outside the
|
||||
/// defining library.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with a declaration other than a public
|
||||
/// instance member in a class or mixin, or
|
||||
/// * the member is referenced outside of the defining library.
|
||||
const _VisibleForOverriding visibleForOverriding = _VisibleForOverriding();
|
||||
|
||||
/// Used to annotate a declaration that was made public, so that it is more
|
||||
/// visible than otherwise necessary, to make code testable.
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with a declaration not in the `lib` folder
|
||||
/// of a package, or a private declaration, or a declaration in an unnamed
|
||||
/// static extension, or
|
||||
/// * the declaration is referenced outside of its defining library or a
|
||||
/// library which is in the `test` folder of the defining package.
|
||||
const _VisibleForTesting visibleForTesting = _VisibleForTesting();
|
||||
|
||||
/// Used to annotate a class.
|
||||
///
|
||||
/// See [immutable] for more details.
|
||||
class Immutable {
|
||||
/// A human-readable explanation of the reason why the class is immutable.
|
||||
final String reason;
|
||||
|
||||
/// Initialize a newly created instance to have the given [reason].
|
||||
const Immutable([this.reason = '']);
|
||||
}
|
||||
|
||||
/// Used to annotate a named parameter `p` in a method or function `f`.
|
||||
///
|
||||
/// See [required] for more details.
|
||||
class Required {
|
||||
/// A human-readable explanation of the reason why the annotated parameter is
|
||||
/// required. For example, the annotation might look like:
|
||||
///
|
||||
/// ButtonWidget({
|
||||
/// Function onHover,
|
||||
/// @Required('Buttons must do something when pressed')
|
||||
/// Function onPressed,
|
||||
/// ...
|
||||
/// }) ...
|
||||
final String reason;
|
||||
|
||||
/// Initialize a newly created instance to have the given [reason].
|
||||
const Required([this.reason = '']);
|
||||
}
|
||||
|
||||
class _AlwaysThrows {
|
||||
const _AlwaysThrows();
|
||||
}
|
||||
|
||||
class _Checked {
|
||||
const _Checked();
|
||||
}
|
||||
|
||||
@Target({
|
||||
TargetKind.classType,
|
||||
TargetKind.function,
|
||||
TargetKind.getter,
|
||||
TargetKind.library,
|
||||
TargetKind.method,
|
||||
})
|
||||
class _DoNotStore {
|
||||
const _DoNotStore();
|
||||
}
|
||||
|
||||
class _Experimental {
|
||||
const _Experimental();
|
||||
}
|
||||
|
||||
class _Factory {
|
||||
const _Factory();
|
||||
}
|
||||
|
||||
class _Internal {
|
||||
const _Internal();
|
||||
}
|
||||
|
||||
class _IsTest {
|
||||
const _IsTest();
|
||||
}
|
||||
|
||||
class _IsTestGroup {
|
||||
const _IsTestGroup();
|
||||
}
|
||||
|
||||
class _Literal {
|
||||
const _Literal();
|
||||
}
|
||||
|
||||
class _MustCallSuper {
|
||||
const _MustCallSuper();
|
||||
}
|
||||
|
||||
class _NonVirtual {
|
||||
const _NonVirtual();
|
||||
}
|
||||
|
||||
class _OptionalTypeArgs {
|
||||
const _OptionalTypeArgs();
|
||||
}
|
||||
|
||||
class _Protected {
|
||||
const _Protected();
|
||||
}
|
||||
|
||||
class _Sealed {
|
||||
const _Sealed();
|
||||
}
|
||||
|
||||
@Deprecated('No longer has meaning')
|
||||
class _Virtual {
|
||||
const _Virtual();
|
||||
}
|
||||
|
||||
class _VisibleForOverriding {
|
||||
const _VisibleForOverriding();
|
||||
}
|
||||
|
||||
class _VisibleForTesting {
|
||||
const _VisibleForTesting();
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2020, 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.
|
||||
|
||||
/// Annotations that describe the intended use of other annotations.
|
||||
library meta_meta;
|
||||
|
||||
/// An annotation used on classes that are intended to be used as annotations
|
||||
/// to indicate the kinds of declarations and directives for which the
|
||||
/// annotation is appropriate.
|
||||
///
|
||||
/// The kinds are represented by the constants defined in [TargetKind].
|
||||
///
|
||||
/// Tools, such as the analyzer, can provide feedback if
|
||||
///
|
||||
/// * the annotation is associated with anything other than a class, where the
|
||||
/// class must be usable as an annotation (that is, contain at least one
|
||||
/// `const` constructor).
|
||||
/// * the annotated annotation is associated with anything other than the kinds
|
||||
/// of declarations listed as valid targets.
|
||||
@Target({TargetKind.classType})
|
||||
class Target {
|
||||
/// The kinds of declarations with which the annotated annotation can be
|
||||
/// associated.
|
||||
final Set<TargetKind> kinds;
|
||||
|
||||
const Target(this.kinds);
|
||||
}
|
||||
|
||||
/// An enumeration of the kinds of targets to which an annotation can be
|
||||
/// applied.
|
||||
enum TargetKind {
|
||||
/// Indicates that an annotation is valid on any class declaration.
|
||||
classType,
|
||||
|
||||
/// Indicates that an annotation is valid on any enum declaration.
|
||||
enumType,
|
||||
|
||||
/// Indicates that an annotation is valid on any extension declaration.
|
||||
extension,
|
||||
|
||||
/// Indicates that an annotation is valid on any field declaration, both
|
||||
/// instance and static fields, whether it's in a class, mixin or extension.
|
||||
field,
|
||||
|
||||
/// Indicates that an annotation is valid on any top-level function
|
||||
/// declaration.
|
||||
function,
|
||||
|
||||
/// Indicates that an annotation is valid on the first directive in a library,
|
||||
/// whether that's a `library`, `import`, `export` or `part` directive. This
|
||||
/// doesn't include the `part of` directive in a part file.
|
||||
library,
|
||||
|
||||
/// Indicates that an annotation is valid on any getter declaration, both
|
||||
/// instance or static getters, whether it's in a class, mixin, extension, or
|
||||
/// at the top-level of a library.
|
||||
getter,
|
||||
|
||||
/// Indicates that an annotation is valid on any method declaration, both
|
||||
/// instance and static methods, whether it's in a class, mixin or extension.
|
||||
method,
|
||||
|
||||
/// Indicates that an annotation is valid on any mixin declaration.
|
||||
mixinType,
|
||||
|
||||
/// Indicates that an annotation is valid on any formal parameter declaration,
|
||||
/// whether it's in a function, method, constructor, or closure.
|
||||
parameter,
|
||||
|
||||
/// Indicates that an annotation is valid on any setter declaration, both
|
||||
/// instance or static setters, whether it's in a class, mixin, extension, or
|
||||
/// at the top-level of a library.
|
||||
setter,
|
||||
|
||||
/// Indicates that an annotation is valid on any declaration that introduces a
|
||||
/// type. This includes classes, enums, mixins and typedefs, but does not
|
||||
/// include extensions because extensions don't introduce a type.
|
||||
type,
|
||||
|
||||
/// Indicates that an annotation is valid on any typedef declaration.
|
||||
typedefType,
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../combinator/delegate.dart';
|
||||
|
||||
extension CastParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that casts itself to `Parser<R>`.
|
||||
Parser<R> cast<R>() => CastParser<R>(this);
|
||||
}
|
||||
|
||||
/// A parser that casts a `Result` to a `Result<R>`.
|
||||
class CastParser<R> extends DelegateParser<R> {
|
||||
CastParser(Parser delegate) : super(delegate);
|
||||
|
||||
@override
|
||||
Result<R> parseOn(Context context) {
|
||||
final result = delegate.parseOn(context);
|
||||
if (result.isSuccess) {
|
||||
return result.success(result.value);
|
||||
} else {
|
||||
return result.failure(result.message);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) =>
|
||||
delegate.fastParseOn(buffer, position);
|
||||
|
||||
@override
|
||||
CastParser<R> copy() => CastParser<R>(delegate);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../combinator/delegate.dart';
|
||||
|
||||
extension CastListParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that casts itself to `Parser<List<R>>`. Assumes this
|
||||
/// parser to be of type `Parser<List>`.
|
||||
Parser<List<R>> castList<R>() => CastListParser<R>(this);
|
||||
}
|
||||
|
||||
/// A parser that casts a `Result<List>` to a `Result<List<R>>`.
|
||||
class CastListParser<R> extends DelegateParser<List<R>> {
|
||||
CastListParser(Parser delegate) : super(delegate);
|
||||
|
||||
@override
|
||||
Result<List<R>> parseOn(Context context) {
|
||||
final result = delegate.parseOn(context);
|
||||
if (result.isSuccess) {
|
||||
return result.success(result.value.cast<R>());
|
||||
} else {
|
||||
return result.failure(result.message);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) =>
|
||||
delegate.fastParseOn(buffer, position);
|
||||
|
||||
@override
|
||||
CastListParser<R> copy() => CastListParser<R>(delegate);
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../../parser/combinator/delegate.dart';
|
||||
|
||||
/// Handler function for the [ContinuationParser].
|
||||
typedef ContinuationHandler<T> = Result<T> Function(
|
||||
ContinuationCallback<T> continuation, Context context);
|
||||
|
||||
/// Callback function for the [ContinuationHandler].
|
||||
typedef ContinuationCallback<T> = Result<T> Function(Context context);
|
||||
|
||||
extension ContinuationParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that when activated captures a continuation function
|
||||
/// and passes it together with the current context into the handler.
|
||||
///
|
||||
/// Handlers are not required to call the continuation, but can completely
|
||||
/// ignore it, call it multiple times, and/or store it away for later use.
|
||||
/// Similarly handlers can modify the current context and/or modify the
|
||||
/// returned result.
|
||||
///
|
||||
/// The following example shows a simple wrapper. Messages are printed before
|
||||
/// and after the `digit()` parser is activated:
|
||||
///
|
||||
/// final parser = digit().callCC((continuation, context) {
|
||||
/// print('Parser will be activated, the context is $context.');
|
||||
/// final result = continuation(context);
|
||||
/// print('Parser was activated, the result is $result.');
|
||||
/// return result;
|
||||
/// });
|
||||
Parser<T> callCC(ContinuationHandler<T> handler) =>
|
||||
ContinuationParser<T>(this, handler);
|
||||
}
|
||||
|
||||
/// Continuation parser that when activated captures a continuation function
|
||||
/// and passes it together with the current context into the handler.
|
||||
class ContinuationParser<T> extends DelegateParser<T> {
|
||||
final ContinuationHandler<T> handler;
|
||||
|
||||
ContinuationParser(Parser<T> delegate, this.handler) : super(delegate);
|
||||
|
||||
@override
|
||||
Result<T> parseOn(Context context) => handler(_parseDelegateOn, context);
|
||||
|
||||
Result<T> _parseDelegateOn(Context context) =>
|
||||
delegate.parseOn(context) as Result<T>;
|
||||
|
||||
@override
|
||||
ContinuationParser<T> copy() =>
|
||||
ContinuationParser<T>(delegate as Parser<T>, handler);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(ContinuationParser<T> other) =>
|
||||
super.hasEqualProperties(other) && handler == other.handler;
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../combinator/delegate.dart';
|
||||
|
||||
extension FlattenParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that discards the result of the receiver, and returns
|
||||
/// a sub-string of the consumed range in the string/list being parsed.
|
||||
///
|
||||
/// If a [message] is provided, the flatten parser can switch to a fast mode
|
||||
/// where error tracking within the receiver is suppressed and in case of a
|
||||
/// problem [message] is reported instead.
|
||||
///
|
||||
/// For example, the parser `letter().plus().flatten()` returns `'abc'`
|
||||
/// for the input `'abc'`. In contrast, the parser `letter().plus()` would
|
||||
/// return `['a', 'b', 'c']` for the same input instead.
|
||||
Parser<String> flatten([String? message]) => FlattenParser(this, message);
|
||||
}
|
||||
|
||||
/// A parser that answers a substring of the range its delegate
|
||||
/// parses.
|
||||
class FlattenParser extends DelegateParser<String> {
|
||||
FlattenParser(Parser delegate, [this.message]) : super(delegate);
|
||||
|
||||
final String? message;
|
||||
|
||||
@override
|
||||
Result<String> parseOn(Context context) {
|
||||
if (message == null) {
|
||||
final result = delegate.parseOn(context);
|
||||
if (result.isSuccess) {
|
||||
final output =
|
||||
context.buffer.substring(context.position, result.position);
|
||||
return result.success(output);
|
||||
}
|
||||
return result.failure(result.message);
|
||||
} else {
|
||||
// If we have a message we can switch to fast mode.
|
||||
final position = delegate.fastParseOn(context.buffer, context.position);
|
||||
if (position < 0) {
|
||||
return context.failure(message!);
|
||||
}
|
||||
final output = context.buffer.substring(context.position, position);
|
||||
return context.success(output, position);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
return delegate.fastParseOn(buffer, position);
|
||||
}
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(FlattenParser other) =>
|
||||
super.hasEqualProperties(other) && message == other.message;
|
||||
|
||||
@override
|
||||
FlattenParser copy() => FlattenParser(delegate, message);
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../combinator/delegate.dart';
|
||||
|
||||
/// Typed action callback.
|
||||
typedef MapCallback<T, R> = R Function(T value);
|
||||
|
||||
extension MapParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that evaluates a [callback] as the production action
|
||||
/// on success of the receiver.
|
||||
///
|
||||
/// By default we assume the [callback] to be side-effect free. Unless
|
||||
/// [hasSideEffects] is set to `true`, the execution might be skipped if there
|
||||
/// are no direct dependencies.
|
||||
///
|
||||
/// For example, the parser `digit().map((char) => int.parse(char))` returns
|
||||
/// the number `1` for the input string `'1'`. If the delegate fails, the
|
||||
/// production action is not executed and the failure is passed on.
|
||||
Parser<R> map<R>(MapCallback<T, R> callback, {bool hasSideEffects = false}) =>
|
||||
MapParser<T, R>(this, callback, hasSideEffects);
|
||||
}
|
||||
|
||||
/// A parser that performs a transformation with a given function on the
|
||||
/// successful parse result of the delegate.
|
||||
class MapParser<T, R> extends DelegateParser<R> {
|
||||
final MapCallback<T, R> callback;
|
||||
final bool hasSideEffects;
|
||||
|
||||
MapParser(Parser<T> delegate, this.callback, [this.hasSideEffects = false])
|
||||
: super(delegate);
|
||||
|
||||
@override
|
||||
Result<R> parseOn(Context context) {
|
||||
final result = delegate.parseOn(context);
|
||||
if (result.isSuccess) {
|
||||
return result.success(callback(result.value));
|
||||
} else {
|
||||
return result.failure(result.message);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
// If we know to have side-effects, we have to fall back to the slow mode.
|
||||
return hasSideEffects
|
||||
? super.fastParseOn(buffer, position)
|
||||
: delegate.fastParseOn(buffer, position);
|
||||
}
|
||||
|
||||
@override
|
||||
MapParser<T, R> copy() =>
|
||||
MapParser<T, R>(delegate as Parser<T>, callback, hasSideEffects);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(MapParser<T, R> other) =>
|
||||
super.hasEqualProperties(other) &&
|
||||
callback == other.callback &&
|
||||
hasSideEffects == other.hasSideEffects;
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../combinator/delegate.dart';
|
||||
|
||||
extension PermuteParserExtension on Parser<List> {
|
||||
/// Returns a parser that transforms a successful parse result by returning
|
||||
/// the permuted elements at [indexes] of a list. Negative indexes can be
|
||||
/// used to access the elements from the back of the list.
|
||||
///
|
||||
/// For example, the parser `letter().star().permute([0, -1])` returns the
|
||||
/// first and last letter parsed. For the input `'abc'` it returns
|
||||
/// `['a', 'c']`.
|
||||
Parser<List<T>> permute<T>(List<int> indexes) =>
|
||||
PermuteParser<T>(this, indexes);
|
||||
}
|
||||
|
||||
/// A parser that performs a transformation with a given function on the
|
||||
/// successful parse result of the delegate.
|
||||
class PermuteParser<T> extends DelegateParser<List<T>> {
|
||||
final List<int> indexes;
|
||||
|
||||
PermuteParser(Parser<List> delegate, this.indexes) : super(delegate);
|
||||
|
||||
@override
|
||||
Result<List<T>> parseOn(Context context) {
|
||||
final result = delegate.parseOn(context);
|
||||
if (result.isSuccess) {
|
||||
final value = result.value;
|
||||
final values = indexes
|
||||
.map((index) => value[index < 0 ? value.length + index : index])
|
||||
.cast<T>()
|
||||
.toList(growable: false);
|
||||
return result.success(values);
|
||||
} else {
|
||||
return result.failure(result.message);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) =>
|
||||
delegate.fastParseOn(buffer, position);
|
||||
|
||||
@override
|
||||
PermuteParser<T> copy() =>
|
||||
PermuteParser<T>(delegate as Parser<List>, indexes);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(PermuteParser<T> other) =>
|
||||
super.hasEqualProperties(other) && indexes == other.indexes;
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../combinator/delegate.dart';
|
||||
|
||||
extension PickParserExtension on Parser<List> {
|
||||
/// Returns a parser that transforms a successful parse result by returning
|
||||
/// the element at [index] of a list. A negative index can be used to access
|
||||
/// the elements from the back of the list.
|
||||
///
|
||||
/// For example, the parser `letter().star().pick(-1)` returns the last
|
||||
/// letter parsed. For the input `'abc'` it returns `'c'`.
|
||||
Parser<T> pick<T>(int index) => PickParser<T>(this, index);
|
||||
}
|
||||
|
||||
/// A parser that performs a transformation with a given function on the
|
||||
/// successful parse result of the delegate.
|
||||
class PickParser<T> extends DelegateParser<T> {
|
||||
final int index;
|
||||
|
||||
PickParser(Parser<List> delegate, this.index) : super(delegate);
|
||||
|
||||
@override
|
||||
Result<T> parseOn(Context context) {
|
||||
final result = delegate.parseOn(context);
|
||||
if (result.isSuccess) {
|
||||
final value = result.value;
|
||||
final picked = value[index < 0 ? value.length + index : index];
|
||||
return result.success(picked);
|
||||
} else {
|
||||
return result.failure(result.message);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) =>
|
||||
delegate.fastParseOn(buffer, position);
|
||||
|
||||
@override
|
||||
PickParser<T> copy() => PickParser<T>(delegate as Parser<List>, index);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(PickParser<T> other) =>
|
||||
super.hasEqualProperties(other) && index == other.index;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../../core/token.dart';
|
||||
import '../combinator/delegate.dart';
|
||||
|
||||
extension TokenParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that returns a [Token]. The token carries the parsed
|
||||
/// value of the receiver [Token.value], as well as the consumed input
|
||||
/// [Token.input] from [Token.start] to [Token.stop] of the input being
|
||||
/// parsed.
|
||||
///
|
||||
/// For example, the parser `letter().plus().token()` returns the token
|
||||
/// `Token[start: 0, stop: 3, value: abc]` for the input `'abc'`.
|
||||
Parser<Token<T>> token() => TokenParser<T>(this);
|
||||
}
|
||||
|
||||
/// A parser that answers a token of the result its delegate parses.
|
||||
class TokenParser<T> extends DelegateParser<Token<T>> {
|
||||
TokenParser(Parser delegate) : super(delegate);
|
||||
|
||||
@override
|
||||
Result<Token<T>> parseOn(Context context) {
|
||||
final result = delegate.parseOn(context);
|
||||
if (result.isSuccess) {
|
||||
final token = Token<T>(
|
||||
result.value, context.buffer, context.position, result.position);
|
||||
return result.success(token);
|
||||
} else {
|
||||
return result.failure(result.message);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) =>
|
||||
delegate.fastParseOn(buffer, position);
|
||||
|
||||
@override
|
||||
TokenParser<T> copy() => TokenParser<T>(delegate);
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../character/whitespace.dart';
|
||||
import '../combinator/delegate.dart';
|
||||
|
||||
extension TrimmingParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that consumes input before and after the receiver,
|
||||
/// discards the excess input and only returns returns the result of the
|
||||
/// receiver. The optional arguments are parsers that consume the excess
|
||||
/// input. By default `whitespace()` is used. Up to two arguments can be
|
||||
/// provided to have different parsers on the [left] and [right] side.
|
||||
///
|
||||
/// For example, the parser `letter().plus().trim()` returns `['a', 'b']`
|
||||
/// for the input `' ab\n'` and consumes the complete input string.
|
||||
Parser<T> trim([Parser? left, Parser? right]) =>
|
||||
TrimmingParser<T>(this, left ??= whitespace(), right ??= left);
|
||||
}
|
||||
|
||||
/// A parser that silently consumes input of another parser around
|
||||
/// its delegate.
|
||||
class TrimmingParser<T> extends DelegateParser<T> {
|
||||
Parser left;
|
||||
Parser right;
|
||||
|
||||
TrimmingParser(Parser<T> delegate, this.left, this.right) : super(delegate);
|
||||
|
||||
@override
|
||||
Result<T> parseOn(Context context) {
|
||||
final buffer = context.buffer;
|
||||
|
||||
// Trim the left part:
|
||||
final before = trim_(left, buffer, context.position);
|
||||
if (before != context.position) {
|
||||
context = Context(buffer, before);
|
||||
}
|
||||
|
||||
// Consume the delegate:
|
||||
final result = delegate.parseOn(context) as Result<T>;
|
||||
if (result.isFailure) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Trim the right part:
|
||||
final after = trim_(right, buffer, result.position);
|
||||
return after == result.position
|
||||
? result
|
||||
: result.success(result.value, after);
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
final result = delegate.fastParseOn(buffer, trim_(left, buffer, position));
|
||||
return result < 0 ? -1 : trim_(right, buffer, result);
|
||||
}
|
||||
|
||||
int trim_(Parser parser, String buffer, int position) {
|
||||
for (;;) {
|
||||
final result = parser.fastParseOn(buffer, position);
|
||||
if (result < 0) {
|
||||
return position;
|
||||
}
|
||||
position = result;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TrimmingParser<T> copy() =>
|
||||
TrimmingParser<T>(delegate as Parser<T>, left, right);
|
||||
|
||||
@override
|
||||
List<Parser> get children => [delegate, left, right];
|
||||
|
||||
@override
|
||||
void replace(Parser source, Parser target) {
|
||||
super.replace(source, target);
|
||||
if (left == source) {
|
||||
left = target;
|
||||
}
|
||||
if (right == source) {
|
||||
right = target;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'code.dart';
|
||||
import 'optimize.dart';
|
||||
import 'parser.dart';
|
||||
|
||||
/// Returns a parser that accepts any of the specified characters.
|
||||
Parser<String> anyOf(String chars, [String? message]) {
|
||||
return CharacterParser(optimizedString(chars),
|
||||
message ?? 'any of "${toReadableString(chars)}" expected');
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'code.dart';
|
||||
import 'parser.dart';
|
||||
import 'predicate.dart';
|
||||
|
||||
/// Returns a parser that accepts a specific character only.
|
||||
Parser<String> char(Object char, [String? message]) {
|
||||
return CharacterParser(SingleCharPredicate(toCharCode(char)),
|
||||
message ?? '"${toReadableString(char)}" expected');
|
||||
}
|
||||
|
||||
class SingleCharPredicate extends CharacterPredicate {
|
||||
final int value;
|
||||
|
||||
const SingleCharPredicate(this.value);
|
||||
|
||||
@override
|
||||
bool test(int value) => identical(this.value, value);
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) =>
|
||||
other is SingleCharPredicate && other.value == value;
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/// Converts an object to a character code.
|
||||
int toCharCode(Object element) {
|
||||
if (element is num) {
|
||||
return element.round();
|
||||
}
|
||||
final value = element.toString();
|
||||
if (value.length != 1) {
|
||||
throw ArgumentError('"$value" is not a character');
|
||||
}
|
||||
return value.codeUnitAt(0);
|
||||
}
|
||||
|
||||
/// Converts a character to a readable string.
|
||||
String toReadableString(Object element) => element is String
|
||||
? _toFormattedString(element)
|
||||
: _toFormattedChar(toCharCode(element));
|
||||
|
||||
String _toFormattedString(String input) =>
|
||||
input.codeUnits.map(_toFormattedChar).join();
|
||||
|
||||
String _toFormattedChar(int code) {
|
||||
switch (code) {
|
||||
case 0x08:
|
||||
return '\\b'; // backspace
|
||||
case 0x09:
|
||||
return '\\t'; // horizontal tab
|
||||
case 0x0A:
|
||||
return '\\n'; // new line
|
||||
case 0x0B:
|
||||
return '\\v'; // vertical tab
|
||||
case 0x0C:
|
||||
return '\\f'; // form feed
|
||||
case 0x0D:
|
||||
return '\\r'; // carriage return
|
||||
case 0x22:
|
||||
return '\\"'; // double quote
|
||||
case 0x27:
|
||||
return "\\'"; // single quote
|
||||
case 0x5C:
|
||||
return '\\\\'; // backslash
|
||||
}
|
||||
if (code < 0x20) {
|
||||
return '\\x${code.toRadixString(16).padLeft(2, '0')}';
|
||||
}
|
||||
return String.fromCharCode(code);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import 'predicate.dart';
|
||||
|
||||
class ConstantCharPredicate extends CharacterPredicate {
|
||||
final bool constant;
|
||||
|
||||
const ConstantCharPredicate(this.constant);
|
||||
|
||||
@override
|
||||
bool test(int value) => constant;
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) =>
|
||||
other is ConstantCharPredicate && other.constant == constant;
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'parser.dart';
|
||||
import 'predicate.dart';
|
||||
|
||||
/// Returns a parser that accepts any digit character.
|
||||
Parser<String> digit([String message = 'digit expected']) {
|
||||
return CharacterParser(const DigitCharPredicate(), message);
|
||||
}
|
||||
|
||||
class DigitCharPredicate extends CharacterPredicate {
|
||||
const DigitCharPredicate();
|
||||
|
||||
@override
|
||||
bool test(int value) => 48 <= value && value <= 57;
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) => other is DigitCharPredicate;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'parser.dart';
|
||||
import 'predicate.dart';
|
||||
|
||||
/// Returns a parser that accepts any letter character.
|
||||
Parser<String> letter([String message = 'letter expected']) {
|
||||
return CharacterParser(const LetterCharPredicate(), message);
|
||||
}
|
||||
|
||||
class LetterCharPredicate extends CharacterPredicate {
|
||||
const LetterCharPredicate();
|
||||
|
||||
@override
|
||||
bool test(int value) =>
|
||||
(65 <= value && value <= 90) || (97 <= value && value <= 122);
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) => other is LetterCharPredicate;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'predicate.dart';
|
||||
import 'range.dart';
|
||||
|
||||
class LookupCharPredicate implements CharacterPredicate {
|
||||
final int start;
|
||||
final int stop;
|
||||
final Uint32List bits;
|
||||
|
||||
LookupCharPredicate(List<RangeCharPredicate> ranges)
|
||||
: start = ranges.first.start,
|
||||
stop = ranges.last.stop,
|
||||
bits = Uint32List(
|
||||
(ranges.last.stop - ranges.first.start + 1 + offset) >> shift) {
|
||||
for (final range in ranges) {
|
||||
for (var index = range.start - start;
|
||||
index <= range.stop - start;
|
||||
index++) {
|
||||
bits[index >> shift] |= mask[index & offset];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool test(int value) =>
|
||||
start <= value && value <= stop && _testBit(value - start);
|
||||
|
||||
bool _testBit(int value) =>
|
||||
(bits[value >> shift] & mask[value & offset]) != 0;
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) =>
|
||||
other is LookupCharPredicate &&
|
||||
other.start == start &&
|
||||
other.stop == stop &&
|
||||
other.bits == bits;
|
||||
|
||||
static const int shift = 5;
|
||||
static const int offset = 31;
|
||||
static const List<int> mask = [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8,
|
||||
16,
|
||||
32,
|
||||
64,
|
||||
128,
|
||||
256,
|
||||
512,
|
||||
1024,
|
||||
2048,
|
||||
4096,
|
||||
8192,
|
||||
16384,
|
||||
32768,
|
||||
65536,
|
||||
131072,
|
||||
262144,
|
||||
524288,
|
||||
1048576,
|
||||
2097152,
|
||||
4194304,
|
||||
8388608,
|
||||
16777216,
|
||||
33554432,
|
||||
67108864,
|
||||
134217728,
|
||||
268435456,
|
||||
536870912,
|
||||
1073741824,
|
||||
2147483648,
|
||||
];
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'parser.dart';
|
||||
import 'predicate.dart';
|
||||
|
||||
/// Returns a parser that accepts any lowercase character.
|
||||
Parser<String> lowercase([String message = 'lowercase letter expected']) {
|
||||
return CharacterParser(const LowercaseCharPredicate(), message);
|
||||
}
|
||||
|
||||
class LowercaseCharPredicate extends CharacterPredicate {
|
||||
const LowercaseCharPredicate();
|
||||
|
||||
@override
|
||||
bool test(int value) => 97 <= value && value <= 122;
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) => other is LowercaseCharPredicate;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'code.dart';
|
||||
import 'not.dart';
|
||||
import 'optimize.dart';
|
||||
import 'parser.dart';
|
||||
|
||||
/// Returns a parser that accepts none of the specified characters.
|
||||
Parser<String> noneOf(String chars, [String? message]) {
|
||||
return CharacterParser(NotCharacterPredicate(optimizedString(chars)),
|
||||
message ?? 'none of "${toReadableString(chars)}" expected');
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import 'predicate.dart';
|
||||
|
||||
/// Negates the result of a character predicate.
|
||||
class NotCharacterPredicate extends CharacterPredicate {
|
||||
final CharacterPredicate predicate;
|
||||
|
||||
const NotCharacterPredicate(this.predicate);
|
||||
|
||||
@override
|
||||
bool test(int value) => !predicate.test(value);
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) =>
|
||||
other is NotCharacterPredicate &&
|
||||
other.predicate.isEqualTo(other.predicate);
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
import 'char.dart';
|
||||
import 'constant.dart';
|
||||
import 'lookup.dart';
|
||||
import 'predicate.dart';
|
||||
import 'range.dart';
|
||||
|
||||
/// Creates an optimized character from a string.
|
||||
CharacterPredicate optimizedString(String string) {
|
||||
return optimizedRanges(
|
||||
string.codeUnits.map((value) => RangeCharPredicate(value, value)));
|
||||
}
|
||||
|
||||
/// Creates an optimized predicate from a list of range predicates.
|
||||
CharacterPredicate optimizedRanges(Iterable<RangeCharPredicate> ranges) {
|
||||
// 1. Sort the ranges:
|
||||
final sortedRanges = List.of(ranges, growable: false);
|
||||
sortedRanges.sort((first, second) {
|
||||
return first.start != second.start
|
||||
? first.start - second.start
|
||||
: first.stop - second.stop;
|
||||
});
|
||||
|
||||
// 2. Merge adjacent or overlapping ranges:
|
||||
final mergedRanges = <RangeCharPredicate>[];
|
||||
for (final thisRange in sortedRanges) {
|
||||
if (mergedRanges.isEmpty) {
|
||||
mergedRanges.add(thisRange);
|
||||
} else {
|
||||
final lastRange = mergedRanges.last;
|
||||
if (lastRange.stop + 1 >= thisRange.start) {
|
||||
final characterRange =
|
||||
RangeCharPredicate(lastRange.start, thisRange.stop);
|
||||
mergedRanges[mergedRanges.length - 1] = characterRange;
|
||||
} else {
|
||||
mergedRanges.add(thisRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Build the best resulting predicate:
|
||||
final matchingCount = mergedRanges.fold<int>(
|
||||
0, (current, range) => current + (range.stop - range.start + 1));
|
||||
if (matchingCount == 0) {
|
||||
return const ConstantCharPredicate(false);
|
||||
} else if (matchingCount - 1 == 0xffff) {
|
||||
return const ConstantCharPredicate(true);
|
||||
} else if (mergedRanges.length == 1) {
|
||||
return mergedRanges[0].start == mergedRanges[0].stop
|
||||
? SingleCharPredicate(mergedRanges[0].start)
|
||||
: mergedRanges[0];
|
||||
} else {
|
||||
return LookupCharPredicate(mergedRanges);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import 'predicate.dart';
|
||||
|
||||
/// Parser class for individual character classes.
|
||||
class CharacterParser extends Parser<String> {
|
||||
final CharacterPredicate predicate;
|
||||
|
||||
final String message;
|
||||
|
||||
CharacterParser(this.predicate, this.message);
|
||||
|
||||
@override
|
||||
Result<String> parseOn(Context context) {
|
||||
final buffer = context.buffer;
|
||||
final position = context.position;
|
||||
if (position < buffer.length &&
|
||||
predicate.test(buffer.codeUnitAt(position))) {
|
||||
return context.success(buffer[position], position + 1);
|
||||
}
|
||||
return context.failure(message);
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) =>
|
||||
position < buffer.length && predicate.test(buffer.codeUnitAt(position))
|
||||
? position + 1
|
||||
: -1;
|
||||
|
||||
@override
|
||||
String toString() => '${super.toString()}[$message]';
|
||||
|
||||
@override
|
||||
CharacterParser copy() => CharacterParser(predicate, message);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(CharacterParser other) =>
|
||||
super.hasEqualProperties(other) &&
|
||||
predicate.isEqualTo(other.predicate) &&
|
||||
message == other.message;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import '../../core/parser.dart';
|
||||
import '../action/map.dart';
|
||||
import '../combinator/choice.dart';
|
||||
import '../combinator/optional.dart';
|
||||
import '../combinator/sequence.dart';
|
||||
import '../predicate/any.dart';
|
||||
import '../repeater/possessive.dart';
|
||||
import 'char.dart';
|
||||
import 'code.dart';
|
||||
import 'not.dart';
|
||||
import 'optimize.dart';
|
||||
import 'parser.dart';
|
||||
import 'predicate.dart';
|
||||
import 'range.dart';
|
||||
|
||||
/// Returns a parser that accepts a single character of a given character set
|
||||
/// provided as a string.
|
||||
///
|
||||
/// Characters match themselves. A dash `-` between two characters matches the
|
||||
/// range of those characters. A caret `^` at the beginning negates the pattern.
|
||||
///
|
||||
/// For example, the parser `pattern('aou')` accepts the character 'a', 'o', or
|
||||
/// 'u', and fails for any other input. The parser `pattern('1-3')` accepts
|
||||
/// either '1', '2', or '3'; and fails for any other character. The parser
|
||||
/// `pattern('^aou') accepts any character, but fails for the characters 'a',
|
||||
/// 'o', or 'u'.
|
||||
Parser<String> pattern(String element, [String? message]) {
|
||||
return CharacterParser(pattern_.parse(element).value,
|
||||
message ?? '[${toReadableString(element)}] expected');
|
||||
}
|
||||
|
||||
/// Returns a parser that accepts a single character of a given case-insensitive
|
||||
/// character set provided as a string.
|
||||
///
|
||||
/// Characters match themselves. A dash `-` between two characters matches the
|
||||
/// range of those characters. A caret `^` at the beginning negates the pattern.
|
||||
///
|
||||
/// For example, the parser `patternIgnoreCase('aoU')` accepts the character
|
||||
/// 'a', 'o', 'u' and 'A', 'O', 'U', and fails for any other input. The parser
|
||||
/// `patternIgnoreCase('a-c')` accepts 'a', 'b', 'c' and 'A', 'B', 'C'; and
|
||||
/// fails for any other character. The parser `patternIgnoreCase('^A') accepts
|
||||
/// any character, but fails for the characters 'a' or 'A'.
|
||||
Parser<String> patternIgnoreCase(String element, [String? message]) {
|
||||
final isNegated = element.startsWith('^');
|
||||
final value = isNegated ? element.substring(1) : element;
|
||||
return pattern(
|
||||
'${isNegated ? '^' : ''}${value.toLowerCase()}${value.toUpperCase()}',
|
||||
message);
|
||||
}
|
||||
|
||||
/// Parser that reads a single character.
|
||||
final Parser<RangeCharPredicate> single_ =
|
||||
any().map((element) => RangeCharPredicate(
|
||||
toCharCode(element),
|
||||
toCharCode(element),
|
||||
));
|
||||
|
||||
/// Parser that reads a character range.
|
||||
final Parser<RangeCharPredicate> range_ =
|
||||
any().seq(char('-')).seq(any()).map((elements) => RangeCharPredicate(
|
||||
toCharCode(elements[0]),
|
||||
toCharCode(elements[2]),
|
||||
));
|
||||
|
||||
/// Parser that reads a sequence of single characters or ranges.
|
||||
final Parser<CharacterPredicate> sequence_ = range_.or(single_).star().map(
|
||||
(predicates) => optimizedRanges(predicates.cast<RangeCharPredicate>()));
|
||||
|
||||
/// Parser that reads a possibly negated sequence of predicates.
|
||||
final Parser<CharacterPredicate> pattern_ = char('^')
|
||||
.optional()
|
||||
.seq(sequence_)
|
||||
.map((predicates) => predicates[0] == null
|
||||
? predicates[1]
|
||||
: NotCharacterPredicate(predicates[1]));
|
@ -0,0 +1,13 @@
|
||||
import '../../meta/lib/meta.dart';
|
||||
|
||||
/// Abstract character predicate class.
|
||||
@immutable
|
||||
abstract class CharacterPredicate {
|
||||
const CharacterPredicate();
|
||||
|
||||
/// Tests if the character predicate is satisfied.
|
||||
bool test(int value);
|
||||
|
||||
/// Compares the two predicates for equality.
|
||||
bool isEqualTo(CharacterPredicate other);
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'code.dart';
|
||||
import 'parser.dart';
|
||||
import 'predicate.dart';
|
||||
|
||||
/// Returns a parser that accepts any character in the range
|
||||
/// between [start] and [stop].
|
||||
Parser<String> range(Object start, Object stop, [String? message]) {
|
||||
return CharacterParser(
|
||||
RangeCharPredicate(toCharCode(start), toCharCode(stop)),
|
||||
message ??
|
||||
'${toReadableString(start)}..${toReadableString(stop)} expected');
|
||||
}
|
||||
|
||||
class RangeCharPredicate implements CharacterPredicate {
|
||||
final int start;
|
||||
final int stop;
|
||||
|
||||
RangeCharPredicate(this.start, this.stop) {
|
||||
if (start > stop) {
|
||||
throw ArgumentError('Invalid range: $start-$stop');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool test(int value) => start <= value && value <= stop;
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) =>
|
||||
other is RangeCharPredicate && other.start == start && other.stop == stop;
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import 'predicate.dart';
|
||||
|
||||
class RangesCharPredicate implements CharacterPredicate {
|
||||
final int length;
|
||||
final List<int> starts;
|
||||
final List<int> stops;
|
||||
|
||||
const RangesCharPredicate(this.length, this.starts, this.stops);
|
||||
|
||||
@override
|
||||
bool test(int value) {
|
||||
var min = 0;
|
||||
var max = length;
|
||||
while (min < max) {
|
||||
final mid = min + ((max - min) >> 1);
|
||||
final comp = starts[mid] - value;
|
||||
if (comp == 0) {
|
||||
return true;
|
||||
} else if (comp < 0) {
|
||||
min = mid + 1;
|
||||
} else {
|
||||
max = mid;
|
||||
}
|
||||
}
|
||||
return 0 < min && value <= stops[min - 1];
|
||||
}
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) =>
|
||||
other is RangesCharPredicate &&
|
||||
other.length == length &&
|
||||
other.starts == starts &&
|
||||
other.stops == stops;
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'parser.dart';
|
||||
import 'predicate.dart';
|
||||
|
||||
/// Returns a parser that accepts any uppercase character.
|
||||
Parser<String> uppercase([String message = 'uppercase letter expected']) {
|
||||
return CharacterParser(const UppercaseCharPredicate(), message);
|
||||
}
|
||||
|
||||
class UppercaseCharPredicate implements CharacterPredicate {
|
||||
const UppercaseCharPredicate();
|
||||
|
||||
@override
|
||||
bool test(int value) => 65 <= value && value <= 90;
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) => other is UppercaseCharPredicate;
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'parser.dart';
|
||||
import 'predicate.dart';
|
||||
|
||||
/// Returns a parser that accepts any whitespace character.
|
||||
Parser<String> whitespace([String message = 'whitespace expected']) {
|
||||
return CharacterParser(const WhitespaceCharPredicate(), message);
|
||||
}
|
||||
|
||||
class WhitespaceCharPredicate implements CharacterPredicate {
|
||||
const WhitespaceCharPredicate();
|
||||
|
||||
@override
|
||||
bool test(int value) {
|
||||
if (value < 256) {
|
||||
switch (value) {
|
||||
case 9:
|
||||
case 10:
|
||||
case 11:
|
||||
case 12:
|
||||
case 13:
|
||||
case 32:
|
||||
case 133:
|
||||
case 160:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
switch (value) {
|
||||
case 5760:
|
||||
case 8192:
|
||||
case 8193:
|
||||
case 8194:
|
||||
case 8195:
|
||||
case 8196:
|
||||
case 8197:
|
||||
case 8198:
|
||||
case 8199:
|
||||
case 8200:
|
||||
case 8201:
|
||||
case 8202:
|
||||
case 8232:
|
||||
case 8233:
|
||||
case 8239:
|
||||
case 8287:
|
||||
case 12288:
|
||||
case 65279:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) => other is WhitespaceCharPredicate;
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'parser.dart';
|
||||
import 'predicate.dart';
|
||||
|
||||
/// Returns a parser that accepts any word character.
|
||||
Parser<String> word([String message = 'letter or digit expected']) {
|
||||
return CharacterParser(const WordCharPredicate(), message);
|
||||
}
|
||||
|
||||
class WordCharPredicate implements CharacterPredicate {
|
||||
const WordCharPredicate();
|
||||
|
||||
@override
|
||||
bool test(int value) =>
|
||||
(65 <= value && value <= 90) ||
|
||||
(97 <= value && value <= 122) ||
|
||||
(48 <= value && value <= 57) ||
|
||||
identical(value, 95);
|
||||
|
||||
@override
|
||||
bool isEqualTo(CharacterPredicate other) => other is WordCharPredicate;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import 'delegate.dart';
|
||||
|
||||
extension AndParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser (logical and-predicate) that succeeds whenever the
|
||||
/// receiver does, but never consumes input.
|
||||
///
|
||||
/// For example, the parser `char('_').and().seq(identifier)` accepts
|
||||
/// identifiers that start with an underscore character. Since the predicate
|
||||
/// does not consume accepted input, the parser `identifier` is given the
|
||||
/// ability to process the complete identifier.
|
||||
Parser<T> and() => AndParser<T>(this);
|
||||
}
|
||||
|
||||
/// The and-predicate, a parser that succeeds whenever its delegate does, but
|
||||
/// does not consume the input stream [Parr 1994, 1995].
|
||||
class AndParser<T> extends DelegateParser<T> {
|
||||
AndParser(Parser delegate) : super(delegate);
|
||||
|
||||
@override
|
||||
Result<T> parseOn(Context context) {
|
||||
final result = delegate.parseOn(context);
|
||||
if (result.isSuccess) {
|
||||
return context.success(result.value);
|
||||
} else {
|
||||
return result as Result<T>;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
final result = delegate.fastParseOn(buffer, position);
|
||||
return result < 0 ? -1 : position;
|
||||
}
|
||||
|
||||
@override
|
||||
AndParser<T> copy() => AndParser<T>(delegate);
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import 'list.dart';
|
||||
|
||||
extension ChoiceParserExtension on Parser {
|
||||
/// Returns a parser that accepts the receiver or [other]. The resulting
|
||||
/// parser returns the parse result of the receiver, if the receiver fails
|
||||
/// it returns the parse result of [other] (exclusive ordered choice).
|
||||
///
|
||||
/// For example, the parser `letter().or(digit())` accepts a letter or a
|
||||
/// digit. An example where the order matters is the following choice between
|
||||
/// overlapping parsers: `letter().or(char('a'))`. In the example the parser
|
||||
/// `char('a')` will never be activated, because the input is always consumed
|
||||
/// `letter()`. This can be problematic if the author intended to attach a
|
||||
/// production action to `char('a')`.
|
||||
Parser or(Parser other) => this is ChoiceParser
|
||||
? ChoiceParser([...children, other])
|
||||
: ChoiceParser([this, other]);
|
||||
|
||||
/// Convenience operator returning a parser that accepts the receiver or
|
||||
/// [other]. See [or] for details.
|
||||
Parser operator |(Parser other) => or(other);
|
||||
}
|
||||
|
||||
extension ChoiceIterableExtension<T> on Iterable<Parser<T>> {
|
||||
/// Converts the parser in this iterable to a choice of parsers.
|
||||
Parser<T> toChoiceParser() => ChoiceParser<T>(this);
|
||||
}
|
||||
|
||||
/// A parser that uses the first parser that succeeds.
|
||||
class ChoiceParser<T> extends ListParser<T> {
|
||||
ChoiceParser(Iterable<Parser<T>> children) : super(children) {
|
||||
if (children.isEmpty) {
|
||||
throw ArgumentError('Choice parser cannot be empty.');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Result<T> parseOn(Context context) {
|
||||
Result? result;
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
result = children[i].parseOn(context);
|
||||
if (result.isSuccess) {
|
||||
return result as Result<T>;
|
||||
}
|
||||
}
|
||||
return result as Result<T>;
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
var result = -1;
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
result = children[i].fastParseOn(buffer, position);
|
||||
if (result >= 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
ChoiceParser<T> copy() => ChoiceParser<T>(children as List<Parser<T>>);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import '../../core/parser.dart';
|
||||
|
||||
/// An abstract parser that delegates to another one.
|
||||
abstract class DelegateParser<T> extends Parser<T> {
|
||||
Parser delegate;
|
||||
|
||||
DelegateParser(this.delegate);
|
||||
|
||||
@override
|
||||
List<Parser> get children => [delegate];
|
||||
|
||||
@override
|
||||
void replace(Parser source, Parser target) {
|
||||
super.replace(source, target);
|
||||
if (delegate == source) {
|
||||
delegate = target;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import '../../core/parser.dart';
|
||||
|
||||
/// Abstract parser that parses a list of things in some way.
|
||||
abstract class ListParser<T> extends Parser<T> {
|
||||
ListParser(Iterable<Parser> children)
|
||||
: children = List.of(children, growable: false);
|
||||
|
||||
@override
|
||||
final List<Parser> children;
|
||||
|
||||
@override
|
||||
void replace(Parser source, Parser target) {
|
||||
super.replace(source, target);
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
if (children[i] == source) {
|
||||
children[i] = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/failure.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../action/cast.dart';
|
||||
import '../action/pick.dart';
|
||||
import '../combinator/sequence.dart';
|
||||
import '../predicate/any.dart';
|
||||
import 'delegate.dart';
|
||||
|
||||
extension NotParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser (logical not-predicate) that succeeds with the [Failure]
|
||||
/// whenever the receiver fails, but never consumes input.
|
||||
///
|
||||
/// For example, the parser `char('_').not().seq(identifier)` accepts
|
||||
/// identifiers that do not start with an underscore character. If the parser
|
||||
/// `char('_')` accepts the input, the negation and subsequently the
|
||||
/// complete parser fails. Otherwise the parser `identifier` is given the
|
||||
/// ability to process the complete identifier.
|
||||
Parser<Failure<T>> not([String message = 'success not expected']) =>
|
||||
NotParser(this, message);
|
||||
|
||||
/// Returns a parser that consumes any input token (character), but the
|
||||
/// receiver.
|
||||
///
|
||||
/// For example, the parser `letter().neg()` accepts any input but a letter.
|
||||
/// The parser fails for inputs like `'a'` or `'Z'`, but succeeds for
|
||||
/// input like `'1'`, `'_'` or `'$'`.
|
||||
Parser<String> neg([String message = 'input not expected']) =>
|
||||
[not(message), any()].toSequenceParser().pick(1).cast<String>();
|
||||
}
|
||||
|
||||
/// The not-predicate, a parser that succeeds whenever its delegate does not,
|
||||
/// but consumes no input [Parr 1994, 1995].
|
||||
class NotParser<T> extends DelegateParser<Failure<T>> {
|
||||
final String message;
|
||||
|
||||
NotParser(Parser<T> delegate, this.message) : super(delegate);
|
||||
|
||||
@override
|
||||
Result<Failure<T>> parseOn(Context context) {
|
||||
final result = delegate.parseOn(context);
|
||||
if (result.isFailure) {
|
||||
return context.success(result as Failure<T>);
|
||||
} else {
|
||||
return context.failure(message);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
final result = delegate.fastParseOn(buffer, position);
|
||||
return result < 0 ? position : -1;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '${super.toString()}[$message]';
|
||||
|
||||
@override
|
||||
NotParser<T> copy() => NotParser<T>(delegate as Parser<T>, message);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(NotParser<T> other) =>
|
||||
super.hasEqualProperties(other) && message == other.message;
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import 'delegate.dart';
|
||||
|
||||
extension OptionalParserExtension<T> on Parser<T> {
|
||||
/// Returns new parser that accepts the receiver, if possible. The resulting
|
||||
/// parser returns the result of the receiver, or `null` if not applicable.
|
||||
///
|
||||
/// For example, the parser `letter().optional()` accepts a letter as input
|
||||
/// and returns that letter. When given something else the parser succeeds as
|
||||
/// well, does not consume anything and returns `null`.
|
||||
Parser<T?> optional() => OptionalParser<T?>(this, null);
|
||||
|
||||
/// Returns new parser that accepts the receiver, if possible. The resulting
|
||||
/// parser returns the result of the receiver, or [value] if not applicable.
|
||||
///
|
||||
/// For example, the parser `letter().optionalWith('!')` accepts a letter as
|
||||
/// input and returns that letter. When given something else the parser
|
||||
/// succeeds as well, does not consume anything and returns `'!'`.
|
||||
Parser<T> optionalWith(T value) => OptionalParser<T>(this, value);
|
||||
}
|
||||
|
||||
/// A parser that optionally parsers its delegate, or answers null.
|
||||
class OptionalParser<T> extends DelegateParser<T> {
|
||||
final T otherwise;
|
||||
|
||||
OptionalParser(Parser<T> delegate, this.otherwise) : super(delegate);
|
||||
|
||||
@override
|
||||
Result<T> parseOn(Context context) {
|
||||
final result = delegate.parseOn(context);
|
||||
if (result.isSuccess) {
|
||||
return result as Result<T>;
|
||||
} else {
|
||||
return context.success(otherwise);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
final result = delegate.fastParseOn(buffer, position);
|
||||
return result < 0 ? position : result;
|
||||
}
|
||||
|
||||
@override
|
||||
OptionalParser<T> copy() =>
|
||||
OptionalParser<T>(delegate as Parser<T>, otherwise);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(OptionalParser<T> other) =>
|
||||
super.hasEqualProperties(other) && otherwise == other.otherwise;
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import 'list.dart';
|
||||
|
||||
extension SequenceParserExtension on Parser {
|
||||
/// Returns a parser that accepts the receiver followed by [other]. The
|
||||
/// resulting parser returns a list of the parse result of the receiver
|
||||
/// followed by the parse result of [other]. Calling this method on an
|
||||
/// existing sequence code does not nest this sequence into a new one, but
|
||||
/// instead augments the existing sequence with [other].
|
||||
///
|
||||
/// For example, the parser `letter().seq(digit()).seq(letter())` accepts a
|
||||
/// letter followed by a digit and another letter. The parse result of the
|
||||
/// input string `'a1b'` is the list `['a', '1', 'b']`.
|
||||
Parser<List> seq(Parser other) => this is SequenceParser
|
||||
? SequenceParser([...children, other])
|
||||
: SequenceParser([this, other]);
|
||||
|
||||
/// Convenience operator returning a parser that accepts the receiver followed
|
||||
/// by [other]. See [seq] for details.
|
||||
Parser<List> operator &(Parser other) => seq(other);
|
||||
}
|
||||
|
||||
extension SequenceIterableExtension<T> on Iterable<Parser<T>> {
|
||||
/// Converts the parser in this iterable to a sequence of parsers.
|
||||
Parser<List<T>> toSequenceParser() => SequenceParser<T>(this);
|
||||
}
|
||||
|
||||
/// A parser that parses a sequence of parsers.
|
||||
class SequenceParser<T> extends ListParser<List<T>> {
|
||||
SequenceParser(Iterable<Parser<T>> children) : super(children);
|
||||
|
||||
@override
|
||||
Result<List<T>> parseOn(Context context) {
|
||||
var current = context;
|
||||
final elements = <T>[];
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
final result = children[i].parseOn(current);
|
||||
if (result.isFailure) {
|
||||
return result.failure(result.message);
|
||||
}
|
||||
elements.add(result.value);
|
||||
current = result;
|
||||
}
|
||||
return current.success(elements);
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
position = children[i].fastParseOn(buffer, position);
|
||||
if (position < 0) {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
@override
|
||||
SequenceParser<T> copy() => SequenceParser<T>(children as List<Parser<T>>);
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../combinator/delegate.dart';
|
||||
import '../misc/failure.dart';
|
||||
|
||||
extension SettableParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that points to the receiver, but can be changed to point
|
||||
/// to something else at a later point in time.
|
||||
///
|
||||
/// For example, the parser `letter().settable()` behaves exactly the same
|
||||
/// as `letter()`, but it can be replaced with another parser using
|
||||
/// [SettableParser.set].
|
||||
SettableParser<T> settable() => SettableParser<T>(this);
|
||||
}
|
||||
|
||||
/// Returns a parser that is not defined, but that can be set at a later
|
||||
/// point in time.
|
||||
///
|
||||
/// For example, the following code sets up a parser that points to itself
|
||||
/// and that accepts a sequence of a's ended with the letter b.
|
||||
///
|
||||
/// final p = undefined();
|
||||
/// p.set(char('a').seq(p).or(char('b')));
|
||||
SettableParser<T> undefined<T>([String message = 'undefined parser']) =>
|
||||
failure<T>(message).settable();
|
||||
|
||||
/// A parser that is not defined, but that can be set at a later
|
||||
/// point in time.
|
||||
class SettableParser<T> extends DelegateParser<T> {
|
||||
SettableParser(Parser<T> delegate) : super(delegate);
|
||||
|
||||
/// Sets the receiver to delegate to [parser].
|
||||
void set(Parser<T> parser) => replace(children[0], parser);
|
||||
|
||||
@override
|
||||
Result<T> parseOn(Context context) => delegate.parseOn(context) as Result<T>;
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) =>
|
||||
delegate.fastParseOn(buffer, position);
|
||||
|
||||
@override
|
||||
SettableParser<T> copy() => SettableParser<T>(delegate as Parser<T>);
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import '../action/cast.dart';
|
||||
import '../action/pick.dart';
|
||||
import '../combinator/sequence.dart';
|
||||
|
||||
extension EndOfInputParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that succeeds only if the receiver consumes the complete
|
||||
/// input, otherwise return a failure with the optional [message].
|
||||
///
|
||||
/// For example, the parser `letter().end()` succeeds on the input `'a'`
|
||||
/// and fails on `'ab'`. In contrast the parser `letter()` alone would
|
||||
/// succeed on both inputs, but not consume everything for the second input.
|
||||
Parser<T> end([String message = 'end of input expected']) =>
|
||||
[this, endOfInput(message)].toSequenceParser().pick(0).cast<T>();
|
||||
}
|
||||
|
||||
/// Returns a parser that succeeds at the end of input.
|
||||
Parser<void> endOfInput([String message = 'end of input expected']) =>
|
||||
EndOfInputParser(message);
|
||||
|
||||
/// A parser that succeeds at the end of input.
|
||||
class EndOfInputParser extends Parser<void> {
|
||||
final String message;
|
||||
|
||||
EndOfInputParser(this.message);
|
||||
|
||||
@override
|
||||
Result parseOn(Context context) => context.position < context.buffer.length
|
||||
? context.failure(message)
|
||||
: context.success(null);
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) =>
|
||||
position < buffer.length ? -1 : position;
|
||||
|
||||
@override
|
||||
String toString() => '${super.toString()}[$message]';
|
||||
|
||||
@override
|
||||
EndOfInputParser copy() => EndOfInputParser(message);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(EndOfInputParser other) =>
|
||||
super.hasEqualProperties(other) && message == other.message;
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
|
||||
/// Returns a parser that consumes nothing and succeeds.
|
||||
///
|
||||
/// For example, `char('a').or(epsilon())` is equivalent to
|
||||
/// `char('a').optional()`.
|
||||
Parser<void> epsilon() => epsilonWith<void>(null);
|
||||
|
||||
/// Returns a parser that consumes nothing and succeeds with [result].
|
||||
Parser<T> epsilonWith<T>(T result) => EpsilonParser<T>(result);
|
||||
|
||||
/// A parser that consumes nothing and succeeds.
|
||||
class EpsilonParser<T> extends Parser<T> {
|
||||
final T result;
|
||||
|
||||
EpsilonParser(this.result);
|
||||
|
||||
@override
|
||||
Result<T> parseOn(Context context) => context.success(result);
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) => position;
|
||||
|
||||
@override
|
||||
EpsilonParser<T> copy() => EpsilonParser<T>(result);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(EpsilonParser<T> other) =>
|
||||
super.hasEqualProperties(other) && result == other.result;
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
|
||||
/// Returns a parser that consumes nothing and fails.
|
||||
///
|
||||
/// For example, `failure()` always fails, no matter what input it is given.
|
||||
Parser<T> failure<T>([String message = 'unable to parse']) =>
|
||||
FailureParser<T>(message);
|
||||
|
||||
/// A parser that consumes nothing and fails.
|
||||
class FailureParser<T> extends Parser<T> {
|
||||
final String message;
|
||||
|
||||
FailureParser(this.message);
|
||||
|
||||
@override
|
||||
Result<T> parseOn(Context context) => context.failure<T>(message);
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) => -1;
|
||||
|
||||
@override
|
||||
String toString() => '${super.toString()}[$message]';
|
||||
|
||||
@override
|
||||
FailureParser<T> copy() => FailureParser<T>(message);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(FailureParser<T> other) =>
|
||||
super.hasEqualProperties(other) && message == other.message;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
|
||||
/// Returns a parser that reports the current input position.
|
||||
Parser position() => PositionParser();
|
||||
|
||||
/// A parser that reports the current input position.
|
||||
class PositionParser extends Parser<int> {
|
||||
PositionParser();
|
||||
|
||||
@override
|
||||
Result<int> parseOn(Context context) => context.success(context.position);
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) => position;
|
||||
|
||||
@override
|
||||
PositionParser copy() => PositionParser();
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
|
||||
/// Returns a parser that accepts any input element.
|
||||
///
|
||||
/// For example, `any()` succeeds and consumes any given letter. It only
|
||||
/// fails for an empty input.
|
||||
Parser<String> any([String message = 'input expected']) {
|
||||
return AnyParser(message);
|
||||
}
|
||||
|
||||
/// A parser that accepts any input element.
|
||||
class AnyParser extends Parser<String> {
|
||||
final String message;
|
||||
|
||||
AnyParser(this.message);
|
||||
|
||||
@override
|
||||
Result<String> parseOn(Context context) {
|
||||
final position = context.position;
|
||||
final buffer = context.buffer;
|
||||
return position < buffer.length
|
||||
? context.success(buffer[position], position + 1)
|
||||
: context.failure(message);
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) =>
|
||||
position < buffer.length ? position + 1 : -1;
|
||||
|
||||
@override
|
||||
AnyParser copy() => AnyParser(message);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(AnyParser other) =>
|
||||
super.hasEqualProperties(other) && message == other.message;
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
|
||||
/// A generic predicate function returning `true` or `false` for a given
|
||||
/// [input] argument.
|
||||
typedef Predicate = bool Function(String input);
|
||||
|
||||
/// Returns a parser that reads input of the specified [length], accepts
|
||||
/// it if the [predicate] matches, or fails with the given [message].
|
||||
Parser<String> predicate(int length, Predicate predicate, String message) {
|
||||
return PredicateParser(length, predicate, message);
|
||||
}
|
||||
|
||||
/// A parser for a literal satisfying a predicate.
|
||||
class PredicateParser extends Parser<String> {
|
||||
/// The length of the input to read.
|
||||
final int length;
|
||||
|
||||
/// The predicate function testing the input.
|
||||
final Predicate predicate;
|
||||
|
||||
/// The failure message in case of a miss-match.
|
||||
final String message;
|
||||
|
||||
PredicateParser(this.length, this.predicate, this.message)
|
||||
: assert(length > 0, 'length must be positive');
|
||||
|
||||
@override
|
||||
Result<String> parseOn(Context context) {
|
||||
final start = context.position;
|
||||
final stop = start + length;
|
||||
if (stop <= context.buffer.length) {
|
||||
final result = context.buffer.substring(start, stop);
|
||||
if (predicate(result)) {
|
||||
return context.success(result, stop);
|
||||
}
|
||||
}
|
||||
return context.failure(message);
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
final stop = position + length;
|
||||
return stop <= buffer.length && predicate(buffer.substring(position, stop))
|
||||
? stop
|
||||
: -1;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '${super.toString()}[$message]';
|
||||
|
||||
@override
|
||||
PredicateParser copy() => PredicateParser(length, predicate, message);
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(PredicateParser other) =>
|
||||
super.hasEqualProperties(other) &&
|
||||
length == other.length &&
|
||||
predicate == other.predicate &&
|
||||
message == other.message;
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import '../../core/parser.dart';
|
||||
import '../character/any_of.dart';
|
||||
import '../character/char.dart';
|
||||
import '../character/pattern.dart';
|
||||
import '../misc/epsilon.dart';
|
||||
import 'predicate.dart';
|
||||
|
||||
extension PredicateStringExtension on String {
|
||||
/// Converts this string to a corresponding parser.
|
||||
Parser<String> toParser({
|
||||
bool isPattern = false,
|
||||
bool caseInsensitive = false,
|
||||
String? message,
|
||||
}) {
|
||||
if (isEmpty) {
|
||||
return epsilonWith<String>(this);
|
||||
} else if (length == 1) {
|
||||
return caseInsensitive
|
||||
? anyOf('${toLowerCase()}${toUpperCase()}', message)
|
||||
: char(this, message);
|
||||
} else {
|
||||
if (isPattern) {
|
||||
return caseInsensitive
|
||||
? patternIgnoreCase(this, message)
|
||||
: pattern(this, message);
|
||||
} else {
|
||||
return caseInsensitive
|
||||
? stringIgnoreCase(this, message)
|
||||
: string(this, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a parser that accepts the string [element].
|
||||
///
|
||||
/// For example, `string('foo')` `succeeds and consumes the input string
|
||||
/// `'foo'`. Fails for any other input.`
|
||||
Parser<String> string(String element, [String? message]) {
|
||||
return predicate(element.length, (each) => element == each,
|
||||
message ?? '$element expected');
|
||||
}
|
||||
|
||||
/// Returns a parser that accepts the string [element] ignoring the case.
|
||||
///
|
||||
/// For example, `stringIgnoreCase('foo')` succeeds and consumes the input
|
||||
/// string `'Foo'` or `'FOO'`. Fails for any other input.
|
||||
Parser<String> stringIgnoreCase(String element, [String? message]) {
|
||||
final lowerElement = element.toLowerCase();
|
||||
return predicate(element.length, (each) => lowerElement == each.toLowerCase(),
|
||||
message ?? '$element expected');
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import 'lazy.dart';
|
||||
import 'limited.dart';
|
||||
import 'possessive.dart';
|
||||
import 'unbounded.dart';
|
||||
|
||||
extension GreedyRepeatingParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that parses the receiver zero or more times until it
|
||||
/// reaches a [limit]. This is a greedy non-blind implementation of the
|
||||
/// [star] operator. The [limit] is not consumed.
|
||||
///
|
||||
/// For example, the parser `char('{') & any().starGreedy(char('}')) &
|
||||
/// char('}')` consumes the complete input `'{abc}def}'` of `'{abc}def}'`.
|
||||
///
|
||||
/// See [starLazy] for the lazy, more efficient, and generally preferred
|
||||
/// variation of this combinator.
|
||||
Parser<List<T>> starGreedy(Parser limit) => repeatGreedy(limit, 0, unbounded);
|
||||
|
||||
/// Returns a parser that parses the receiver one or more times until it
|
||||
/// reaches [limit]. This is a greedy non-blind implementation of the [plus]
|
||||
/// operator. The [limit] is not consumed.
|
||||
///
|
||||
/// For example, the parser `char('{') & any().plusGreedy(char('}')) &
|
||||
/// char('}')` consumes the complete input `'{abc}def}'` of `'{abc}def}'`.
|
||||
///
|
||||
/// See [plusLazy] for the lazy, more efficient, and generally preferred
|
||||
/// variation of this combinator.
|
||||
Parser<List<T>> plusGreedy(Parser limit) => repeatGreedy(limit, 1, unbounded);
|
||||
|
||||
/// Returns a parser that parses the receiver at least [min] and at most [max]
|
||||
/// times until it reaches a [limit]. This is a greedy non-blind
|
||||
/// implementation of the [repeat] operator. The [limit] is not consumed.
|
||||
///
|
||||
/// This is the more generic variation of the [starGreedy] and [plusGreedy]
|
||||
/// combinators.
|
||||
Parser<List<T>> repeatGreedy(Parser limit, int min, int max) =>
|
||||
GreedyRepeatingParser<T>(this, limit, min, max);
|
||||
}
|
||||
|
||||
/// A greedy repeating parser, commonly seen in regular expression
|
||||
/// implementations. It aggressively consumes as much input as possible and then
|
||||
/// backtracks to meet the 'limit' condition.
|
||||
class GreedyRepeatingParser<T> extends LimitedRepeatingParser<T> {
|
||||
GreedyRepeatingParser(Parser<T> parser, Parser limit, int min, int max)
|
||||
: super(parser, limit, min, max);
|
||||
|
||||
@override
|
||||
Result<List<T>> parseOn(Context context) {
|
||||
var current = context;
|
||||
final elements = <T>[];
|
||||
while (elements.length < min) {
|
||||
final result = delegate.parseOn(current);
|
||||
if (result.isFailure) {
|
||||
return result.failure(result.message);
|
||||
}
|
||||
elements.add(result.value);
|
||||
current = result;
|
||||
}
|
||||
final contexts = <Context>[current];
|
||||
while (max == unbounded || elements.length < max) {
|
||||
final result = delegate.parseOn(current);
|
||||
if (result.isFailure) {
|
||||
break;
|
||||
}
|
||||
elements.add(result.value);
|
||||
contexts.add(current = result);
|
||||
}
|
||||
for (;;) {
|
||||
final limiter = limit.parseOn(contexts.last);
|
||||
if (limiter.isSuccess) {
|
||||
return contexts.last.success(elements);
|
||||
}
|
||||
if (elements.isEmpty) {
|
||||
return limiter.failure(limiter.message);
|
||||
}
|
||||
contexts.removeLast();
|
||||
elements.removeLast();
|
||||
if (contexts.isEmpty) {
|
||||
return limiter.failure(limiter.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
var count = 0;
|
||||
var current = position;
|
||||
while (count < min) {
|
||||
final result = delegate.fastParseOn(buffer, current);
|
||||
if (result < 0) {
|
||||
return -1;
|
||||
}
|
||||
current = result;
|
||||
count++;
|
||||
}
|
||||
final positions = <int>[current];
|
||||
while (max == unbounded || count < max) {
|
||||
final result = delegate.fastParseOn(buffer, current);
|
||||
if (result < 0) {
|
||||
break;
|
||||
}
|
||||
positions.add(current = result);
|
||||
count++;
|
||||
}
|
||||
for (;;) {
|
||||
final limiter = limit.fastParseOn(buffer, positions.last);
|
||||
if (limiter >= 0) {
|
||||
return positions.last;
|
||||
}
|
||||
if (count == 0) {
|
||||
return -1;
|
||||
}
|
||||
positions.removeLast();
|
||||
count--;
|
||||
if (positions.isEmpty) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
GreedyRepeatingParser<T> copy() =>
|
||||
GreedyRepeatingParser<T>(delegate as Parser<T>, limit, min, max);
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import 'greedy.dart';
|
||||
import 'limited.dart';
|
||||
import 'possessive.dart';
|
||||
import 'unbounded.dart';
|
||||
|
||||
extension LazyRepeatingParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that parses the receiver zero or more times until it
|
||||
/// reaches a [limit]. This is a lazy non-blind implementation of the [star]
|
||||
/// operator. The [limit] is not consumed.
|
||||
///
|
||||
/// For example, the parser `char('{') & any().starLazy(char('}')) &
|
||||
/// char('}')` only consumes the part `'{abc}'` of `'{abc}def}'`.
|
||||
///
|
||||
/// See [starGreedy] for the greedy and less efficient variation of
|
||||
/// this combinator.
|
||||
Parser<List<T>> starLazy(Parser limit) => repeatLazy(limit, 0, unbounded);
|
||||
|
||||
/// Returns a parser that parses the receiver one or more times until it
|
||||
/// reaches a [limit]. This is a lazy non-blind implementation of the [plus]
|
||||
/// operator. The [limit] is not consumed.
|
||||
///
|
||||
/// For example, the parser `char('{') & any().plusLazy(char('}')) &
|
||||
/// char('}')` only consumes the part `'{abc}'` of `'{abc}def}'`.
|
||||
///
|
||||
/// See [plusGreedy] for the greedy and less efficient variation of
|
||||
/// this combinator.
|
||||
Parser<List<T>> plusLazy(Parser limit) => repeatLazy(limit, 1, unbounded);
|
||||
|
||||
/// Returns a parser that parses the receiver at least [min] and at most [max]
|
||||
/// times until it reaches a [limit]. This is a lazy non-blind implementation
|
||||
/// of the [repeat] operator. The [limit] is not consumed.
|
||||
///
|
||||
/// This is the more generic variation of the [starLazy] and [plusLazy]
|
||||
/// combinators.
|
||||
Parser<List<T>> repeatLazy(Parser limit, int min, int max) =>
|
||||
LazyRepeatingParser<T>(this, limit, min, max);
|
||||
}
|
||||
|
||||
/// A lazy repeating parser, commonly seen in regular expression
|
||||
/// implementations. It limits its consumption to meet the 'limit' condition as
|
||||
/// early as possible.
|
||||
class LazyRepeatingParser<T> extends LimitedRepeatingParser<T> {
|
||||
LazyRepeatingParser(Parser<T> parser, Parser limit, int min, int max)
|
||||
: super(parser, limit, min, max);
|
||||
|
||||
@override
|
||||
Result<List<T>> parseOn(Context context) {
|
||||
var current = context;
|
||||
final elements = <T>[];
|
||||
while (elements.length < min) {
|
||||
final result = delegate.parseOn(current);
|
||||
if (result.isFailure) {
|
||||
return result.failure(result.message);
|
||||
}
|
||||
elements.add(result.value);
|
||||
current = result;
|
||||
}
|
||||
for (;;) {
|
||||
final limiter = limit.parseOn(current);
|
||||
if (limiter.isSuccess) {
|
||||
return current.success(elements);
|
||||
} else {
|
||||
if (max != unbounded && elements.length >= max) {
|
||||
return limiter.failure(limiter.message);
|
||||
}
|
||||
final result = delegate.parseOn(current);
|
||||
if (result.isFailure) {
|
||||
return limiter.failure(limiter.message);
|
||||
}
|
||||
elements.add(result.value);
|
||||
current = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
var count = 0;
|
||||
var current = position;
|
||||
while (count < min) {
|
||||
final result = delegate.fastParseOn(buffer, current);
|
||||
if (result < 0) {
|
||||
return -1;
|
||||
}
|
||||
current = result;
|
||||
count++;
|
||||
}
|
||||
for (;;) {
|
||||
final limiter = limit.fastParseOn(buffer, current);
|
||||
if (limiter >= 0) {
|
||||
return current;
|
||||
} else {
|
||||
if (max != unbounded && count >= max) {
|
||||
return -1;
|
||||
}
|
||||
final result = delegate.fastParseOn(buffer, current);
|
||||
if (result < 0) {
|
||||
return -1;
|
||||
}
|
||||
current = result;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
LazyRepeatingParser<T> copy() =>
|
||||
LazyRepeatingParser<T>(delegate as Parser<T>, limit, min, max);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import '../../core/parser.dart';
|
||||
import 'repeating.dart';
|
||||
|
||||
/// An abstract parser that repeatedly parses between 'min' and 'max' instances
|
||||
/// of its delegate and that requires the input to be completed with a specified
|
||||
/// parser 'limit'. Subclasses provide repeating behavior as typically seen in
|
||||
/// regular expression implementations (non-blind).
|
||||
abstract class LimitedRepeatingParser<T> extends RepeatingParser<T> {
|
||||
Parser limit;
|
||||
|
||||
LimitedRepeatingParser(Parser<T> delegate, this.limit, int min, int max)
|
||||
: super(delegate, min, max);
|
||||
|
||||
@override
|
||||
List<Parser> get children => [delegate, limit];
|
||||
|
||||
@override
|
||||
void replace(Parser source, Parser target) {
|
||||
super.replace(source, target);
|
||||
if (limit == source) {
|
||||
limit = target;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
import '../../context/context.dart';
|
||||
import '../../context/result.dart';
|
||||
import '../../core/parser.dart';
|
||||
import 'repeating.dart';
|
||||
import 'unbounded.dart';
|
||||
|
||||
extension PossessiveRepeatingParserExtension<T> on Parser<T> {
|
||||
/// Returns a parser that accepts the receiver zero or more times. The
|
||||
/// resulting parser returns a list of the parse results of the receiver.
|
||||
///
|
||||
/// This is a greedy and blind implementation that tries to consume as much
|
||||
/// input as possible and that does not consider what comes afterwards.
|
||||
///
|
||||
/// For example, the parser `letter().star()` accepts the empty string or
|
||||
/// any sequence of letters and returns a possibly empty list of the parsed
|
||||
/// letters.
|
||||
Parser<List<T>> star() => repeat(0, unbounded);
|
||||
|
||||
/// Returns a parser that accepts the receiver one or more times. The
|
||||
/// resulting parser returns a list of the parse results of the receiver.
|
||||
///
|
||||
/// This is a greedy and blind implementation that tries to consume as much
|
||||
/// input as possible and that does not consider what comes afterwards.
|
||||
///
|
||||
/// For example, the parser `letter().plus()` accepts any sequence of
|
||||
/// letters and returns a list of the parsed letters.
|
||||
Parser<List<T>> plus() => repeat(1, unbounded);
|
||||
|
||||
/// Returns a parser that accepts the receiver exactly [count] times. The
|
||||
/// resulting parser returns a list of the parse results of the receiver.
|
||||
///
|
||||
/// For example, the parser `letter().times(2)` accepts two letters and
|
||||
/// returns a list of the two parsed letters.
|
||||
Parser<List<T>> times(int count) => repeat(count, count);
|
||||
|
||||
/// Returns a parser that accepts the receiver between [min] and [max] times.
|
||||
/// The resulting parser returns a list of the parse results of the receiver.
|
||||
///
|
||||
/// This is a greedy and blind implementation that tries to consume as much
|
||||
/// input as possible and that does not consider what comes afterwards.
|
||||
///
|
||||
/// For example, the parser `letter().repeat(2, 4)` accepts a sequence of
|
||||
/// two, three, or four letters and returns the accepted letters as a list.
|
||||
Parser<List<T>> repeat(int min, [int? max]) =>
|
||||
PossessiveRepeatingParser<T>(this, min, max ?? min);
|
||||
}
|
||||
|
||||
/// A greedy parser that repeatedly parses between 'min' and 'max' instances of
|
||||
/// its delegate.
|
||||
class PossessiveRepeatingParser<T> extends RepeatingParser<T> {
|
||||
PossessiveRepeatingParser(Parser<T> parser, int min, int max)
|
||||
: super(parser, min, max);
|
||||
|
||||
@override
|
||||
Result<List<T>> parseOn(Context context) {
|
||||
final elements = <T>[];
|
||||
var current = context;
|
||||
while (elements.length < min) {
|
||||
final result = delegate.parseOn(current);
|
||||
if (result.isFailure) {
|
||||
return result.failure(result.message);
|
||||
}
|
||||
elements.add(result.value);
|
||||
current = result;
|
||||
}
|
||||
while (max == unbounded || elements.length < max) {
|
||||
final result = delegate.parseOn(current);
|
||||
if (result.isFailure) {
|
||||
return current.success(elements);
|
||||
}
|
||||
elements.add(result.value);
|
||||
current = result;
|
||||
}
|
||||
return current.success(elements);
|
||||
}
|
||||
|
||||
@override
|
||||
int fastParseOn(String buffer, int position) {
|
||||
var count = 0;
|
||||
var current = position;
|
||||
while (count < min) {
|
||||
final result = delegate.fastParseOn(buffer, current);
|
||||
if (result < 0) {
|
||||
return -1;
|
||||
}
|
||||
current = result;
|
||||
count++;
|
||||
}
|
||||
while (max == unbounded || count < max) {
|
||||
final result = delegate.fastParseOn(buffer, current);
|
||||
if (result < 0) {
|
||||
return current;
|
||||
}
|
||||
current = result;
|
||||
count++;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
@override
|
||||
PossessiveRepeatingParser<T> copy() =>
|
||||
PossessiveRepeatingParser<T>(delegate as Parser<T>, min, max);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import '../../core/parser.dart';
|
||||
import '../combinator/delegate.dart';
|
||||
import 'unbounded.dart';
|
||||
|
||||
/// An abstract parser that repeatedly parses between 'min' and 'max' instances
|
||||
/// of its delegate.
|
||||
abstract class RepeatingParser<T> extends DelegateParser<List<T>> {
|
||||
final int min;
|
||||
final int max;
|
||||
|
||||
RepeatingParser(Parser<T> parser, this.min, this.max) : super(parser) {
|
||||
if (min < 0) {
|
||||
throw ArgumentError(
|
||||
'Minimum repetitions must be positive, but got $min.');
|
||||
}
|
||||
if (max != unbounded && max < min) {
|
||||
throw ArgumentError(
|
||||
'Maximum repetitions must be larger than $min, but got $max.');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'${super.toString()}[$min..${max == unbounded ? '*' : max}]';
|
||||
|
||||
@override
|
||||
bool hasEqualProperties(RepeatingParser<T> other) =>
|
||||
super.hasEqualProperties(other) && min == other.min && max == other.max;
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import '../../core/parser.dart';
|
||||
import '../action/map.dart';
|
||||
import '../combinator/optional.dart';
|
||||
import '../combinator/sequence.dart';
|
||||
import '../repeater/possessive.dart';
|
||||
|
||||
extension SeparatedBy<T> on Parser<T> {
|
||||
/// Returns a parser that consumes the receiver one or more times separated
|
||||
/// by the [separator] parser. The resulting parser returns a flat list of
|
||||
/// the parse results of the receiver interleaved with the parse result of the
|
||||
/// separator parser. The type parameter `R` defines the type of the returned
|
||||
/// list.
|
||||
///
|
||||
/// If the optional argument [includeSeparators] is set to `false`, then the
|
||||
/// separators are not included in the parse result. If the optional argument
|
||||
/// [optionalSeparatorAtEnd] is set to `true` the parser also accepts an
|
||||
/// optional separator at the end.
|
||||
///
|
||||
/// For example, the parser `digit().separatedBy(char('-'))` returns a parser
|
||||
/// that consumes input like `'1-2-3'` and returns a list of the elements and
|
||||
/// separators: `['1', '-', '2', '-', '3']`.
|
||||
Parser<List<R>> separatedBy<R>(Parser separator,
|
||||
{bool includeSeparators = true, bool optionalSeparatorAtEnd = false}) {
|
||||
final parser = [
|
||||
this,
|
||||
[separator, this].toSequenceParser().star(),
|
||||
if (optionalSeparatorAtEnd) separator.optional(),
|
||||
].toSequenceParser();
|
||||
return parser.map((list) {
|
||||
final result = <R>[];
|
||||
result.add(list[0]);
|
||||
for (final tuple in list[1]) {
|
||||
if (includeSeparators) {
|
||||
result.add(tuple[0]);
|
||||
}
|
||||
result.add(tuple[1]);
|
||||
}
|
||||
if (includeSeparators && optionalSeparatorAtEnd && list[2] != null) {
|
||||
result.add(list[2]);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
/// An [int] used to mark an unbounded maximum repetition.
|
||||
const int unbounded = -1;
|
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -0,0 +1,15 @@
|
||||
/// Generates a hash code for three objects.
|
||||
int hash3(a, b, c) => _finish(
|
||||
_combine(_combine(_combine(0, a.hashCode), b.hashCode), c.hashCode));
|
||||
|
||||
int _finish(int hash) {
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
|
||||
int _combine(int hash, int value) {
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
library expressions.evaluator;
|
||||
|
||||
import 'expressions.dart';
|
||||
|
||||
class ExpressionEvaluator {
|
||||
const ExpressionEvaluator();
|
||||
|
||||
dynamic eval(Expression expression, Map<String, dynamic> context) {
|
||||
if (expression is Literal) return evalLiteral(expression, context);
|
||||
if (expression is Variable) return evalVariable(expression, context);
|
||||
if (expression is ThisExpression) return evalThis(expression, context);
|
||||
if (expression is MemberExpression) {
|
||||
return evalMemberExpression(expression, context);
|
||||
}
|
||||
if (expression is IndexExpression) {
|
||||
return evalIndexExpression(expression, context);
|
||||
}
|
||||
if (expression is CallExpression) {
|
||||
return evalCallExpression(expression, context);
|
||||
}
|
||||
if (expression is UnaryExpression) {
|
||||
return evalUnaryExpression(expression, context);
|
||||
}
|
||||
if (expression is BinaryExpression) {
|
||||
return evalBinaryExpression(expression, context);
|
||||
}
|
||||
if (expression is ConditionalExpression) {
|
||||
return evalConditionalExpression(expression, context);
|
||||
}
|
||||
throw ArgumentError("Unknown expression type '${expression.runtimeType}'");
|
||||
}
|
||||
|
||||
dynamic evalLiteral(Literal literal, Map<String, dynamic> context) {
|
||||
var value = literal.value;
|
||||
if (value is List) return value.map((e) => eval(e, context)).toList();
|
||||
if (value is Map) {
|
||||
return value.map(
|
||||
(key, value) => MapEntry(eval(key, context), eval(value, context)));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
dynamic evalVariable(Variable variable, Map<String, dynamic> context) {
|
||||
return context[variable.identifier.name];
|
||||
}
|
||||
|
||||
dynamic evalThis(ThisExpression expression, Map<String, dynamic> context) {
|
||||
return context['this'];
|
||||
}
|
||||
|
||||
dynamic evalMemberExpression(
|
||||
MemberExpression expression, Map<String, dynamic> context) {
|
||||
throw UnsupportedError('Member expressions not supported');
|
||||
}
|
||||
|
||||
dynamic evalIndexExpression(
|
||||
IndexExpression expression, Map<String, dynamic> context) {
|
||||
return eval(expression.object, context)[eval(expression.index, context)];
|
||||
}
|
||||
|
||||
dynamic evalCallExpression(
|
||||
CallExpression expression, Map<String, dynamic> context) {
|
||||
var callee = eval(expression.callee, context);
|
||||
var arguments = expression.arguments.map((e) => eval(e, context)).toList();
|
||||
return Function.apply(callee, arguments);
|
||||
}
|
||||
|
||||
dynamic evalUnaryExpression(
|
||||
UnaryExpression expression, Map<String, dynamic> context) {
|
||||
var argument = eval(expression.argument, context);
|
||||
switch (expression.operator) {
|
||||
case '-':
|
||||
return -argument;
|
||||
case '+':
|
||||
return argument;
|
||||
case '!':
|
||||
return !argument;
|
||||
case '~':
|
||||
return ~argument;
|
||||
}
|
||||
throw ArgumentError('Unknown unary operator ${expression.operator}');
|
||||
}
|
||||
|
||||
dynamic evalBinaryExpression(
|
||||
BinaryExpression expression, Map<String, dynamic> context) {
|
||||
var left = eval(expression.left, context);
|
||||
var right = () => eval(expression.right, context);
|
||||
switch (expression.operator) {
|
||||
case '||':
|
||||
return left || right();
|
||||
case '&&':
|
||||
return left && right();
|
||||
case '|':
|
||||
return left | right();
|
||||
case '^':
|
||||
return left ^ right();
|
||||
case '&':
|
||||
return left & right();
|
||||
case '==':
|
||||
return left == right();
|
||||
case '!=':
|
||||
return left != right();
|
||||
case '<=':
|
||||
return left <= right();
|
||||
case '>=':
|
||||
return left >= right();
|
||||
case '<':
|
||||
return left < right();
|
||||
case '>':
|
||||
return left > right();
|
||||
case '<<':
|
||||
return left << right();
|
||||
case '>>':
|
||||
return left >> right();
|
||||
case '+':
|
||||
return left + right();
|
||||
case '-':
|
||||
return left - right();
|
||||
case '*':
|
||||
return left * right();
|
||||
case '/':
|
||||
return left / right();
|
||||
case '%':
|
||||
return left % right();
|
||||
}
|
||||
throw ArgumentError(
|
||||
'Unknown operator ${expression.operator} in expression');
|
||||
}
|
||||
|
||||
dynamic evalConditionalExpression(
|
||||
ConditionalExpression expression, Map<String, dynamic> context) {
|
||||
var test = eval(expression.test, context);
|
||||
return test
|
||||
? eval(expression.consequent, context)
|
||||
: eval(expression.alternate, context);
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
library expressions.core;
|
||||
|
||||
import 'deps/quiver/core.dart';
|
||||
import 'parser.dart';
|
||||
import 'deps/petitparser/petitparser.dart';
|
||||
|
||||
class Identifier {
|
||||
final String name;
|
||||
|
||||
Identifier(this.name) {
|
||||
assert(name != 'null');
|
||||
assert(name != 'false');
|
||||
assert(name != 'true');
|
||||
assert(name != 'this');
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
|
||||
abstract class Expression {
|
||||
String toTokenString();
|
||||
|
||||
static final ExpressionParser _parser = ExpressionParser();
|
||||
|
||||
static Expression? tryParse(String formattedString) {
|
||||
final result = _parser.expression.end().parse(formattedString);
|
||||
return result.isSuccess ? result.value : null;
|
||||
}
|
||||
|
||||
static Expression parse(String formattedString) =>
|
||||
_parser.expression.end().parse(formattedString).value;
|
||||
}
|
||||
|
||||
abstract class SimpleExpression implements Expression {
|
||||
@override
|
||||
String toTokenString() => toString();
|
||||
}
|
||||
|
||||
abstract class CompoundExpression implements Expression {
|
||||
@override
|
||||
String toTokenString() => '($this)';
|
||||
}
|
||||
|
||||
class Literal extends SimpleExpression {
|
||||
final dynamic value;
|
||||
final String raw;
|
||||
|
||||
Literal(this.value, [String? raw])
|
||||
: raw = raw ?? (value is String ? '"$value"' /*TODO escape*/ : '$value');
|
||||
|
||||
@override
|
||||
String toString() => raw;
|
||||
|
||||
@override
|
||||
int get hashCode => value.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) => other is Literal && other.value == value;
|
||||
}
|
||||
|
||||
class Variable extends SimpleExpression {
|
||||
final Identifier identifier;
|
||||
|
||||
Variable(this.identifier);
|
||||
|
||||
@override
|
||||
String toString() => '$identifier';
|
||||
}
|
||||
|
||||
class ThisExpression extends SimpleExpression {}
|
||||
|
||||
class MemberExpression extends SimpleExpression {
|
||||
final Expression object;
|
||||
|
||||
final Identifier property;
|
||||
|
||||
MemberExpression(this.object, this.property);
|
||||
|
||||
@override
|
||||
String toString() => '${object.toTokenString()}.$property';
|
||||
}
|
||||
|
||||
class IndexExpression extends SimpleExpression {
|
||||
final Expression object;
|
||||
|
||||
final Expression index;
|
||||
|
||||
IndexExpression(this.object, this.index);
|
||||
|
||||
@override
|
||||
String toString() => '${object.toTokenString()}[$index]';
|
||||
}
|
||||
|
||||
class CallExpression extends SimpleExpression {
|
||||
final Expression callee;
|
||||
final List<Expression> arguments;
|
||||
|
||||
CallExpression(this.callee, this.arguments);
|
||||
|
||||
@override
|
||||
String toString() => '${callee.toTokenString()}(${arguments.join(', ')})';
|
||||
}
|
||||
|
||||
class UnaryExpression extends SimpleExpression {
|
||||
final String operator;
|
||||
|
||||
final Expression argument;
|
||||
|
||||
final bool prefix;
|
||||
|
||||
UnaryExpression(this.operator, this.argument, {this.prefix = true});
|
||||
|
||||
@override
|
||||
String toString() => '$operator$argument';
|
||||
}
|
||||
|
||||
class BinaryExpression extends CompoundExpression {
|
||||
final String operator;
|
||||
final Expression left;
|
||||
final Expression right;
|
||||
|
||||
BinaryExpression(this.operator, this.left, this.right);
|
||||
|
||||
static int precedenceForOperator(String operator) =>
|
||||
ExpressionParser.binaryOperations[operator]!;
|
||||
|
||||
int get precedence => precedenceForOperator(operator);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var l = (left is BinaryExpression &&
|
||||
(left as BinaryExpression).precedence < precedence)
|
||||
? '($left)'
|
||||
: '$left';
|
||||
var r = (right is BinaryExpression &&
|
||||
(right as BinaryExpression).precedence < precedence)
|
||||
? '($right)'
|
||||
: '$right';
|
||||
return '$l$operator$r';
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hash3(left, operator, right);
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) =>
|
||||
other is BinaryExpression &&
|
||||
other.left == left &&
|
||||
other.operator == operator &&
|
||||
other.right == right;
|
||||
}
|
||||
|
||||
class ConditionalExpression extends CompoundExpression {
|
||||
final BinaryExpression test;
|
||||
final Expression consequent;
|
||||
final Expression alternate;
|
||||
|
||||
ConditionalExpression(this.test, this.consequent, this.alternate);
|
||||
|
||||
@override
|
||||
String toString() => '$test ? $consequent : $alternate';
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
library expressions.parser;
|
||||
|
||||
import 'expressions.dart';
|
||||
import 'deps/petitparser/petitparser.dart';
|
||||
|
||||
class ExpressionParser {
|
||||
ExpressionParser() {
|
||||
expression.set(binaryExpression.seq(conditionArguments.optional()).map(
|
||||
(l) => l[1] == null
|
||||
? l[0]
|
||||
: ConditionalExpression(l[0], l[1][0], l[1][1])));
|
||||
token.set((literal | unaryExpression | variable).cast<Expression>());
|
||||
}
|
||||
|
||||
// Gobbles only identifiers
|
||||
// e.g.: `foo`, `_value`, `$x1`
|
||||
Parser<Identifier> get identifier =>
|
||||
(digit().not() & (word() | char(r'$')).plus())
|
||||
.flatten()
|
||||
.map((v) => Identifier(v));
|
||||
|
||||
// Parse simple numeric literals: `12`, `3.4`, `.5`.
|
||||
Parser<Literal> get numericLiteral => ((digit() | char('.')).and() &
|
||||
(digit().star() &
|
||||
((char('.') & digit().plus()) |
|
||||
(char('x') & digit().plus()) |
|
||||
(anyOf('Ee') &
|
||||
anyOf('+-').optional() &
|
||||
digit().plus()))
|
||||
.optional()))
|
||||
.flatten()
|
||||
.map((v) {
|
||||
return Literal(num.parse(v), v);
|
||||
});
|
||||
|
||||
Parser<String> get escapedChar => (char(r'\') & anyOf("nrtbfv\"'")).pick(1);
|
||||
|
||||
String unescape(String v) => v.replaceAllMapped(
|
||||
RegExp("\\\\[nrtbf\"']"),
|
||||
(v) => const {
|
||||
'n': '\n',
|
||||
'r': '\r',
|
||||
't': '\t',
|
||||
'b': '\b',
|
||||
'f': '\f',
|
||||
'v': '\v',
|
||||
"'": "'",
|
||||
'"': '"'
|
||||
}[v.group(0)!.substring(1)]!);
|
||||
|
||||
Parser<Literal> get sqStringLiteral => (char("'") &
|
||||
(anyOf(r"'\").neg() | escapedChar).star().flatten() &
|
||||
char("'"))
|
||||
.pick(1)
|
||||
.map((v) => Literal(unescape(v), "'$v'"));
|
||||
|
||||
Parser<Literal> get dqStringLiteral => (char('"') &
|
||||
(anyOf(r'"\').neg() | escapedChar).star().flatten() &
|
||||
char('"'))
|
||||
.pick(1)
|
||||
.map((v) => Literal(unescape(v), '"$v"'));
|
||||
|
||||
// Parses a string literal, staring with single or double quotes with basic
|
||||
// support for escape codes e.g. `'hello world'`, `'this is\nJSEP'`
|
||||
Parser<Literal> get stringLiteral =>
|
||||
sqStringLiteral.or(dqStringLiteral).cast();
|
||||
|
||||
// Parses a boolean literal
|
||||
Parser<Literal> get boolLiteral =>
|
||||
(string('true') | string('false')).map((v) => Literal(v == 'true', v));
|
||||
|
||||
// Parses the null literal
|
||||
Parser<Literal> get nullLiteral =>
|
||||
string('null').map((v) => Literal(null, v));
|
||||
|
||||
// Parses the this literal
|
||||
Parser<ThisExpression> get thisExpression =>
|
||||
string('this').map((v) => ThisExpression());
|
||||
|
||||
// Responsible for parsing Array literals `[1, 2, 3]`
|
||||
// This function assumes that it needs to gobble the opening bracket
|
||||
// and then tries to gobble the expressions as arguments.
|
||||
Parser<Literal> get arrayLiteral =>
|
||||
(char('[').trim() & arguments & char(']').trim())
|
||||
.pick(1)
|
||||
.map((l) => Literal(l, '$l'));
|
||||
|
||||
Parser<Literal> get mapLiteral =>
|
||||
(char('{').trim() & mapArguments & char('}').trim())
|
||||
.pick(1)
|
||||
.map((l) => Literal(l, '$l'));
|
||||
|
||||
Parser<Literal> get literal => (numericLiteral |
|
||||
stringLiteral |
|
||||
boolLiteral |
|
||||
nullLiteral |
|
||||
arrayLiteral |
|
||||
mapLiteral)
|
||||
.cast();
|
||||
|
||||
// An individual part of a binary expression:
|
||||
// e.g. `foo.bar(baz)`, `1`, `'abc'`, `(a % 2)` (because it's in parenthesis)
|
||||
final SettableParser<Expression> token = undefined<Expression>();
|
||||
|
||||
// Also use a map for the binary operations but set their values to their
|
||||
// binary precedence for quick reference:
|
||||
// see [Order of operations](http://en.wikipedia.org/wiki/Order_of_operations#Programming_language)
|
||||
static const Map<String, int> binaryOperations = {
|
||||
'||': 1,
|
||||
'&&': 2,
|
||||
'|': 3,
|
||||
'^': 4,
|
||||
'&': 5,
|
||||
'==': 6,
|
||||
'!=': 6,
|
||||
'<=': 7,
|
||||
'>=': 7,
|
||||
'<': 7,
|
||||
'>': 7,
|
||||
'<<': 8,
|
||||
'>>': 8,
|
||||
'+': 9,
|
||||
'-': 9,
|
||||
'*': 10,
|
||||
'/': 10,
|
||||
'%': 10
|
||||
};
|
||||
|
||||
// This function is responsible for gobbling an individual expression,
|
||||
// e.g. `1`, `1+2`, `a+(b*2)-Math.sqrt(2)`
|
||||
Parser<String> get binaryOperation => binaryOperations.keys
|
||||
.map<Parser<String>>((v) => string(v))
|
||||
.reduce((a, b) => (a | b).cast<String>())
|
||||
.trim();
|
||||
|
||||
Parser<Expression> get binaryExpression =>
|
||||
token.separatedBy(binaryOperation).map((l) {
|
||||
var first = l[0];
|
||||
var stack = <dynamic>[first];
|
||||
|
||||
for (var i = 1; i < l.length; i += 2) {
|
||||
var op = l[i];
|
||||
var prec = BinaryExpression.precedenceForOperator(op);
|
||||
|
||||
// Reduce: make a binary expression from the three topmost entries.
|
||||
while ((stack.length > 2) &&
|
||||
(prec <=
|
||||
BinaryExpression.precedenceForOperator(
|
||||
stack[stack.length - 2]))) {
|
||||
var right = stack.removeLast();
|
||||
var op = stack.removeLast();
|
||||
var left = stack.removeLast();
|
||||
var node = BinaryExpression(op, left, right);
|
||||
stack.add(node);
|
||||
}
|
||||
|
||||
var node = l[i + 1];
|
||||
stack.addAll([op, node]);
|
||||
}
|
||||
|
||||
var i = stack.length - 1;
|
||||
var node = stack[i];
|
||||
while (i > 1) {
|
||||
node = BinaryExpression(stack[i - 1], stack[i - 2], node);
|
||||
i -= 2;
|
||||
}
|
||||
return node;
|
||||
});
|
||||
|
||||
// Use a quickly-accessible map to store all of the unary operators
|
||||
// Values are set to `true` (it really doesn't matter)
|
||||
static const _unaryOperations = ['-', '!', '~', '+'];
|
||||
|
||||
Parser<UnaryExpression> get unaryExpression => _unaryOperations
|
||||
.map<Parser<String>>((v) => string(v))
|
||||
.reduce((a, b) => (a | b).cast<String>())
|
||||
.trim()
|
||||
.seq(token)
|
||||
.map((l) => UnaryExpression(l[0], l[1]));
|
||||
|
||||
// Gobbles a list of arguments within the context of a function call
|
||||
// or array literal. This function also assumes that the opening character
|
||||
// `(` or `[` has already been gobbled, and gobbles expressions and commas
|
||||
// until the terminator character `)` or `]` is encountered.
|
||||
// e.g. `foo(bar, baz)`, `my_func()`, or `[bar, baz]`
|
||||
Parser<List<Expression>> get arguments => expression
|
||||
.separatedBy(char(',').trim(), includeSeparators: false)
|
||||
.castList<Expression>()
|
||||
.optionalWith([]);
|
||||
|
||||
Parser<Map<Expression, Expression>> get mapArguments =>
|
||||
(expression & char(':').trim() & expression)
|
||||
.map((l) => MapEntry<Expression, Expression>(l[0], l[2]))
|
||||
.separatedBy(char(',').trim(), includeSeparators: false)
|
||||
.castList<MapEntry<Expression, Expression>>()
|
||||
.map((l) => Map.fromEntries(l))
|
||||
.optionalWith({});
|
||||
|
||||
// Gobble a non-literal variable name. This variable name may include properties
|
||||
// e.g. `foo`, `bar.baz`, `foo['bar'].baz`
|
||||
// It also gobbles function calls:
|
||||
// e.g. `Math.acos(obj.angle)`
|
||||
Parser<Expression> get variable => groupOrIdentifier
|
||||
.seq((memberArgument.cast() | indexArgument | callArgument).star())
|
||||
.map((l) {
|
||||
var a = l[0] as Expression;
|
||||
var b = l[1] as List;
|
||||
return b.fold(a, (Expression object, argument) {
|
||||
if (argument is Identifier) {
|
||||
return MemberExpression(object, argument);
|
||||
}
|
||||
if (argument is Expression) {
|
||||
return IndexExpression(object, argument);
|
||||
}
|
||||
if (argument is List<Expression>) {
|
||||
return CallExpression(object, argument);
|
||||
}
|
||||
throw ArgumentError('Invalid type ${argument.runtimeType}');
|
||||
});
|
||||
});
|
||||
|
||||
// Responsible for parsing a group of things within parentheses `()`
|
||||
// This function assumes that it needs to gobble the opening parenthesis
|
||||
// and then tries to gobble everything within that parenthesis, assuming
|
||||
// that the next thing it should see is the close parenthesis. If not,
|
||||
// then the expression probably doesn't have a `)`
|
||||
Parser<Expression> get group =>
|
||||
(char('(') & expression.trim() & char(')')).pick(1);
|
||||
|
||||
Parser<Expression> get groupOrIdentifier =>
|
||||
(group | thisExpression | identifier.map((v) => Variable(v))).cast();
|
||||
|
||||
Parser<Identifier> get memberArgument => (char('.') & identifier).pick(1);
|
||||
|
||||
Parser<Expression> get indexArgument =>
|
||||
(char('[') & expression.trim() & char(']')).pick(1);
|
||||
|
||||
Parser<List<Expression>> get callArgument =>
|
||||
(char('(') & arguments & char(')')).pick(1);
|
||||
|
||||
// Ternary expression: test ? consequent : alternate
|
||||
Parser<List<Expression>> get conditionArguments =>
|
||||
(char('?').trim() & expression & char(':').trim())
|
||||
.pick(1)
|
||||
.seq(expression)
|
||||
.castList();
|
||||
|
||||
final SettableParser<Expression> expression = undefined();
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Web related
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Exceptions to above rules.
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
@ -1,10 +0,0 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: 5f21edf8b66e31a39133177319414395cc5b5f48
|
||||
channel: stable
|
||||
|
||||
project_type: app
|
@ -1,16 +0,0 @@
|
||||
# flutterapp
|
||||
|
||||
A new Flutter application.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
|
||||
|
||||
For help getting started with Flutter, view our
|
||||
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
@ -1,7 +0,0 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
@ -1,63 +0,0 @@
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.debuggerx.flutterapp"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.debuggerx.flutterapp">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
@ -1,47 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.debuggerx.flutterapp">
|
||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
In most cases you can leave this as-is, but you if you want to provide
|
||||
additional functionality it is fine to subclass or reimplement
|
||||
FlutterApplication and put your custom class here. -->
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:label="flutterapp"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<!-- Displays an Android View that continues showing the launch screen
|
||||
Drawable until Flutter paints its first frame, then this splash
|
||||
screen fades out. A splash screen is useful to avoid any visual
|
||||
gap between the end of Android's launch screen and the painting of
|
||||
Flutter's first frame. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
android:resource="@drawable/launch_background"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
</manifest>
|
@ -1,6 +0,0 @@
|
||||
package com.debuggerx.flutterapp
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
Before Width: | Height: | Size: 544 B |
Before Width: | Height: | Size: 442 B |
Before Width: | Height: | Size: 721 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.4 KiB |
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
@ -1,7 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.debuggerx.flutterapp">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
@ -1,31 +0,0 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.50'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue