From 8468d7e155d3a5842ba90fb1cfc3a337109281ca Mon Sep 17 00:00:00 2001 From: Lukas Renggli Date: Sun, 7 Feb 2021 17:44:10 +0100 Subject: [PATCH] #80: Attempt at abstracting over String, to be able to use Characters. --- petitparser/lib/buffer.dart | 38 ++++ petitparser/lib/petitparser.dart | 1 + .../lib/src/buffer/character_buffer.dart | 35 ++++ petitparser/lib/src/buffer/string_buffer.dart | 22 ++ petitparser/lib/src/context/context.dart | 3 +- petitparser/lib/src/context/failure.dart | 3 +- petitparser/lib/src/context/result.dart | 3 +- petitparser/lib/src/context/success.dart | 3 +- petitparser/lib/src/core/exception.dart | 3 +- petitparser/lib/src/core/parser.dart | 7 +- petitparser/lib/src/core/token.dart | 7 +- petitparser/lib/src/definition/parser.dart | 3 +- petitparser/lib/src/definition/reference.dart | 3 +- petitparser/lib/src/matcher/accept.dart | 4 +- petitparser/lib/src/matcher/matches.dart | 5 +- .../lib/src/matcher/matches_skipping.dart | 8 +- .../src/matcher/pattern/parser_pattern.dart | 3 +- .../src/matcher/pattern/pattern_iterator.dart | 9 +- petitparser/lib/src/parser/action/cast.dart | 3 +- .../lib/src/parser/action/cast_list.dart | 3 +- .../lib/src/parser/action/flatten.dart | 3 +- petitparser/lib/src/parser/action/map.dart | 3 +- .../lib/src/parser/action/permute.dart | 3 +- petitparser/lib/src/parser/action/pick.dart | 3 +- petitparser/lib/src/parser/action/token.dart | 3 +- .../lib/src/parser/action/trimming.dart | 5 +- .../lib/src/parser/character/parser.dart | 12 +- .../lib/src/parser/combinator/and.dart | 3 +- .../lib/src/parser/combinator/choice.dart | 3 +- .../lib/src/parser/combinator/not.dart | 3 +- .../lib/src/parser/combinator/optional.dart | 3 +- .../lib/src/parser/combinator/sequence.dart | 3 +- .../lib/src/parser/combinator/settable.dart | 3 +- petitparser/lib/src/parser/misc/eof.dart | 3 +- petitparser/lib/src/parser/misc/epsilon.dart | 3 +- petitparser/lib/src/parser/misc/failure.dart | 3 +- petitparser/lib/src/parser/misc/position.dart | 3 +- petitparser/lib/src/parser/predicate/any.dart | 5 +- .../lib/src/parser/predicate/predicate.dart | 13 +- .../lib/src/parser/repeater/greedy.dart | 3 +- petitparser/lib/src/parser/repeater/lazy.dart | 3 +- .../lib/src/parser/repeater/possessive.dart | 3 +- petitparser/pubspec.yaml | 1 + petitparser/test/context_test.dart | 4 +- petitparser/test/parser_test.dart | 7 +- petitparser/test/test_utils.dart | 23 +- petitparser_examples/bin/core_benchmark.dart | 197 +++++++++--------- 47 files changed, 320 insertions(+), 167 deletions(-) create mode 100644 petitparser/lib/buffer.dart create mode 100644 petitparser/lib/src/buffer/character_buffer.dart create mode 100644 petitparser/lib/src/buffer/string_buffer.dart diff --git a/petitparser/lib/buffer.dart b/petitparser/lib/buffer.dart new file mode 100644 index 00000000..291ca212 --- /dev/null +++ b/petitparser/lib/buffer.dart @@ -0,0 +1,38 @@ +/// This package contains the parse input buffers. +import 'package:characters/characters.dart'; + +import 'src/buffer/character_buffer.dart'; +import 'src/buffer/string_buffer.dart'; + +abstract class Buffer { + /// Create a buffer from the most appropriate internal implementation. + factory Buffer(/*Buffer|String|Characters*/ dynamic input) { + if (input is Buffer) { + return input; + } else if (input is String) { + return Buffer.fromString(input); + } else if (input is Characters) { + return Buffer.fromCharacters(input); + } else { + throw ArgumentError.value(input, 'input', 'Invalid input buffer'); + } + } + + /// Create a buffer from an input [String] (UTF-16). + factory Buffer.fromString(String input) => StringBuffer(input); + + /// Create a buffer from input [Characters] (Unicode Grapheme). + factory Buffer.fromCharacters(Characters input) => CharactersBuffer(input); + + /// Return the length of the buffer. + int get length; + + /// Return the character at [position]. + String charAt(int position); + + /// Return the character code at [position]. + int codeUnitAt(int position); + + /// Return the substring between [start] and [stop] (exclusive). + String substring(int start, int stop); +} diff --git a/petitparser/lib/petitparser.dart b/petitparser/lib/petitparser.dart index f7d1145d..2ac659ed 100644 --- a/petitparser/lib/petitparser.dart +++ b/petitparser/lib/petitparser.dart @@ -1,5 +1,6 @@ /// This package exports the core library of PetitParser, a dynamic parser /// combinator framework. +export 'buffer.dart'; export 'context.dart'; export 'core.dart'; export 'definition.dart'; diff --git a/petitparser/lib/src/buffer/character_buffer.dart b/petitparser/lib/src/buffer/character_buffer.dart new file mode 100644 index 00000000..2fef0607 --- /dev/null +++ b/petitparser/lib/src/buffer/character_buffer.dart @@ -0,0 +1,35 @@ +import 'package:characters/characters.dart'; + +import '../../buffer.dart'; + +class CharactersBuffer implements Buffer { + final List characters; + + CharactersBuffer(Characters characters) + : characters = characters.toList(growable: false); + + @override + int get length => characters.length; + + @override + String charAt(int position) => characters[position]; + + @override + int codeUnitAt(int position) { + final chars = characters[position]; + final charsLength = chars.length; + if (charsLength == 1) { + return chars.codeUnitAt(0); + } else { + var value = 0; + for (var i = 0; i < charsLength; i++) { + value = (value << 16) | chars.codeUnitAt(i); + } + return value; + } + } + + @override + String substring(int start, int end) => + characters.getRange(start, end).join(); +} diff --git a/petitparser/lib/src/buffer/string_buffer.dart b/petitparser/lib/src/buffer/string_buffer.dart new file mode 100644 index 00000000..3e9bbd8f --- /dev/null +++ b/petitparser/lib/src/buffer/string_buffer.dart @@ -0,0 +1,22 @@ +import 'package:meta/meta.dart'; + +import '../../buffer.dart'; + +@immutable +class StringBuffer implements Buffer { + final String string; + + const StringBuffer(this.string); + + @override + int get length => string.length; + + @override + String charAt(int position) => string[position]; + + @override + int codeUnitAt(int position) => string.codeUnitAt(position); + + @override + String substring(int start, int end) => string.substring(start, end); +} diff --git a/petitparser/lib/src/context/context.dart b/petitparser/lib/src/context/context.dart index 4b301a4f..c70908db 100644 --- a/petitparser/lib/src/context/context.dart +++ b/petitparser/lib/src/context/context.dart @@ -1,5 +1,6 @@ import 'package:meta/meta.dart'; +import '../../buffer.dart'; import '../core/token.dart'; import 'failure.dart'; import 'result.dart'; @@ -11,7 +12,7 @@ class Context { const Context(this.buffer, this.position); /// The buffer we are working on. - final String buffer; + final Buffer buffer; /// The current position in the [buffer]. final int position; diff --git a/petitparser/lib/src/context/failure.dart b/petitparser/lib/src/context/failure.dart index 121440da..1c778808 100644 --- a/petitparser/lib/src/context/failure.dart +++ b/petitparser/lib/src/context/failure.dart @@ -1,9 +1,10 @@ +import '../../buffer.dart'; import '../core/exception.dart'; import 'result.dart'; /// An immutable parse result in case of a failed parse. class Failure extends Result { - const Failure(String buffer, int position, this.message) + const Failure(Buffer buffer, int position, this.message) : super(buffer, position); @override diff --git a/petitparser/lib/src/context/result.dart b/petitparser/lib/src/context/result.dart index 6c59f785..c7d80e3a 100644 --- a/petitparser/lib/src/context/result.dart +++ b/petitparser/lib/src/context/result.dart @@ -1,9 +1,10 @@ +import '../../buffer.dart'; import '../core/exception.dart'; import 'context.dart'; /// An immutable parse result. abstract class Result extends Context { - const Result(String buffer, int position) : super(buffer, position); + const Result(Buffer buffer, int position) : super(buffer, position); /// Returns `true` if this result indicates a parse success. bool get isSuccess => false; diff --git a/petitparser/lib/src/context/success.dart b/petitparser/lib/src/context/success.dart index dfede396..f08f881e 100644 --- a/petitparser/lib/src/context/success.dart +++ b/petitparser/lib/src/context/success.dart @@ -1,8 +1,9 @@ +import '../../buffer.dart'; import 'result.dart'; /// An immutable parse result in case of a successful parse. class Success extends Result { - const Success(String buffer, int position, this.value) + const Success(Buffer buffer, int position, this.value) : super(buffer, position); @override diff --git a/petitparser/lib/src/core/exception.dart b/petitparser/lib/src/core/exception.dart index 39759e89..a44a7042 100644 --- a/petitparser/lib/src/core/exception.dart +++ b/petitparser/lib/src/core/exception.dart @@ -1,3 +1,4 @@ +import '../../buffer.dart'; import '../context/failure.dart'; /// An exception raised in case of a parse error. @@ -13,7 +14,7 @@ class ParserException implements FormatException { int get offset => failure.position; @override - String get source => failure.buffer; + Buffer get source => failure.buffer; @override String toString() => '${failure.message} at ${failure.toPositionString()}'; diff --git a/petitparser/lib/src/core/parser.dart b/petitparser/lib/src/core/parser.dart index a12a6a9a..7787ce98 100644 --- a/petitparser/lib/src/core/parser.dart +++ b/petitparser/lib/src/core/parser.dart @@ -1,5 +1,6 @@ import 'package:meta/meta.dart'; +import '../../buffer.dart'; import '../context/context.dart'; import '../context/failure.dart'; import '../context/result.dart'; @@ -30,7 +31,7 @@ abstract class Parser { /// /// Subclasses don't necessarily have to override this method, since it is /// emulated using its slower brother. - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer buffer, int position) { final result = parseOn(Context(buffer, position)); return result.isSuccess ? result.position : -1; } @@ -47,7 +48,9 @@ abstract class Parser { /// Similarly, `letter().plus().parse('123')` results in an instance of /// [Failure], where [Context.position] is `0` and [Failure.message] is /// ['letter expected']. - Result parse(String input) => parseOn(Context(input, 0)); + @nonVirtual + Result parse(/*Buffer|String|Characters*/ dynamic input) => + parseOn(Context(Buffer(input), 0)); /// Returns a shallow copy of the receiver. /// diff --git a/petitparser/lib/src/core/token.dart b/petitparser/lib/src/core/token.dart index 8601bec6..d324e1c8 100644 --- a/petitparser/lib/src/core/token.dart +++ b/petitparser/lib/src/core/token.dart @@ -1,5 +1,6 @@ import 'package:meta/meta.dart'; +import '../../buffer.dart'; import '../matcher/matches_skipping.dart'; import '../parser/action/token.dart'; import '../parser/character/char.dart'; @@ -23,7 +24,7 @@ class Token { final T value; /// The parsed buffer of the token. - final String buffer; + final Buffer buffer; /// The start position of the token in the buffer. final int start; @@ -64,7 +65,7 @@ class Token { char('\n') | (char('\r') & char('\n').optional()); /// Converts the [position] index in a [buffer] to a line and column tuple. - static List lineAndColumnOf(String buffer, int position) { + static List lineAndColumnOf(Buffer buffer, int position) { var line = 1, offset = 0; for (final token in newlineParser().token().matchesSkipping(buffer)) { if (position < token.stop) { @@ -78,7 +79,7 @@ class Token { /// Returns a human readable string representing the [position] index in a /// [buffer]. - static String positionString(String buffer, int position) { + static String positionString(Buffer buffer, int position) { final lineAndColumn = lineAndColumnOf(buffer, position); return '${lineAndColumn[0]}:${lineAndColumn[1]}'; } diff --git a/petitparser/lib/src/definition/parser.dart b/petitparser/lib/src/definition/parser.dart index 28c62e77..3eba97e6 100644 --- a/petitparser/lib/src/definition/parser.dart +++ b/petitparser/lib/src/definition/parser.dart @@ -1,3 +1,4 @@ +import '../../buffer.dart'; import '../context/context.dart'; import '../context/result.dart'; import '../core/parser.dart'; @@ -14,7 +15,7 @@ class GrammarParser extends DelegateParser { Result parseOn(Context context) => delegate.parseOn(context); @override - int fastParseOn(String buffer, int position) => + int fastParseOn(Buffer buffer, int position) => delegate.fastParseOn(buffer, position); @override diff --git a/petitparser/lib/src/definition/reference.dart b/petitparser/lib/src/definition/reference.dart index a4d430c9..2476136c 100644 --- a/petitparser/lib/src/definition/reference.dart +++ b/petitparser/lib/src/definition/reference.dart @@ -1,5 +1,6 @@ import 'package:meta/meta.dart'; +import '../../buffer.dart'; import '../context/context.dart'; import '../context/result.dart'; import '../core/parser.dart'; @@ -44,7 +45,7 @@ class Reference extends Parser { throw UnsupportedError('References cannot be parsed.'); @override - int fastParseOn(String buffer, int position) => + int fastParseOn(Buffer buffer, int position) => throw UnsupportedError('References cannot be parsed.'); @override diff --git a/petitparser/lib/src/matcher/accept.dart b/petitparser/lib/src/matcher/accept.dart index 8b6b3ee6..cb120a6c 100644 --- a/petitparser/lib/src/matcher/accept.dart +++ b/petitparser/lib/src/matcher/accept.dart @@ -1,3 +1,4 @@ +import '../../buffer.dart'; import '../core/parser.dart'; extension AcceptParser on Parser { @@ -5,5 +6,6 @@ extension AcceptParser on Parser { /// /// For example, `letter().plus().accept('abc')` returns `true`, and /// `letter().plus().accept('123')` returns `false`. - bool accept(String input) => fastParseOn(input, 0) >= 0; + bool accept(/*Buffer|String|Characters*/ dynamic input) => + fastParseOn(Buffer(input), 0) >= 0; } diff --git a/petitparser/lib/src/matcher/matches.dart b/petitparser/lib/src/matcher/matches.dart index ff5b02aa..48e13238 100644 --- a/petitparser/lib/src/matcher/matches.dart +++ b/petitparser/lib/src/matcher/matches.dart @@ -1,3 +1,4 @@ +import '../../buffer.dart'; import '../core/parser.dart'; import '../parser/action/map.dart'; import '../parser/combinator/and.dart'; @@ -13,14 +14,14 @@ extension MatchesParser on Parser { /// 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 matches(String input) { + List matches(/*Buffer|String|Characters*/ dynamic input) { final list = []; and() .map(list.add, hasSideEffects: true) .seq(any()) .or(any()) .star() - .fastParseOn(input, 0); + .fastParseOn(Buffer(input), 0); return list; } } diff --git a/petitparser/lib/src/matcher/matches_skipping.dart b/petitparser/lib/src/matcher/matches_skipping.dart index f7b93b2c..8483fc65 100644 --- a/petitparser/lib/src/matcher/matches_skipping.dart +++ b/petitparser/lib/src/matcher/matches_skipping.dart @@ -1,3 +1,4 @@ +import '../../buffer.dart'; import '../core/parser.dart'; import '../parser/action/map.dart'; import '../parser/combinator/choice.dart'; @@ -11,9 +12,12 @@ extension MatchesSkippingParser on Parser { /// For example, `letter().plus().matchesSkipping('abc de')` results in the /// list `[['a', 'b', 'c'], ['d', 'e']]`. See [matches] to retrieve /// overlapping parse results. - List matchesSkipping(String input) { + List matchesSkipping(/*Buffer|String|Characters*/ dynamic input) { final list = []; - map(list.add, hasSideEffects: true).or(any()).star().fastParseOn(input, 0); + map(list.add, hasSideEffects: true) + .or(any()) + .star() + .fastParseOn(Buffer(input), 0); return list; } } diff --git a/petitparser/lib/src/matcher/pattern/parser_pattern.dart b/petitparser/lib/src/matcher/pattern/parser_pattern.dart index d3f3e253..269925cb 100644 --- a/petitparser/lib/src/matcher/pattern/parser_pattern.dart +++ b/petitparser/lib/src/matcher/pattern/parser_pattern.dart @@ -1,5 +1,6 @@ import 'package:meta/meta.dart'; +import '../../../buffer.dart'; import '../../core/parser.dart'; import 'parser_match.dart'; import 'pattern_iterable.dart'; @@ -31,7 +32,7 @@ class ParserPattern implements Pattern { /// Returns `null` if the pattern doesn't match. @override ParserMatch? matchAsPrefix(String string, [int start = 0]) { - final end = parser.fastParseOn(string, start); + final end = parser.fastParseOn(Buffer.fromString(string), start); return end < 0 ? null : ParserMatch(this, string, start, end); } } diff --git a/petitparser/lib/src/matcher/pattern/pattern_iterator.dart b/petitparser/lib/src/matcher/pattern/pattern_iterator.dart index 88ea8ba3..94a887ce 100644 --- a/petitparser/lib/src/matcher/pattern/pattern_iterator.dart +++ b/petitparser/lib/src/matcher/pattern/pattern_iterator.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../core/parser.dart'; import 'parser_match.dart'; import 'parser_pattern.dart'; @@ -6,17 +7,19 @@ class PatternIterator extends Iterator { final ParserPattern pattern; final Parser parser; final String input; + final Buffer buffer; int start; - PatternIterator(this.pattern, this.parser, this.input, this.start); + PatternIterator(this.pattern, this.parser, this.input, this.start) + : buffer = Buffer.fromString(input); @override late ParserMatch current; @override bool moveNext() { - while (start <= input.length) { - final end = parser.fastParseOn(input, start); + while (start <= buffer.length) { + final end = parser.fastParseOn(buffer, start); if (end < 0) { start++; } else { diff --git a/petitparser/lib/src/parser/action/cast.dart b/petitparser/lib/src/parser/action/cast.dart index 7d999bb6..dc4cb0cf 100644 --- a/petitparser/lib/src/parser/action/cast.dart +++ b/petitparser/lib/src/parser/action/cast.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -23,7 +24,7 @@ class CastParser extends DelegateParser { } @override - int fastParseOn(String buffer, int position) => + int fastParseOn(Buffer buffer, int position) => delegate.fastParseOn(buffer, position); @override diff --git a/petitparser/lib/src/parser/action/cast_list.dart b/petitparser/lib/src/parser/action/cast_list.dart index 15b8b61b..cdbebf3b 100644 --- a/petitparser/lib/src/parser/action/cast_list.dart +++ b/petitparser/lib/src/parser/action/cast_list.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -24,7 +25,7 @@ class CastListParser extends DelegateParser> { } @override - int fastParseOn(String buffer, int position) => + int fastParseOn(Buffer buffer, int position) => delegate.fastParseOn(buffer, position); @override diff --git a/petitparser/lib/src/parser/action/flatten.dart b/petitparser/lib/src/parser/action/flatten.dart index 1431297c..c3cfb0fa 100644 --- a/petitparser/lib/src/parser/action/flatten.dart +++ b/petitparser/lib/src/parser/action/flatten.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -46,7 +47,7 @@ class FlattenParser extends DelegateParser { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer buffer, int position) { return delegate.fastParseOn(buffer, position); } diff --git a/petitparser/lib/src/parser/action/map.dart b/petitparser/lib/src/parser/action/map.dart index 8d5f163f..2d9585bd 100644 --- a/petitparser/lib/src/parser/action/map.dart +++ b/petitparser/lib/src/parser/action/map.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -41,7 +42,7 @@ class MapParser extends DelegateParser { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer 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) diff --git a/petitparser/lib/src/parser/action/permute.dart b/petitparser/lib/src/parser/action/permute.dart index 397835c7..954a2b47 100644 --- a/petitparser/lib/src/parser/action/permute.dart +++ b/petitparser/lib/src/parser/action/permute.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -38,7 +39,7 @@ class PermuteParser extends DelegateParser> { } @override - int fastParseOn(String buffer, int position) => + int fastParseOn(Buffer buffer, int position) => delegate.fastParseOn(buffer, position); @override diff --git a/petitparser/lib/src/parser/action/pick.dart b/petitparser/lib/src/parser/action/pick.dart index 679556d7..f56880ec 100644 --- a/petitparser/lib/src/parser/action/pick.dart +++ b/petitparser/lib/src/parser/action/pick.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -33,7 +34,7 @@ class PickParser extends DelegateParser { } @override - int fastParseOn(String buffer, int position) => + int fastParseOn(Buffer buffer, int position) => delegate.fastParseOn(buffer, position); @override diff --git a/petitparser/lib/src/parser/action/token.dart b/petitparser/lib/src/parser/action/token.dart index 2485cc3d..aef65f01 100644 --- a/petitparser/lib/src/parser/action/token.dart +++ b/petitparser/lib/src/parser/action/token.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -32,7 +33,7 @@ class TokenParser extends DelegateParser> { } @override - int fastParseOn(String buffer, int position) => + int fastParseOn(Buffer buffer, int position) => delegate.fastParseOn(buffer, position); @override diff --git a/petitparser/lib/src/parser/action/trimming.dart b/petitparser/lib/src/parser/action/trimming.dart index ed6e3f40..6d54d726 100644 --- a/petitparser/lib/src/parser/action/trimming.dart +++ b/petitparser/lib/src/parser/action/trimming.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -49,12 +50,12 @@ class TrimmingParser extends DelegateParser { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer 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) { + int trim_(Parser parser, Buffer buffer, int position) { for (;;) { final result = parser.fastParseOn(buffer, position); if (result < 0) { diff --git a/petitparser/lib/src/parser/character/parser.dart b/petitparser/lib/src/parser/character/parser.dart index 9fd6ccdb..9fbf27c3 100644 --- a/petitparser/lib/src/parser/character/parser.dart +++ b/petitparser/lib/src/parser/character/parser.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -15,15 +16,14 @@ class CharacterParser extends Parser { Result 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); + return position < buffer.length && + predicate.test(buffer.codeUnitAt(position)) + ? context.success(buffer.charAt(position), position + 1) + : context.failure(message); } @override - int fastParseOn(String buffer, int position) => + int fastParseOn(Buffer buffer, int position) => position < buffer.length && predicate.test(buffer.codeUnitAt(position)) ? position + 1 : -1; diff --git a/petitparser/lib/src/parser/combinator/and.dart b/petitparser/lib/src/parser/combinator/and.dart index c690f67e..fe985638 100644 --- a/petitparser/lib/src/parser/combinator/and.dart +++ b/petitparser/lib/src/parser/combinator/and.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -30,7 +31,7 @@ class AndParser extends DelegateParser { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer buffer, int position) { final result = delegate.fastParseOn(buffer, position); return result < 0 ? -1 : position; } diff --git a/petitparser/lib/src/parser/combinator/choice.dart b/petitparser/lib/src/parser/combinator/choice.dart index fa47da0e..6645014e 100644 --- a/petitparser/lib/src/parser/combinator/choice.dart +++ b/petitparser/lib/src/parser/combinator/choice.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -49,7 +50,7 @@ class ChoiceParser extends ListParser { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer buffer, int position) { var result = -1; for (var i = 0; i < children.length; i++) { result = children[i].fastParseOn(buffer, position); diff --git a/petitparser/lib/src/parser/combinator/not.dart b/petitparser/lib/src/parser/combinator/not.dart index cd190853..dcce38d4 100644 --- a/petitparser/lib/src/parser/combinator/not.dart +++ b/petitparser/lib/src/parser/combinator/not.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/failure.dart'; import '../../context/result.dart'; @@ -48,7 +49,7 @@ class NotParser extends DelegateParser> { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer buffer, int position) { final result = delegate.fastParseOn(buffer, position); return result < 0 ? position : -1; } diff --git a/petitparser/lib/src/parser/combinator/optional.dart b/petitparser/lib/src/parser/combinator/optional.dart index 73f55c4b..4460601a 100644 --- a/petitparser/lib/src/parser/combinator/optional.dart +++ b/petitparser/lib/src/parser/combinator/optional.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -38,7 +39,7 @@ class OptionalParser extends DelegateParser { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer buffer, int position) { final result = delegate.fastParseOn(buffer, position); return result < 0 ? position : result; } diff --git a/petitparser/lib/src/parser/combinator/sequence.dart b/petitparser/lib/src/parser/combinator/sequence.dart index b744038a..0e7c3e64 100644 --- a/petitparser/lib/src/parser/combinator/sequence.dart +++ b/petitparser/lib/src/parser/combinator/sequence.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -47,7 +48,7 @@ class SequenceParser extends ListParser> { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer buffer, int position) { for (var i = 0; i < children.length; i++) { position = children[i].fastParseOn(buffer, position); if (position < 0) { diff --git a/petitparser/lib/src/parser/combinator/settable.dart b/petitparser/lib/src/parser/combinator/settable.dart index b6442e16..5b4ac598 100644 --- a/petitparser/lib/src/parser/combinator/settable.dart +++ b/petitparser/lib/src/parser/combinator/settable.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -37,7 +38,7 @@ class SettableParser extends DelegateParser { Result parseOn(Context context) => delegate.parseOn(context) as Result; @override - int fastParseOn(String buffer, int position) => + int fastParseOn(Buffer buffer, int position) => delegate.fastParseOn(buffer, position); @override diff --git a/petitparser/lib/src/parser/misc/eof.dart b/petitparser/lib/src/parser/misc/eof.dart index e5a4bc70..9ba61db3 100644 --- a/petitparser/lib/src/parser/misc/eof.dart +++ b/petitparser/lib/src/parser/misc/eof.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -32,7 +33,7 @@ class EndOfInputParser extends Parser { : context.success(null); @override - int fastParseOn(String buffer, int position) => + int fastParseOn(Buffer buffer, int position) => position < buffer.length ? -1 : position; @override diff --git a/petitparser/lib/src/parser/misc/epsilon.dart b/petitparser/lib/src/parser/misc/epsilon.dart index 030318fd..ab651dbe 100644 --- a/petitparser/lib/src/parser/misc/epsilon.dart +++ b/petitparser/lib/src/parser/misc/epsilon.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -21,7 +22,7 @@ class EpsilonParser extends Parser { Result parseOn(Context context) => context.success(result); @override - int fastParseOn(String buffer, int position) => position; + int fastParseOn(Buffer buffer, int position) => position; @override EpsilonParser copy() => EpsilonParser(result); diff --git a/petitparser/lib/src/parser/misc/failure.dart b/petitparser/lib/src/parser/misc/failure.dart index 8518d858..dcf89663 100644 --- a/petitparser/lib/src/parser/misc/failure.dart +++ b/petitparser/lib/src/parser/misc/failure.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -18,7 +19,7 @@ class FailureParser extends Parser { Result parseOn(Context context) => context.failure(message); @override - int fastParseOn(String buffer, int position) => -1; + int fastParseOn(Buffer buffer, int position) => -1; @override String toString() => '${super.toString()}[$message]'; diff --git a/petitparser/lib/src/parser/misc/position.dart b/petitparser/lib/src/parser/misc/position.dart index 571025c7..04895ea9 100644 --- a/petitparser/lib/src/parser/misc/position.dart +++ b/petitparser/lib/src/parser/misc/position.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -13,7 +14,7 @@ class PositionParser extends Parser { Result parseOn(Context context) => context.success(context.position); @override - int fastParseOn(String buffer, int position) => position; + int fastParseOn(Buffer buffer, int position) => position; @override PositionParser copy() => PositionParser(); diff --git a/petitparser/lib/src/parser/predicate/any.dart b/petitparser/lib/src/parser/predicate/any.dart index 08a2782a..bdc9bdf2 100644 --- a/petitparser/lib/src/parser/predicate/any.dart +++ b/petitparser/lib/src/parser/predicate/any.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -21,12 +22,12 @@ class AnyParser extends Parser { final position = context.position; final buffer = context.buffer; return position < buffer.length - ? context.success(buffer[position], position + 1) + ? context.success(buffer.charAt(position), position + 1) : context.failure(message); } @override - int fastParseOn(String buffer, int position) => + int fastParseOn(Buffer buffer, int position) => position < buffer.length ? position + 1 : -1; @override diff --git a/petitparser/lib/src/parser/predicate/predicate.dart b/petitparser/lib/src/parser/predicate/predicate.dart index 0821104f..4d133422 100644 --- a/petitparser/lib/src/parser/predicate/predicate.dart +++ b/petitparser/lib/src/parser/predicate/predicate.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -8,9 +9,8 @@ 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 predicate(int length, Predicate predicate, String message) { - return PredicateParser(length, predicate, message); -} +Parser predicate(int length, Predicate predicate, String message) => + PredicateParser(length, predicate, message); /// A parser for a literal satisfying a predicate. class PredicateParser extends Parser { @@ -28,10 +28,11 @@ class PredicateParser extends Parser { @override Result parseOn(Context context) { + final buffer = context.buffer; final start = context.position; final stop = start + length; - if (stop <= context.buffer.length) { - final result = context.buffer.substring(start, stop); + if (stop <= buffer.length) { + final result = buffer.substring(start, stop); if (predicate(result)) { return context.success(result, stop); } @@ -40,7 +41,7 @@ class PredicateParser extends Parser { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer buffer, int position) { final stop = position + length; return stop <= buffer.length && predicate(buffer.substring(position, stop)) ? stop diff --git a/petitparser/lib/src/parser/repeater/greedy.dart b/petitparser/lib/src/parser/repeater/greedy.dart index 784dc358..696e0a92 100644 --- a/petitparser/lib/src/parser/repeater/greedy.dart +++ b/petitparser/lib/src/parser/repeater/greedy.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -84,7 +85,7 @@ class GreedyRepeatingParser extends LimitedRepeatingParser { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer buffer, int position) { var count = 0; var current = position; while (count < min) { diff --git a/petitparser/lib/src/parser/repeater/lazy.dart b/petitparser/lib/src/parser/repeater/lazy.dart index 573c368c..6e68499e 100644 --- a/petitparser/lib/src/parser/repeater/lazy.dart +++ b/petitparser/lib/src/parser/repeater/lazy.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -77,7 +78,7 @@ class LazyRepeatingParser extends LimitedRepeatingParser { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer buffer, int position) { var count = 0; var current = position; while (count < min) { diff --git a/petitparser/lib/src/parser/repeater/possessive.dart b/petitparser/lib/src/parser/repeater/possessive.dart index 62022a6d..ad81abbd 100644 --- a/petitparser/lib/src/parser/repeater/possessive.dart +++ b/petitparser/lib/src/parser/repeater/possessive.dart @@ -1,3 +1,4 @@ +import '../../../buffer.dart'; import '../../context/context.dart'; import '../../context/result.dart'; import '../../core/parser.dart'; @@ -75,7 +76,7 @@ class PossessiveRepeatingParser extends RepeatingParser { } @override - int fastParseOn(String buffer, int position) { + int fastParseOn(Buffer buffer, int position) { var count = 0; var current = position; while (count < min) { diff --git a/petitparser/pubspec.yaml b/petitparser/pubspec.yaml index 144632af..761bb93a 100644 --- a/petitparser/pubspec.yaml +++ b/petitparser/pubspec.yaml @@ -8,6 +8,7 @@ description: A dynamic parser framework to build efficient grammars and parsers environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: + characters: ^1.1.0-nullsafety meta: ^1.3.0-nullsafety dev_dependencies: test: ^1.16.0-nullsafety diff --git a/petitparser/test/context_test.dart b/petitparser/test/context_test.dart index 2bfe22d1..b038d209 100644 --- a/petitparser/test/context_test.dart +++ b/petitparser/test/context_test.dart @@ -4,8 +4,8 @@ import 'package:test/test.dart'; import 'test_utils.dart'; void main() { - const buffer = 'a\nc'; - const context = Context(buffer, 0); + final buffer = Buffer('a\nc'); + final context = Context(buffer, 0); test('context', () { expect(context.buffer, buffer); expect(context.position, 0); diff --git a/petitparser/test/parser_test.dart b/petitparser/test/parser_test.dart index bca704d9..8573d12e 100644 --- a/petitparser/test/parser_test.dart +++ b/petitparser/test/parser_test.dart @@ -201,9 +201,10 @@ void main() { final parser = digit().plus().token(); expectFailure(parser, ''); expectFailure(parser, 'a'); - final token = parser.parse('123').value; + final buffer = Buffer('123'); + final token = parser.parse(buffer).value; expect(token.value, ['1', '2', '3']); - expect(token.buffer, '123'); + expect(token.buffer, buffer); expect(token.start, 0); expect(token.stop, 3); expect(token.input, '123'); @@ -212,7 +213,7 @@ void main() { expect(token.column, 1); expect(token.toString(), 'Token[1:1]: [1, 2, 3]'); }); - const buffer = '1\r12\r\n123\n1234'; + final buffer = Buffer('1\r12\r\n123\n1234'); final parser = any().map((value) => value.codeUnitAt(0)).token().star(); final result = parser.parse(buffer).value; test('value', () { diff --git a/petitparser/test/test_utils.dart b/petitparser/test/test_utils.dart index 8e276be5..2f1c4fc7 100644 --- a/petitparser/test/test_utils.dart +++ b/petitparser/test/test_utils.dart @@ -6,9 +6,10 @@ const isFailure = TypeMatcher(); const isParserException = TypeMatcher(); -void expectSuccess(Parser parser, String input, dynamic expected, +void expectSuccess(Parser parser, dynamic input, dynamic expected, [int? position]) { - final result = parser.parse(input); + final buffer = Buffer(input); + final result = parser.parse(buffer); expect(result, isSuccess); expect(result.isSuccess, isTrue, reason: 'Expected Result.isSuccess to be true.'); @@ -16,17 +17,19 @@ void expectSuccess(Parser parser, String input, dynamic expected, reason: 'Expected Result.isFailure to be false.'); expect(result.value, expected, reason: 'Expected Result.value to match $expected.'); - expect(result.position, position ?? input.length, - reason: 'Expected Result.position to match ${position ?? input.length}.'); - expect(parser.fastParseOn(input, 0), result.position, + expect(result.position, position ?? buffer.length, + reason: + 'Expected Result.position to match ${position ?? buffer.length}.'); + expect(parser.fastParseOn(buffer, 0), result.position, reason: 'Expected fast parsed result to succeed at same position.'); - expect(parser.accept(input), isTrue, + expect(parser.accept(buffer), isTrue, reason: 'Expected input to be accepted.'); } -void expectFailure(Parser parser, String input, +void expectFailure(Parser parser, dynamic input, [int position = 0, String? message]) { - final result = parser.parse(input); + final buffer = Buffer(input); + final result = parser.parse(buffer); expect(result, isFailure); expect(result.isFailure, isTrue, reason: 'Expected Result.isFailure to be true.'); @@ -38,9 +41,9 @@ void expectFailure(Parser parser, String input, expect(result.message, message, reason: 'Expected Result.message to match $message.'); } - expect(parser.fastParseOn(input, 0), -1, + expect(parser.fastParseOn(buffer, 0), -1, reason: 'Expected fast parse to fail.'); - expect(parser.accept(input), isFalse, + expect(parser.accept(buffer), isFalse, reason: 'Expected input to be rejected.'); expect( () => result.value, diff --git a/petitparser_examples/bin/core_benchmark.dart b/petitparser_examples/bin/core_benchmark.dart index f46c1343..2996749a 100644 --- a/petitparser_examples/bin/core_benchmark.dart +++ b/petitparser_examples/bin/core_benchmark.dart @@ -1,47 +1,45 @@ +import 'package:characters/characters.dart'; import 'package:petitparser/petitparser.dart'; import 'package:petitparser_examples/json.dart'; import 'benchmark.dart'; -// Character tests - -Function charTest(List inputs, Parser parser) { - return (fast) { - if (fast) { - return () { - for (var i = 0; i < inputs.length; i++) { - parser.accept(inputs[i]); +class Benchmark { + final String name; + final Parser parser; + final List inputs; + + Benchmark(this.name, this.parser, this.inputs); + + double measure({bool isFast = false, bool isCharacters = false}) { + final buffers = isCharacters + ? inputs + .map((input) => Buffer.fromCharacters(input.characters)) + .toList(growable: false) + : inputs + .map((input) => Buffer.fromString(input)) + .toList(growable: false); + if (isFast) { + return benchmark(() { + for (var i = 0; i < buffers.length; i++) { + parser.accept(buffers[i]); } - }; + }); } else { - return () { - for (var i = 0; i < inputs.length; i++) { - parser.parse(inputs[i]); + return benchmark(() { + for (var i = 0; i < buffers.length; i++) { + parser.parse(buffers[i]); } - }; + }); } - }; + } } final List characters = List.generate(0xff, (value) => String.fromCharCode(value)); -// String tests - -Function stringTest(String input, Parser parser, {bool fast = false}) { - return (fast) { - if (fast) { - return () => parser.accept(input); - } else { - return () => parser.parse(input).isSuccess; - } - }; -} - final String string = characters.join(); -// JSON tests - final JsonParser json = JsonParser(); const String jsonEvent = @@ -59,81 +57,90 @@ const String jsonEvent = '"TEXT": 1073741824, "ALT_MASK": 1, "CONTROL_MASK": 2, ' '"SHIFT_MASK": 4, "META_MASK": 8}'; -// All benchmarks - -final Map benchmarks = { +final List benchmarks = [ // char tests - 'any()': charTest(characters, any()), - "anyOf('uncopyrightable')": charTest(characters, anyOf('uncopyrightable')), - "char('a')": charTest(characters, char('a')), - 'digit()': charTest(characters, digit()), - 'failure()': charTest(characters, failure()), - 'letter()': charTest(characters, letter()), - 'lowercase()': charTest(characters, lowercase()), - "noneOf('uncopyrightable')": charTest(characters, noneOf('uncopyrightable')), - "pattern('^a')": charTest(characters, pattern('^a')), - "pattern('^a-cx-zA-CX-Z1-37-9')": - charTest(characters, pattern('^a-cx-zA-CX-Z1-37-9')), - "pattern('^a-z')": charTest(characters, pattern('^a-z')), - "pattern('^acegik')": charTest(characters, pattern('^acegik')), - "pattern('a')": charTest(characters, pattern('a')), - "pattern('a-cx-zA-CX-Z1-37-9')": - charTest(characters, pattern('a-cx-zA-CX-Z1-37-9')), - "pattern('a-z')": charTest(characters, pattern('a-z')), - "pattern('acegik')": charTest(characters, pattern('acegik')), - "range('a', 'z')": charTest(characters, range('a', 'z')), - 'uppercase()': charTest(characters, uppercase()), - 'whitespace()': charTest(characters, whitespace()), - 'word()': charTest(characters, word()), + Benchmark("anyOf('uncopyrightable')", anyOf('uncopyrightable'), characters), + Benchmark("char('a')", char('a'), characters), + Benchmark("noneOf('uncopyrightable')", noneOf('uncopyrightable'), characters), + Benchmark("pattern('^a')", pattern('^a'), characters), + Benchmark("pattern('^a-cx-zA-CX-Z1-37-9')", pattern('^a-cx-zA-CX-Z1-37-9'), + characters), + Benchmark("pattern('^a-z')", pattern('^a-z'), characters), + Benchmark("pattern('^acegik')", pattern('^acegik'), characters), + Benchmark("pattern('a')", pattern('a'), characters), + Benchmark("pattern('a-cx-zA-CX-Z1-37-9')", pattern('a-cx-zA-CX-Z1-37-9'), + characters), + Benchmark("pattern('a-z')", pattern('a-z'), characters), + Benchmark("pattern('acegik')", pattern('acegik'), characters), + Benchmark("range('a', 'z')", range('a', 'z'), characters), + Benchmark('any()', any(), characters), + Benchmark('digit()', digit(), characters), + Benchmark('failure()', failure(), characters), + Benchmark('letter()', letter(), characters), + Benchmark('lowercase()', lowercase(), characters), + Benchmark('uppercase()', uppercase(), characters), + Benchmark('whitespace()', whitespace(), characters), + Benchmark('word()', word(), characters), // combinator tests - 'optional()': charTest(characters, any().optional()), - 'and()': charTest(characters, any().and()), - 'not()': charTest(characters, any().not()), - 'neg()': charTest(characters, any().neg()), - 'flatten()': charTest(characters, any().flatten()), - 'token()': charTest(characters, any().token()), - 'trim()': charTest(characters, any().trim()), - 'end()': charTest(characters, any().end()), - 'set()': charTest(characters, any().settable()), - 'map()': charTest(characters, any().map((_) => null)), - 'cast()': charTest(characters, any().cast()), - 'castList()': charTest(characters, any().star().castList()), - 'pick()': charTest(characters, any().star().pick(0)), - 'permute()': charTest(characters, any().star().permute([0])), - 'or()': charTest(characters, failure().or(any()).star()), + Benchmark('optional()', any().optional(), characters), + Benchmark('and()', any().and(), characters), + Benchmark('not()', any().not(), characters), + Benchmark('neg()', any().neg(), characters), + Benchmark('flatten()', any().flatten(), characters), + Benchmark('token()', any().token(), characters), + Benchmark('trim()', any().trim(), characters), + Benchmark('end()', any().end(), characters), + Benchmark('set()', any().settable(), characters), + Benchmark('map()', any().map((_) => null), characters), + Benchmark('cast()', any().cast(), characters), + Benchmark('castList()', any().star().castList(), characters), + Benchmark('pick()', any().star().pick(0), characters), + Benchmark('permute()', any().star().permute([0]), characters), + Benchmark('or()', failure().or(any()).star(), characters), // repeater tests - 'star()': stringTest(string, any().star()), - 'starGreedy()': stringTest(string, any().starGreedy(failure())), - 'starLazy()': stringTest(string, any().starLazy(failure())), - 'plus()': stringTest(string, any().plus()), - 'plusGreedy()': stringTest(string, any().plusGreedy(failure())), - 'plusLazy()': stringTest(string, any().plusLazy(failure())), - 'times()': stringTest(string, any().times(string.length)), - 'seq()': stringTest( - string, - List.filled(string.length, any()).toSequenceParser(), - ), + Benchmark('star()', any().star(), [string]), + Benchmark('starGreedy()', any().starGreedy(failure()), [string]), + Benchmark('starLazy()', any().starLazy(failure()), [string]), + Benchmark('plus()', any().plus(), [string]), + Benchmark('plusGreedy()', any().plusGreedy(failure()), [string]), + Benchmark('plusLazy()', any().plusLazy(failure()), [string]), + Benchmark('times()', any().times(string.length), [string]), + Benchmark( + 'seq()', SequenceParser(List.filled(string.length, any())), [string]), // composite - 'JsonParser()': (fast) { - if (fast) { - return () => json.fastParseOn(jsonEvent, 0); - } else { - return () => json.parse(jsonEvent); - } - }, -}; + Benchmark('JsonParser()', json, [jsonEvent]), +]; void main() { - print('Name\tparseOn\tfastParseOn\tChange'); - for (final entry in benchmarks.entries) { - final parseOnTime = benchmark(entry.value(false)); - final fastParseOnTime = benchmark(entry.value(true)); - print('${entry.key}\t' - '${parseOnTime.toStringAsFixed(3)}\t' - '${fastParseOnTime.toStringAsFixed(3)}\t' - '${percentChange(parseOnTime, fastParseOnTime).round()}%'); + print([ + 'Name', + 'Normal String', + 'Fast String', + 'Normal Characters', + 'Fast Characters', + 'Normal String -> Fast String %', + 'Normal Characters -> Fast Characters %', + 'Normal String -> Normal Characters %', + 'Fast String -> Fast Characters %', + ].join('\t')); + for (final benchmark in benchmarks) { + final normalString = benchmark.measure(isFast: false, isCharacters: false); + final fastString = benchmark.measure(isFast: true, isCharacters: false); + final normalChars = benchmark.measure(isFast: false, isCharacters: true); + final fastChars = benchmark.measure(isFast: true, isCharacters: true); + print([ + '${benchmark.name}', + '${normalString.toStringAsFixed(3)}', + '${fastString.toStringAsFixed(3)}', + '${normalChars.toStringAsFixed(3)}', + '${fastChars.toStringAsFixed(3)}', + '${percentChange(normalString, fastString).round()}%', + '${percentChange(normalChars, fastChars).round()}%', + '${percentChange(normalString, normalChars).round()}%', + '${percentChange(fastString, fastChars).round()}%', + ].join('\t')); } }