feat: use expressions.

master
DebuggerX 4 years ago
parent 347e26f6cf
commit f4fc58fa17

@ -1,37 +1,25 @@
# Flutter工程条件编译打包脚本
## 原理
通过将 flutter run/build [--option] 命令替换为 bash flutter.sh run/build [--option] 在原有flutter运行/打包流程前后执行内置及用户自定义脚本,从而实现对打包流程的自定义控制,默认内置功能为根据命令参数中的 --debug/release 以及 --flavor 渠道名,对代码和资源文件的条件编译
通过将 flutter run/build [--option] 命令替换为 bash flutter.sh run/build [--option] 在原有flutter运行/打包流程前后执行内置及用户自定义脚本,从而实现对打包流程的自定义控制,默认内置功能为根据命令参数中的
--debug/release 以及 --flavor 渠道名,对代码条件编译.
## 用法语法
### 代码:
代码中使用形如以下的注释来进行代码块的条件标记
```dart
void main(List<String> arguments) {
print(1);
// #[release[google]]
// #{{exp}}
// print(2);
// #[release[!asd]]
// print(3);
// #[debug]
// print(4);
// #[release[test]]
// print(5);
// #[[test asd]]
// print(6);
// #[[]]
// print(7)
// #[[]]
// #{{default}}
print(7);
// #{{end}}
}
```
基本注释标记语法为 **// #[debug/release[flavorName1 flavorName2 ...]]** 外层方括号内填写debug模式或release模式可以留空表示同时匹配两种模式内层方括号内填写flavor名的列表打包命令中 --flavor flavorName 参数传递进来的渠道名在列表中则匹配成功。
特殊的,如果渠道名列表中的名称前加''表示取反命令参数中传入的渠道名不在该列表中时匹配成功ps: 注意普通渠道名条件列表与带取反的列表不能同时存在)
注释标记后紧跟该条件下的代码,并注意用 '// ' 进行代码注释,脚本解析时从上到下,某个注释标记的条件匹配成功,就会将紧跟的代码片段覆盖最近的默认代码块,并忽略掉两者之间的其他条件代码块。
默认代码块的前后需要用 '// #[[]]' 注释作为标记。
### 文件:
类似代码中注释条件标记的语法,文件名中使用形如 'abc[debug/release[flavorName1 flavorName2 ...]].xxx' 的形式进行文件命名,并用对应的 'abc.xxx' 文件名作为默认情况下使用的文件,即可在编译运行的时候自动根据传入参数将合适的文件替换为实际使用的文件,并在编译完成后自动还原。
# **WIP**
基本注释标记语法为 **// #{{exp | default | end}}**

@ -8,9 +8,5 @@ void main(List<String> arguments) {
File(p.path.substring(0, p.path.length - 4)).deleteSync();
p.renameSync(p.path.substring(0, p.path.length - 4));
}
if (p.path.endsWith('.default')) {
File(p.path.substring(0, p.path.length - 8)).deleteSync();
p.renameSync(p.path.substring(0, p.path.length - 8));
}
});
}

@ -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,94 +1,73 @@
import 'dart:io';
import 'parse_arguments.dart';
import 'path_utils.dart';
import 'expressions/expressions.dart';
String mode;
List<String> flavors;
Args args;
String? exp;
late String mode;
late String flavor;
enum STATE {
none,
notMatch,
caching,
cached,
replace,
}
void main(List<String> arguments) {
print("Running default pre_script.");
args = parse(arguments);
var args = parse(arguments);
mode = args.mode;
flavor = args.flavor;
var rootDir = Directory('./');
rootDir.listSync().forEach(walkPath);
}
File file;
File? file;
StringBuffer sb = StringBuffer();
StringBuffer tmp = StringBuffer();
STATE state = STATE.none;
RegExp re = RegExp(r'// #\[(debug|release)?(?:\[(.*)\])?\]');
RegExp pathRe = RegExp(r'\[(debug|release)?(?:\[(.*)\])?\]');
Match ma;
RegExp re = RegExp(r'// #\{\{(.+)\}\}');
Match? ma;
bool modified = false;
const evaluator = const ExpressionEvaluator();
void walkPath(FileSystemEntity path) {
var stat = path.statSync();
if (stat.type == FileSystemEntityType.directory) {
Directory(path.path).listSync().forEach(walkPath);
} else if (stat.type == FileSystemEntityType.file) {
ma = pathRe.firstMatch(PathUtils.baseName(path));
if (ma != null) {
mode = ma.group(1);
flavors = (ma.group(2) ?? '').split(' ').where((ele) => ele.trim() != '').toList();
if (checkModeAndFlavors()) {
var defaultFilePath = path.path.replaceFirst(ma.group(0), '');
var defaultFile = File(defaultFilePath);
if (defaultFile.existsSync()) {
defaultFile.renameSync(defaultFilePath + '.default');
}
File(path.path).copySync(defaultFilePath);
}
return;
}
//
file = File(path.path);
sb.clear();
modified = false;
try {
file.readAsLinesSync().forEach((line) {
file!.readAsLinesSync().forEach((line) {
ma = re.firstMatch(line);
if (ma != null) {
modified = true;
mode = ma.group(1);
flavors = (ma.group(2) ?? '').split(' ').where((ele) => ele.trim() != '').toList();
//
if (mode == null && flavors.length == 0) {
// replacenone
if (state == STATE.replace) {
exp = ma!.group(1);
if (exp == "default") {
//
if (tmp.isNotEmpty) {
sb.write(tmp);
state = STATE.none;
}
//
else if (state == STATE.caching || state == STATE.cached) {
state = STATE.replace;
} else if (state == STATE.notMatch) {
} else {
state = STATE.none;
}
} else if (exp == "end") {
//
state = STATE.none;
tmp.clear();
} else {
//
if (state == STATE.caching) {
//
state = STATE.cached;
} else if (state == STATE.none || state == STATE.notMatch) {
// none
if (checkModeAndFlavors()) {
state = STATE.caching;
tmp.clear();
} else {
state = STATE.notMatch;
}
if (evaluator.eval(
Expression.parse(exp!), {'mode': mode, 'flavor': flavor})) {
//
tmp.clear();
state = STATE.caching;
} else {
state = STATE.notMatch;
}
}
} else {
@ -97,13 +76,15 @@ void walkPath(FileSystemEntity path) {
sb.writeln(line);
}
//
else if (state == STATE.caching) tmp.writeln(line.replaceFirst('// ', ''));
else if (state == STATE.caching)
tmp.writeln(line.replaceFirst('// ', ''));
//
}
});
if (modified) {
file.renameSync(path.path + '.bak');
File(path.path).writeAsStringSync(sb.toString(), flush: true);
// file!.renameSync(path.path + '.bak');
// File(path.path).writeAsStringSync(sb.toString(), flush: true);
print(sb.toString());
}
} catch (e) {
if (!(e is FileSystemException)) {
@ -112,17 +93,3 @@ void walkPath(FileSystemEntity path) {
}
}
}
bool checkModeAndFlavors() {
if (mode != null && mode != args.mode) {
return false;
}
if (flavors.isEmpty) return true;
if (flavors[0].startsWith('!')) {
return !flavors.map((e) => e.replaceFirst('!', '')).toList().contains(args.flavor);
} else {
return flavors.contains(args.flavor);
}
}

@ -4,7 +4,7 @@ SCRIPT_ABS=$(readlink -f "$0")
SCRIPT_DIR=$(dirname "$SCRIPT_ABS")
# shellcheck disable=SC2005
DART_EXE=$(echo "$(command -v flutter)" | awk '{print substr($0,0,length()-7)}')cache/dart-sdk/bin/dart
DART_EXE=$(command -v dart)
if [[ ! -x "$DART_EXE" ]]; then
echo "Can't find dart executable file !"

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

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…
Cancel
Save