├── example └── README.md ├── lib ├── bibtex.dart ├── pascal.dart ├── src │ ├── json │ │ ├── types.dart │ │ ├── encoding.dart │ │ └── definition.dart │ ├── lisp │ │ ├── quote.dart │ │ ├── types.dart │ │ ├── name.dart │ │ ├── evaluator.dart │ │ ├── parser.dart │ │ ├── cons.dart │ │ ├── environment.dart │ │ ├── standard.dart │ │ ├── grammar.dart │ │ └── native.dart │ ├── bibtex │ │ ├── model.dart │ │ └── definition.dart │ ├── uri │ │ ├── query.dart │ │ └── authority.dart │ ├── math │ │ ├── common.dart │ │ ├── ast.dart │ │ └── parser.dart │ ├── regexp │ │ ├── pattern.dart │ │ ├── nfa.dart │ │ ├── parser.dart │ │ └── node.dart │ ├── smalltalk │ │ ├── visitor.dart │ │ ├── ast.dart │ │ ├── objects.dart │ │ ├── parser.dart │ │ └── grammar.dart │ └── prolog │ │ ├── parser.dart │ │ ├── grammar.dart │ │ └── evaluator.dart ├── math.dart ├── smalltalk.dart ├── dart.dart ├── regexp.dart ├── prolog.dart ├── lisp.dart ├── json.dart ├── uri.dart └── tabular.dart ├── .gitignore ├── AUTHORS ├── .github ├── dependabot.yml └── workflows │ └── dart.yml ├── bin ├── prolog │ ├── family.pl │ └── prolog.dart ├── benchmark │ ├── suites │ │ ├── misc_benchmark.dart │ │ ├── predicate_benchmark.dart │ │ ├── action_benchmark.dart │ │ ├── repeat_benchmark.dart │ │ ├── combinator_benchmark.dart │ │ ├── json_benchmark.dart │ │ ├── regexp_benchmark.dart │ │ ├── example_benchmark.dart │ │ └── character_benchmark.dart │ ├── benchmark.sh │ ├── utils │ │ ├── benchmark.dart │ │ └── runner.dart │ └── benchmark.dart └── lisp │ └── lisp.dart ├── pubspec.yaml ├── web ├── uri │ ├── uri.html │ └── uri.dart ├── math │ ├── eval.html │ ├── plot.html │ ├── eval.dart │ └── plot.dart ├── lisp │ ├── lisp.html │ └── lisp.dart ├── tabular │ ├── tabular.html │ └── tabular.dart ├── prolog │ ├── prolog.dart │ └── prolog.html ├── smalltalk │ ├── smalltalk.html │ └── smalltalk.dart ├── json │ ├── json.html │ └── json.dart ├── index.html └── xml │ ├── xml.html │ └── xml.dart ├── .vscode └── launch.json ├── CHANGELOG.md ├── test ├── utils │ └── expect.dart ├── math_test.dart ├── bibtex_test.dart ├── uri_test.dart ├── prolog_test.dart └── tabular_test.dart ├── LICENSE ├── analysis_options.yaml └── README.md /example/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /lib/bibtex.dart: -------------------------------------------------------------------------------- 1 | export 'src/bibtex/definition.dart'; 2 | export 'src/bibtex/model.dart'; 3 | -------------------------------------------------------------------------------- /lib/pascal.dart: -------------------------------------------------------------------------------- 1 | /// This package contains the grammar of Pascal. 2 | library; 3 | 4 | export 'src/pascal/grammar.dart'; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .dart_tool/ 3 | .DS_Store 4 | .idea/ 5 | .packages 6 | .pub/ 7 | petitparser_examples/build 8 | pubspec.lock 9 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Lukas Renggli (https://www.lukas-renggli.ch) and contributors (https://github.com/renggli/dart-petitparser/graphs/contributors). 2 | -------------------------------------------------------------------------------- /lib/src/json/types.dart: -------------------------------------------------------------------------------- 1 | /// Type definition for JSON data. 2 | typedef JSON = Object? /* Map|List|String|bool|num|Null */; 3 | -------------------------------------------------------------------------------- /lib/src/lisp/quote.dart: -------------------------------------------------------------------------------- 1 | /// A quoted datum. 2 | class Quote { 3 | /// Constructs as a quote. 4 | Quote(this.datum); 5 | 6 | /// The quoted datum. 7 | dynamic datum; 8 | } 9 | -------------------------------------------------------------------------------- /lib/math.dart: -------------------------------------------------------------------------------- 1 | /// This package contains a simple expression parser. 2 | library; 3 | 4 | export 'src/math/ast.dart'; 5 | export 'src/math/common.dart'; 6 | export 'src/math/parser.dart'; 7 | -------------------------------------------------------------------------------- /lib/src/json/encoding.dart: -------------------------------------------------------------------------------- 1 | /// Escape characters of JSON strings. 2 | const Map jsonEscapeChars = { 3 | '\\': '\\', 4 | '/': '/', 5 | '"': '"', 6 | 'b': '\b', 7 | 'f': '\f', 8 | 'n': '\n', 9 | 'r': '\r', 10 | 't': '\t', 11 | }; 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | - package-ecosystem: "pub" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" -------------------------------------------------------------------------------- /lib/smalltalk.dart: -------------------------------------------------------------------------------- 1 | /// This package contains the complete grammar of Smalltalk. 2 | /// 3 | /// It was automatically exported from PetitParser for Smalltalk. 4 | library; 5 | 6 | export 'src/smalltalk/ast.dart'; 7 | export 'src/smalltalk/grammar.dart'; 8 | export 'src/smalltalk/parser.dart'; 9 | export 'src/smalltalk/visitor.dart'; 10 | -------------------------------------------------------------------------------- /lib/dart.dart: -------------------------------------------------------------------------------- 1 | /// This package contains the grammar of the Dart programming language. 2 | /// 3 | /// The grammar is adapted from [Dart programming language specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-408/). 4 | /// Unfortunately, it is unable to parse all valid Dart programs yet. 5 | library; 6 | 7 | export 'src/dart/grammar.dart'; 8 | -------------------------------------------------------------------------------- /lib/src/lisp/types.dart: -------------------------------------------------------------------------------- 1 | import 'environment.dart'; 2 | 3 | /// Lambda function type in the Dart world. 4 | typedef Lambda = dynamic Function(Environment env, dynamic args); 5 | 6 | /// Type of printer function to output text on the console. 7 | typedef Printer = void Function(Object? object); 8 | 9 | /// Default printer to output text on the console. 10 | Printer printer = print; 11 | -------------------------------------------------------------------------------- /lib/regexp.dart: -------------------------------------------------------------------------------- 1 | /// This package contains a simple parser and evaluator for Regular Expressions. 2 | // 3 | // Based on the following blog posts: 4 | // - http://xysun.github.io/posts/regex-parsing-thompsons-algorithm.html 5 | // - https://deniskyashif.com/2019/02/17/implementing-a-regular-expression-engine/. 6 | library; 7 | 8 | export 'src/regexp/nfa.dart'; 9 | export 'src/regexp/node.dart'; 10 | export 'src/regexp/parser.dart'; 11 | -------------------------------------------------------------------------------- /lib/prolog.dart: -------------------------------------------------------------------------------- 1 | /// This package contains a simple grammar and evaluator for Prolog based on 2 | /// this blog post: https://curiosity-driven.org/prolog-interpreter. 3 | /// 4 | /// The code is reasonably complete to run and evaluate reasonably complex 5 | /// programs from the console or the web browser. 6 | library; 7 | 8 | export 'src/prolog/evaluator.dart'; 9 | export 'src/prolog/grammar.dart'; 10 | export 'src/prolog/parser.dart'; 11 | -------------------------------------------------------------------------------- /bin/prolog/family.pl: -------------------------------------------------------------------------------- 1 | father_child(massimo, ridge). 2 | father_child(eric, thorne). 3 | father_child(thorne, alexandria). 4 | 5 | mother_child(stephanie, thorne). 6 | mother_child(stephanie, kristen). 7 | mother_child(stephanie, felicia). 8 | 9 | parent_child(X, Y) :- father_child(X, Y). 10 | parent_child(X, Y) :- mother_child(X, Y). 11 | 12 | sibling(X, Y) :- parent_child(Z, X), parent_child(Z, Y). 13 | 14 | ancestor(X, Y) :- parent_child(X, Y). 15 | ancestor(X, Y) :- parent_child(X, Z), ancestor(Z, Y). -------------------------------------------------------------------------------- /lib/lisp.dart: -------------------------------------------------------------------------------- 1 | /// This package contains a simple grammar and evaluator for LISP. 2 | /// 3 | /// The code is reasonably complete to run and evaluate reasonably complex 4 | /// programs from the console or the web browser. 5 | library; 6 | 7 | export 'src/lisp/cons.dart'; 8 | export 'src/lisp/environment.dart'; 9 | export 'src/lisp/evaluator.dart'; 10 | export 'src/lisp/grammar.dart'; 11 | export 'src/lisp/name.dart'; 12 | export 'src/lisp/native.dart'; 13 | export 'src/lisp/parser.dart'; 14 | export 'src/lisp/standard.dart'; 15 | export 'src/lisp/types.dart'; 16 | -------------------------------------------------------------------------------- /bin/benchmark/suites/misc_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | import '../utils/runner.dart'; 4 | 5 | void main() { 6 | runChars('misc - end', any().end(), success: 1); 7 | runChars('misc - endOfInput', endOfInput(), success: 0); 8 | runChars('misc - epsilon', epsilon(), success: 351); 9 | runChars('misc - epsilonWith', epsilonWith('!'), success: 351); 10 | runChars('misc - failure', failure(), success: 0); 11 | runChars('misc - label', any().labeled('label'), success: 351); 12 | runChars('misc - newline', newline(), success: 2); 13 | runChars('misc - position', position(), success: 351); 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/bibtex/model.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | /// Models a single BibTeX entry. 4 | @immutable 5 | class BibTeXEntry { 6 | const BibTeXEntry({ 7 | required this.type, 8 | required this.key, 9 | required this.fields, 10 | }); 11 | 12 | final String type; 13 | final String key; 14 | final Map fields; 15 | 16 | @override 17 | String toString() { 18 | final buffer = StringBuffer('@$type{$key'); 19 | for (final field in fields.entries) { 20 | buffer.write(',\n\t${field.key} = ${field.value}'); 21 | } 22 | buffer.write('}'); 23 | return buffer.toString(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bin/benchmark/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Verify that there are no failing benchmarks. 4 | mapfile -t FAILURES < <(dart run bin/benchmark/benchmark.dart --no-benchmark | grep -v OK) 5 | if [[ ${#FAILURES[@]} -gt 0 ]]; then 6 | for NAME in "${FAILURES[@]}"; do 7 | echo "- $NAME" 8 | done 9 | exit 1 10 | fi 11 | 12 | # Run all the benchmarks in a separate process. 13 | mapfile -t NAMES < <(dart run bin/benchmark/benchmark.dart --no-benchmark --no-verify) 14 | for NAME in "${NAMES[@]}"; do 15 | dart run --no-enable-asserts bin/benchmark/benchmark.dart --filter="$NAME" --separator=";" --no-human --confidence 16 | sleep 1 17 | done 18 | -------------------------------------------------------------------------------- /lib/src/uri/query.dart: -------------------------------------------------------------------------------- 1 | /// Parsers an URI query. 2 | /// 3 | /// Accepts input of the form "{key[=value]}&...". 4 | library; 5 | 6 | import 'package:petitparser/petitparser.dart'; 7 | 8 | final query = _param 9 | .plusSeparated('&'.toParser()) 10 | .map( 11 | (list) => list.elements.where((each) => each[0] != '' || each[1] != null), 12 | ); 13 | 14 | final _param = seq2( 15 | _paramKey, 16 | seq2('='.toParser(), _paramValue).optional(), 17 | ).map2((key, value) => [key, value?.$2]); 18 | 19 | final _paramKey = pattern('^=&').starString(message: 'param key'); 20 | 21 | final _paramValue = pattern('^&').starString(message: 'param value'); 22 | -------------------------------------------------------------------------------- /lib/json.dart: -------------------------------------------------------------------------------- 1 | /// This package contains a complete implementation of [JSON](https://json.org/). 2 | library; 3 | 4 | import 'src/json/definition.dart'; 5 | import 'src/json/types.dart'; 6 | 7 | export 'src/json/definition.dart'; 8 | export 'src/json/types.dart'; 9 | 10 | /// Internal JSON parser. 11 | final _jsonParser = JsonDefinition().build(); 12 | 13 | /// Converts the given JSON-string [input] to its corresponding object. 14 | /// 15 | /// For example: 16 | /// 17 | /// ```dart 18 | /// final result = parseJson('{"a": 1, "b": [2, 3.4], "c": false}'); 19 | /// print(result.value); // {a: 1, b: [2, 3.4], c: false} 20 | /// ``` 21 | JSON parseJson(String input) => _jsonParser.parse(input).value; 22 | -------------------------------------------------------------------------------- /bin/benchmark/suites/predicate_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | import '../utils/runner.dart'; 4 | 5 | void main() { 6 | runString('predicate - string', string(defaultStringInput)); 7 | runString( 8 | 'predicate - string (ignore case)', 9 | string(defaultStringInput, ignoreCase: true), 10 | ); 11 | runString( 12 | 'predicate - predicate', 13 | predicate(defaultStringInput.length, (_) => true, ''), 14 | ); 15 | runString( 16 | 'predicate - pattern - regexp', 17 | PatternParser(RegExp('.*a.*', dotAll: true), ''), 18 | ); 19 | runString( 20 | 'predicate - pattern - string', 21 | PatternParser(defaultStringInput, ''), 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/lisp/name.dart: -------------------------------------------------------------------------------- 1 | /// An unique symbolic name. 2 | /// 3 | /// This provides essentially the behavior of the built-in [Symbol], but 4 | /// allows access and printing of the underlying string. 5 | class Name { 6 | /// Factory for new symbol cells. 7 | factory Name(String name) => 8 | _interned.putIfAbsent(name, () => Name._internal(name)); 9 | 10 | /// Internal constructor for symbol. 11 | Name._internal(this._name); 12 | 13 | /// The interned symbols. 14 | static final Map _interned = {}; 15 | 16 | /// The name of the symbol. 17 | final String _name; 18 | 19 | /// Returns the string representation of the symbolic name. 20 | @override 21 | String toString() => _name; 22 | } 23 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: petitparser_examples 2 | version: 7.0.0 3 | 4 | homepage: https://petitparser.github.io 5 | repository: https://github.com/petitparser/dart-petitparser-examples 6 | description: A collection of example parsers, applications, and benchmarks that 7 | illustrate the PetitParser framework. 8 | topics: 9 | - grammar 10 | - parser 11 | - parser-combinator 12 | - parsing 13 | - peg 14 | 15 | environment: 16 | sdk: ^3.8.0 17 | dependencies: 18 | args: ^2.7.0 19 | collection: ^1.19.0 20 | data: ^0.15.0 21 | http: ^1.4.0 22 | meta: ^1.16.0 23 | more: ^4.6.0 24 | petitparser: ^7.0.0 25 | web: ^1.1.0 26 | xml: ^6.6.0 27 | dev_dependencies: 28 | build_runner: ^2.4.0 29 | build_web_compilers: ^4.1.0 30 | lints: ^6.0.0 31 | test: ^1.26.0 32 | -------------------------------------------------------------------------------- /bin/benchmark/suites/action_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | import '../utils/runner.dart'; 4 | 5 | void main() { 6 | runChars('action - cast', any().cast()); 7 | runChars('action - castList', any().star().castList()); 8 | runChars( 9 | 'action - continuation', 10 | any().callCC((continuation, context) => continuation(context)), 11 | ); 12 | runChars('action - flatten', any().flatten()); 13 | runChars('action - map', any().map((_) {})); 14 | runChars('action - permute', any().star().permute([0])); 15 | runChars('action - pick', any().star().pick(0)); 16 | runChars('action - token', any().token()); 17 | runChars('action - trim', any().trim(), success: 351); 18 | runChars('action - where', any().where((_) => true)); 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/math/common.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | /// Common mathematical constants. 4 | const constants = {'e': e, 'pi': pi}; 5 | 6 | /// Common mathematical functions (1 argument). 7 | final functions1 = { 8 | 'acos': acos, 9 | 'asin': asin, 10 | 'atan': atan, 11 | 'cos': cos, 12 | 'exp': exp, 13 | 'log': log, 14 | 'sin': sin, 15 | 'sqrt': sqrt, 16 | 'tan': tan, 17 | 'abs': (num x) => x.abs(), 18 | 'ceil': (num x) => x.ceil(), 19 | 'floor': (num x) => x.floor(), 20 | 'round': (num x) => x.round(), 21 | 'sign': (num x) => x.sign, 22 | 'truncate': (num x) => x.truncate(), 23 | }; 24 | 25 | /// Common mathematical functions (2 arguments). 26 | final functions2 = { 27 | 'atan2': (num x, num y) => atan2(x, y), 28 | 'max': (num x, num y) => max(x, y), 29 | 'min': (num x, num y) => min(x, y), 30 | 'pow': (num x, num y) => pow(x, y), 31 | }; 32 | -------------------------------------------------------------------------------- /web/uri/uri.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | URI Parser 6 | 8 | 14 | 15 | 16 |

URI Parser

17 | 18 |

Enter URI

19 |

20 | 21 |

Parsed components

22 |

23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run all Tests", 6 | "type": "dart", 7 | "request": "launch", 8 | "program": "test" 9 | }, 10 | { 11 | "name": "Run Benchmark Tests", 12 | "type": "dart", 13 | "request": "launch", 14 | "program": "bin/benchmark/benchmark.dart", 15 | "args": [ 16 | "--no-benchmark" 17 | ] 18 | }, 19 | { 20 | "name": "Lisp", 21 | "type": "dart", 22 | "request": "launch", 23 | "console": "terminal", 24 | "program": "bin/lisp/lisp.dart" 25 | }, 26 | { 27 | "name": "Prolog", 28 | "type": "dart", 29 | "request": "launch", 30 | "console": "terminal", 31 | "program": "bin/prolog/prolog.dart", 32 | "args": [ 33 | "bin/prolog/family.pl" 34 | ] 35 | }, 36 | ] 37 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 7.0.0 4 | 5 | - Update to Dart 3.8 and PetitParser 7.0. 6 | - Added Pascal, CSV example grammars. 7 | - Numerous fixes and improvements. 8 | - Better testing of grammars. 9 | 10 | ## 6.0.0 11 | 12 | - Update to Dart 3.0 and PetitParser 6.0. 13 | - Cleanup usage of deprecated code. 14 | - Better test coverage. 15 | 16 | ## 5.4.0 17 | 18 | - Upgrade to Dart 2.19 and PetitParser 5.4. 19 | - Add BibTeX and RegExp examples. 20 | - Add XML, XPath, and BibTeX demos. 21 | 22 | ## 5.1.0 23 | 24 | - Dart 2.18 requirement. 25 | - Cleanup the JSON and URL parsers to be fully typed. 26 | 27 | ## 5.0.0 28 | 29 | - Dart 2.16 requirement. 30 | - Cleaned up dynamic typing. 31 | - Updates to latest version of PetitParser. 32 | - Added a mathematical expression example. 33 | 34 | ## 4.4.0 35 | 36 | - Initial version extracted from https://github.com/petitparser/dart-petitparser. 37 | -------------------------------------------------------------------------------- /web/math/eval.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Math Evaluator 6 | 8 | 12 | 13 | 14 |

Math Evaluator

15 | 16 |

Expression

17 | 18 | 19 |

Result

20 |

21 | 22 |

Tree

23 |

24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /web/math/plot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Math Evaluator 6 | 8 | 12 | 13 | 14 |

Math Plotter

15 | 16 |

Function ft(x)

17 | 18 |

19 | 20 |

Graph

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /lib/src/uri/authority.dart: -------------------------------------------------------------------------------- 1 | /// Further parse the URI authority into username, password, hostname and port. 2 | /// 3 | /// Accepts input of the form "[username[:password]@]hostname[:port]". 4 | library; 5 | 6 | import 'package:petitparser/petitparser.dart'; 7 | 8 | final authority = 9 | seq3(_credentials.optional(), _hostname.optional(), _port.optional()).map3( 10 | (credentials, hostname, port) => { 11 | #username: credentials?.$1, 12 | #password: credentials?.$2?.$2, 13 | #hostname: hostname, 14 | #port: port?.$2, 15 | }, 16 | ); 17 | 18 | final _credentials = seq3( 19 | _username, 20 | seq2(':'.toParser(), _password).optional(), 21 | '@'.toParser(), 22 | ); 23 | 24 | final _username = pattern('^:@').plusString(message: 'username'); 25 | 26 | final _password = pattern('^@').plusString(message: 'password'); 27 | 28 | final _hostname = pattern('^:').plusString(message: 'hostname'); 29 | 30 | final _port = seq2(':'.toParser(), digit().plusString(message: 'port')); 31 | -------------------------------------------------------------------------------- /test/utils/expect.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | import 'package:test/expect.dart'; 3 | 4 | /// Expects a parse success. 5 | TypeMatcher isSuccess( 6 | String input, { 7 | dynamic value = anything, 8 | dynamic position, 9 | }) => isA().having( 10 | (parser) => parser.parse(input), 11 | 'parse', 12 | isA() 13 | .having( 14 | (result) => result.value is Token && value is! Token 15 | ? result.value.value 16 | : result.value, 17 | 'result', 18 | value, 19 | ) 20 | .having( 21 | (result) => result.position, 22 | 'position', 23 | position ?? input.length, 24 | ), 25 | ); 26 | 27 | /// Expects a parse failure. 28 | TypeMatcher isFailure( 29 | String input, { 30 | dynamic message = anything, 31 | dynamic position = anything, 32 | }) => isA().having( 33 | (parser) => parser.parse(input), 34 | 'parse', 35 | isA() 36 | .having((result) => result.message, 'message', message) 37 | .having((result) => result.position, 'position', position), 38 | ); 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2006-2025 Lukas Renggli. 4 | All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /web/lisp/lisp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Lisp Interpreter 6 | 8 | 13 | 14 | 15 |

Lisp Interpreter

16 | 17 |

Program

18 | 25 | 26 | 27 |

Output

28 |

29 | 30 |

Console

31 |

32 | 33 |

Environment

34 |

35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /web/tabular/tabular.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tabular Text Parser 6 | 8 | 15 | 16 | 17 |

Tabular Text Parser

18 | 19 | 23 | 24 | 27 | 28 |

Input

29 | 30 | 31 |

Output

32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /bin/prolog/prolog.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:petitparser_examples/prolog.dart'; 5 | 6 | /// Entry point for the command line interpreter. 7 | void main(List arguments) { 8 | // parse arguments 9 | final rules = StringBuffer(); 10 | for (final option in arguments) { 11 | if (option.startsWith('-')) { 12 | if (option == '-?') { 13 | stdout.writeln('${Platform.executable} prolog.dart rules...'); 14 | exit(0); 15 | } else { 16 | stdout.writeln('Unknown option: $option'); 17 | exit(1); 18 | } 19 | } else { 20 | final file = File(option); 21 | if (file.existsSync()) { 22 | rules.writeln(file.readAsStringSync()); 23 | } else { 24 | stdout.writeln('File not found: $option'); 25 | exit(2); 26 | } 27 | } 28 | } 29 | 30 | // evaluation context 31 | final db = Database.parse(rules.toString()); 32 | 33 | // the read-eval loop 34 | stdout.write('?- '); 35 | stdin 36 | .transform(systemEncoding.decoder) 37 | .transform(const LineSplitter()) 38 | .map(Term.parse) 39 | .map((goal) { 40 | db.query(goal).forEach(stdout.writeln); 41 | }) 42 | .forEach((each) => stdout.write('?- ')); 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/lisp/evaluator.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | import 'cons.dart'; 4 | import 'environment.dart'; 5 | import 'name.dart'; 6 | import 'quote.dart'; 7 | 8 | /// The evaluation function. 9 | dynamic eval(Environment env, dynamic expr) { 10 | if (expr is Quote) { 11 | return expr.datum; 12 | } else if (expr is Cons) { 13 | final Function function = eval(env, expr.head); 14 | return function(env, expr.tail); 15 | } else if (expr is Name) { 16 | return env[expr]; 17 | } else { 18 | return expr; 19 | } 20 | } 21 | 22 | /// Evaluate a cons of instructions. 23 | dynamic evalList(Environment env, dynamic expr) { 24 | dynamic result; 25 | while (expr is Cons) { 26 | result = eval(env, expr.head); 27 | expr = expr.tail; 28 | } 29 | return result; 30 | } 31 | 32 | /// The arguments evaluation function. 33 | dynamic evalArguments(Environment env, dynamic args) { 34 | if (args is Cons) { 35 | return Cons(eval(env, args.head), evalArguments(env, args.tail)); 36 | } else { 37 | return null; 38 | } 39 | } 40 | 41 | /// Reads and evaluates a [script]. 42 | dynamic evalString(Parser parser, Environment env, String script) { 43 | dynamic result; 44 | for (final cell in parser.parse(script).value) { 45 | result = eval(env, cell); 46 | } 47 | return result; 48 | } 49 | -------------------------------------------------------------------------------- /web/math/eval.dart: -------------------------------------------------------------------------------- 1 | import 'dart:js_interop'; 2 | 3 | import 'package:petitparser_examples/math.dart'; 4 | import 'package:web/web.dart'; 5 | 6 | final input = document.querySelector('#input') as HTMLInputElement; 7 | final result = document.querySelector('#result') as HTMLElement; 8 | final tree = document.querySelector('#tree') as HTMLElement; 9 | 10 | void update() { 11 | tree.textContent = ''; 12 | try { 13 | final expr = parser.parse(input.value).value; 14 | tree.innerHTML = inspect(expr).toJS; 15 | result.textContent = ' = ${expr.eval({})}'; 16 | result.classList.value = ''; 17 | } on Object catch (exception) { 18 | result.textContent = exception.toString(); 19 | result.classList.add('error'); 20 | } 21 | window.location.hash = Uri.encodeComponent(input.value); 22 | } 23 | 24 | String inspect(Expression expr, [String indent = '']) { 25 | final result = StringBuffer('$indent$expr
'); 26 | if (expr is Application) { 27 | for (final argument in expr.arguments) { 28 | result.write(inspect(argument, '  $indent')); 29 | } 30 | } 31 | return result.toString(); 32 | } 33 | 34 | void main() { 35 | if (window.location.hash.startsWith('#')) { 36 | input.value = Uri.decodeComponent(window.location.hash.substring(1)); 37 | } 38 | update(); 39 | input.onInput.listen((event) => update()); 40 | } 41 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | #analyzer: 3 | # language: 4 | # strict-casts: true 5 | # strict-inference: true 6 | # strict-raw-types: true 7 | linter: 8 | rules: 9 | - annotate_redeclares 10 | # - avoid_dynamic_calls 11 | - avoid_final_parameters 12 | - avoid_print 13 | - avoid_unused_constructor_parameters 14 | - combinators_ordering 15 | - comment_references 16 | - directives_ordering 17 | - invalid_case_patterns 18 | - missing_code_block_language_in_doc_comment 19 | - no_self_assignments 20 | - omit_local_variable_types 21 | - prefer_const_constructors 22 | - prefer_const_constructors_in_immutables 23 | - prefer_const_declarations 24 | - prefer_const_literals_to_create_immutables 25 | - prefer_expression_function_bodies 26 | - prefer_final_in_for_each 27 | - prefer_final_locals 28 | - prefer_if_elements_to_conditional_expressions 29 | - prefer_relative_imports 30 | # - prefer_single_quotes 31 | - remove_deprecations_in_breaking_versions 32 | - unnecessary_await_in_return 33 | - unnecessary_breaks 34 | # - unnecessary_lambdas 35 | - unnecessary_null_aware_operator_on_extension_on_nullable 36 | - unnecessary_null_checks 37 | - unnecessary_parenthesis 38 | - unnecessary_statements 39 | - unreachable_from_main 40 | -------------------------------------------------------------------------------- /lib/src/regexp/pattern.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | abstract class RegexpPattern implements Pattern { 4 | // TODO: make it correctly match sub-strings 5 | @override 6 | Match? matchAsPrefix(String input, [int start = 0]) => 7 | tryMatch(input.substring(start)) 8 | ? RegexpMatch(this, input, start, input.length) 9 | : null; 10 | 11 | @override 12 | Iterable allMatches(String input, [int start = 0]) sync* { 13 | for (var i = start; i < input.length; i++) { 14 | final match = matchAsPrefix(input, i); 15 | if (match != null) yield match; 16 | } 17 | } 18 | 19 | @internal 20 | bool tryMatch(String input); 21 | } 22 | 23 | class RegexpMatch implements Match { 24 | RegexpMatch(this.pattern, this.input, this.start, this.end); 25 | 26 | @override 27 | final Pattern pattern; 28 | 29 | @override 30 | final String input; 31 | 32 | @override 33 | final int start; 34 | 35 | @override 36 | final int end; 37 | 38 | @override 39 | int get groupCount => 0; 40 | 41 | @override 42 | String? group(int group) => this[group]; 43 | 44 | @override 45 | String? operator [](int group) => 46 | group == 0 ? input.substring(start, end) : null; 47 | 48 | @override 49 | List groups(List groupIndices) => 50 | groupIndices.map(group).toList(growable: false); 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/lisp/parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | import 'cons.dart'; 4 | import 'grammar.dart'; 5 | import 'name.dart'; 6 | import 'quote.dart'; 7 | 8 | /// The standard lisp parser definition. 9 | final _definition = LispParserDefinition(); 10 | 11 | /// The standard prolog parser to read rules. 12 | final lispParser = _definition.build(); 13 | 14 | /// LISP parser definition. 15 | class LispParserDefinition extends LispGrammarDefinition { 16 | @override 17 | Parser list() => super.list().map((each) => each[1]); 18 | 19 | @override 20 | Parser cell() => super.cell().map((each) => Cons(each[0], each[1])); 21 | 22 | @override 23 | Parser empty() => super.empty().map((each) => null); 24 | 25 | @override 26 | Parser string() => 27 | super.string().map((each) => String.fromCharCodes(each[1].cast())); 28 | 29 | @override 30 | Parser characterEscape() => 31 | super.characterEscape().map((each) => each[1].codeUnitAt(0)); 32 | 33 | @override 34 | Parser characterRaw() => 35 | super.characterRaw().map((each) => each.codeUnitAt(0)); 36 | 37 | @override 38 | Parser symbol() => super.symbol().map((each) => Name(each)); 39 | 40 | @override 41 | Parser number() => super.number().map((each) => num.parse(each)); 42 | 43 | @override 44 | Parser quote() => super.quote().map((each) => Quote(each[1])); 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/math/ast.dart: -------------------------------------------------------------------------------- 1 | /// An abstract expression that can be evaluated. 2 | abstract class Expression { 3 | /// Evaluates the expression with the provided [variables]. 4 | num eval(Map variables); 5 | } 6 | 7 | /// A value expression. 8 | class Value extends Expression { 9 | Value(this.value); 10 | 11 | final num value; 12 | 13 | @override 14 | num eval(Map variables) => value; 15 | 16 | @override 17 | String toString() => 'Value{$value}'; 18 | } 19 | 20 | /// A variable expression. 21 | class Variable extends Expression { 22 | Variable(this.name); 23 | 24 | final String name; 25 | 26 | @override 27 | num eval(Map variables) => variables.containsKey(name) 28 | ? variables[name]! 29 | : throw ArgumentError.value(name, 'Unknown variable'); 30 | 31 | @override 32 | String toString() => 'Variable{$name}'; 33 | } 34 | 35 | /// A function application. 36 | class Application extends Expression { 37 | Application(this.name, this.arguments, this.function); 38 | 39 | final String name; 40 | final List arguments; 41 | final Function function; 42 | 43 | @override 44 | num eval(Map variables) => Function.apply( 45 | function, 46 | arguments.map((argument) => argument.eval(variables)).toList(), 47 | ); 48 | 49 | @override 50 | String toString() => 'Application{$name}'; 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/lisp/cons.dart: -------------------------------------------------------------------------------- 1 | /// The basic data structure of LISP. 2 | class Cons { 3 | /// Constructs a cons. 4 | Cons([this.car, this.cdr]); 5 | 6 | /// The first object. 7 | dynamic car; 8 | 9 | /// The second object. 10 | dynamic cdr; 11 | 12 | /// The head of the cons. 13 | dynamic get head => car; 14 | 15 | /// The tail of the cons, if applicable. 16 | Cons? get tail { 17 | if (cdr is Cons) { 18 | return cdr; 19 | } else if (cdr == null) { 20 | return null; 21 | } else { 22 | throw StateError('${toString()} does not have a tail.'); 23 | } 24 | } 25 | 26 | @override 27 | bool operator ==(Object other) => 28 | other is Cons && car == other.car && cdr == other.cdr; 29 | 30 | @override 31 | int get hashCode => 31 * car.hashCode + cdr.hashCode; 32 | 33 | @override 34 | String toString() { 35 | final buffer = StringBuffer(); 36 | buffer.write('('); 37 | var current = this; 38 | for (;;) { 39 | buffer.write(current.car); 40 | if (current.cdr is Cons) { 41 | current = current.cdr; 42 | buffer.write(' '); 43 | } else if (current.cdr == null) { 44 | buffer.write(')'); 45 | return buffer.toString(); 46 | } else { 47 | buffer.write(' . '); 48 | buffer.write(current.cdr); 49 | buffer.write(')'); 50 | return buffer.toString(); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/uri.dart: -------------------------------------------------------------------------------- 1 | /// A simple URI parser based on RFC-3986. 2 | /// 3 | /// The accepted inputs and decomposition matches the example given in 4 | /// Appendix B of the standard: https://tools.ietf.org/html/rfc3986#appendix-B. 5 | library; 6 | 7 | import 'package:petitparser/petitparser.dart'; 8 | 9 | import 'src/uri/authority.dart' as lib_authority; 10 | import 'src/uri/query.dart' as lib_query; 11 | 12 | final uri = 13 | seq5( 14 | seq2(_scheme, ':'.toParser()).optional(), 15 | seq2('//'.toParser(), _authority).optional(), 16 | _path, 17 | seq2('?'.toParser(), _query).optional(), 18 | seq2('#'.toParser(), _fragment).optional(), 19 | ).map5( 20 | (scheme, authority, path, query, fragment) => { 21 | #scheme: scheme?.$1, 22 | #authority: authority?.$2, 23 | ...lib_authority.authority.parse(authority?.$2 ?? '').value, 24 | #path: path, 25 | #query: query?.$2, 26 | #params: lib_query.query.parse(query?.$2 ?? '').value, 27 | #fragment: fragment?.$2, 28 | }, 29 | ); 30 | 31 | final _scheme = pattern('^:/?#').plusString(message: 'scheme'); 32 | 33 | final _authority = pattern('^/?#').starString(message: 'authority'); 34 | 35 | final _path = pattern('^?#').starString(message: 'path'); 36 | 37 | final _query = pattern('^#').starString(message: 'query'); 38 | 39 | final _fragment = any().starString(message: 'fragment'); 40 | -------------------------------------------------------------------------------- /lib/src/smalltalk/visitor.dart: -------------------------------------------------------------------------------- 1 | import 'ast.dart'; 2 | 3 | abstract class Visitor { 4 | void visit(Node node) => node.accept(this); 5 | 6 | void visitMethodNode(MethodNode node) { 7 | node.arguments.forEach(visit); 8 | node.pragmas.forEach(visit); 9 | visit(node.body); 10 | } 11 | 12 | void visitPragmaNode(PragmaNode node) { 13 | node.arguments.forEach(visit); 14 | } 15 | 16 | void visitReturnNode(ReturnNode node) { 17 | visit(node.value); 18 | } 19 | 20 | void visitSequenceNode(SequenceNode node) { 21 | node.temporaries.forEach(visit); 22 | node.statements.forEach(visit); 23 | } 24 | 25 | void visitArrayNode(ArrayNode node) { 26 | node.statements.forEach(visit); 27 | } 28 | 29 | void visitAssignmentNode(AssignmentNode node) { 30 | visit(node.variable); 31 | visit(node.value); 32 | } 33 | 34 | void visitBlockNode(BlockNode node) { 35 | node.arguments.forEach(visit); 36 | visit(node.body); 37 | } 38 | 39 | void visitCascadeNode(CascadeNode node) { 40 | node.messages.forEach(visit); 41 | } 42 | 43 | void visitLiteralArrayNode(LiteralArrayNode node) { 44 | node.values.forEach(visit); 45 | } 46 | 47 | void visitLiteralValueNode(LiteralValueNode node) {} 48 | 49 | void visitMessageNode(MessageNode node) { 50 | visit(node.receiver); 51 | node.arguments.forEach(visit); 52 | } 53 | 54 | void visitVariableNode(VariableNode node) {} 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/regexp/nfa.dart: -------------------------------------------------------------------------------- 1 | import 'node.dart'; 2 | import 'pattern.dart'; 3 | 4 | /// Nondeterministic Finite Automaton 5 | class Nfa extends RegexpPattern { 6 | Nfa({required this.start, required this.end}); 7 | 8 | factory Nfa.fromString(String regexp) => Node.fromString(regexp).toNfa(); 9 | 10 | final NfaState start; 11 | final NfaState end; 12 | 13 | @override 14 | bool tryMatch(String input) { 15 | var currentStates = {}; 16 | _addStates(start, currentStates); 17 | for (final value in input.runes) { 18 | final nextStates = {}; 19 | for (final state in currentStates) { 20 | final nextState = state.transitions[value]; 21 | if (nextState != null) { 22 | _addStates(nextState, nextStates); 23 | } 24 | for (final nextState in state.dots) { 25 | _addStates(nextState, nextStates); 26 | } 27 | } 28 | if (nextStates.isEmpty) return false; 29 | currentStates = nextStates; 30 | } 31 | return currentStates.any((state) => state.isEnd); 32 | } 33 | 34 | void _addStates(NfaState state, Set states) { 35 | if (!states.add(state)) return; 36 | for (final other in state.epsilons) { 37 | _addStates(other, states); 38 | } 39 | } 40 | } 41 | 42 | class NfaState { 43 | NfaState({required this.isEnd}); 44 | 45 | bool isEnd; 46 | 47 | final Map transitions = {}; 48 | 49 | final List epsilons = []; 50 | 51 | final List dots = []; 52 | } 53 | -------------------------------------------------------------------------------- /web/prolog/prolog.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser_examples/prolog.dart'; 2 | import 'package:web/web.dart'; 3 | 4 | final rulesElement = document.querySelector('#rules') as HTMLInputElement; 5 | final queryElement = document.querySelector('#query') as HTMLInputElement; 6 | final askElement = document.querySelector('#ask') as HTMLButtonElement; 7 | final answersElement = document.querySelector('#answers') as HTMLElement; 8 | 9 | void main() { 10 | askElement.onClick.listen((event) async { 11 | answersElement.innerText = ''; 12 | 13 | Database? db; 14 | try { 15 | db = Database.parse(rulesElement.value); 16 | } on Object catch (error) { 17 | appendMessage('Error parsing rules: $error', isError: true); 18 | } 19 | 20 | Term? query; 21 | try { 22 | query = Term.parse(queryElement.value); 23 | } on Object catch (error) { 24 | appendMessage('Error parsing query: $error', isError: true); 25 | } 26 | 27 | if (db == null || query == null) { 28 | return; 29 | } 30 | 31 | var hasResult = false; 32 | db.query(query).forEach((item) { 33 | appendMessage(item.toString()); 34 | hasResult = true; 35 | }); 36 | if (!hasResult) { 37 | appendMessage('No'); 38 | } 39 | }); 40 | } 41 | 42 | void appendMessage(String message, {bool isError = false}) { 43 | final element = document.createElement('li'); 44 | element.textContent = message; 45 | if (isError) { 46 | element.classList.add('error'); 47 | } 48 | answersElement.append(element); 49 | } 50 | -------------------------------------------------------------------------------- /bin/benchmark/utils/benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:data/stats.dart'; 2 | 3 | /// Function type to be benchmarked. 4 | typedef Benchmark = void Function(); 5 | 6 | /// Measures the time it takes to run [function] in microseconds. 7 | /// 8 | /// A single measurement is repeated at least [minLoop] times, and for at 9 | /// least [minDuration]. The measurement is sampled [sampleCount] times. 10 | Jackknife benchmark( 11 | Benchmark function, { 12 | int minLoop = 25, 13 | Duration minDuration = const Duration(milliseconds: 100), 14 | int sampleCount = 25, 15 | double confidenceLevel = 0.95, 16 | }) { 17 | // Estimate count and warmup. 18 | var count = minLoop; 19 | while (_benchmark(function, count) <= minDuration) { 20 | count *= 2; 21 | } 22 | if (count == minLoop) { 23 | throw StateError( 24 | '$function cannot be performed $minLoop times ' 25 | 'in $minDuration.', 26 | ); 27 | } 28 | // Collect samples. 29 | final samples = []; 30 | for (var i = 0; i < sampleCount; i++) { 31 | samples.add(_benchmark(function, count).inMicroseconds / count); 32 | } 33 | return Jackknife( 34 | samples, 35 | (list) => list.arithmeticMean(), 36 | confidenceLevel: confidenceLevel, 37 | ); 38 | } 39 | 40 | @pragma('vm:never-inline') 41 | @pragma('vm:unsafe:no-interrupts') 42 | Duration _benchmark(Benchmark function, int count) { 43 | final watch = Stopwatch(); 44 | watch.start(); 45 | var n = count + 0; 46 | while (n-- > 0) { 47 | function(); 48 | } 49 | watch.stop(); 50 | return watch.elapsed; 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | schedule: 9 | - cron: "0 0 * * 0" 10 | workflow_dispatch: 11 | defaults: 12 | run: 13 | shell: bash 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | build: 19 | name: "Dart CI" 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/cache@v5 23 | with: 24 | path: "~/.pub-cache/hosted" 25 | key: "os:ubuntu-latest;pub-cache-hosted;dart:stable" 26 | - uses: actions/checkout@v6 27 | - uses: dart-lang/setup-dart@v1 28 | - name: "Dependencies" 29 | run: dart pub upgrade --no-precompile 30 | - name: "Dart Analyzer" 31 | run: dart analyze --fatal-infos . 32 | - name: "Dart Formatter" 33 | run: dart format --output=none --set-exit-if-changed . 34 | - name: "VM Tests" 35 | run: dart test --platform vm 36 | - name: "Chrome Tests" 37 | run: dart test --platform chrome 38 | - name: "Collect coverage" 39 | run: | 40 | dart pub global activate coverage 41 | dart pub global run coverage:test_with_coverage 42 | - name: "Upload coverage" 43 | uses: codecov/codecov-action@v5 44 | with: 45 | token: ${{ secrets.CODECOV_TOKEN }} 46 | check: 47 | name: "Flutter CI" 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v6 51 | - uses: amake/dart-flutter-compat@v1 52 | with: 53 | channel: stable 54 | cache: true -------------------------------------------------------------------------------- /lib/src/lisp/environment.dart: -------------------------------------------------------------------------------- 1 | import 'name.dart'; 2 | 3 | /// Environment of bindings. 4 | class Environment { 5 | /// Constructor for the nested environment. 6 | Environment([this._owner]) : _bindings = {}; 7 | 8 | /// The owning environment. 9 | final Environment? _owner; 10 | 11 | /// The internal environment bindings. 12 | final Map _bindings; 13 | 14 | /// Constructor for a nested environment. 15 | Environment create() => Environment(this); 16 | 17 | /// Return the binding for [key]. 18 | dynamic operator [](Name key) { 19 | if (_bindings.containsKey(key)) { 20 | return _bindings[key]; 21 | } else if (_owner != null) { 22 | return _owner[key]; 23 | } else { 24 | _invalidBinding(key); 25 | return null; 26 | } 27 | } 28 | 29 | /// Updates the binding for [key] with a [value]. 30 | void operator []=(Name key, dynamic value) { 31 | if (_bindings.containsKey(key)) { 32 | _bindings[key] = value; 33 | } else if (_owner != null) { 34 | _owner[key] = value; 35 | } else { 36 | _invalidBinding(key); 37 | } 38 | } 39 | 40 | /// Defines a new binding from [key] to [value]. 41 | dynamic define(Name key, dynamic value) => _bindings[key] = value; 42 | 43 | /// Returns the keys of the bindings. 44 | Iterable get keys => _bindings.keys; 45 | 46 | /// Returns the parent of the bindings. 47 | Environment? get owner => _owner; 48 | 49 | /// Called when a missing binding is accessed. 50 | void _invalidBinding(Name key) => 51 | throw ArgumentError('Unknown binding for $key'); 52 | } 53 | -------------------------------------------------------------------------------- /web/prolog/prolog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Prolog Interpreter 6 | 8 | 14 | 15 | 16 |

Prolog Interpreter

17 | 18 |

Rules

19 | 36 | 37 |

Query

38 | 39 | 40 | 41 |

Answers

42 |
    43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /lib/src/lisp/standard.dart: -------------------------------------------------------------------------------- 1 | import 'environment.dart'; 2 | import 'evaluator.dart'; 3 | import 'parser.dart'; 4 | 5 | /// The standard library. 6 | class StandardEnvironment extends Environment { 7 | /// Imports the standard library into the [Environment]. 8 | StandardEnvironment(super.owner) { 9 | evalString(lispParser, this, _standardLibrary); 10 | } 11 | 12 | /// A simple standard library, should be moved to external file. 13 | static const String _standardLibrary = """ 14 | ; null functions 15 | (define null '()) 16 | (define (null? x) (= '() x)) 17 | 18 | ; booleans 19 | (define true (and)) 20 | (define false (or)) 21 | 22 | ; list functions 23 | (define (length list) 24 | (if (null? list) 25 | 0 26 | (+ 1 (length (cdr list))))) 27 | 28 | (define (append list1 list2) 29 | (if (null? list1) 30 | list2 31 | (cons (car list1) (append (cdr list1) list2)))) 32 | 33 | (define (list-head list index) 34 | (if (= index 0) 35 | (car list) 36 | (list-head 37 | (cdr list) 38 | (- index 1)))) 39 | 40 | (define (list-tail list index) 41 | (if (= index 0) 42 | (cdr list) 43 | (list-tail 44 | (cdr list) 45 | (- index 1)))) 46 | 47 | (define (for-each list proc) 48 | (while (not (null? list)) 49 | (proc (car list)) 50 | (set! list (cdr list)))) 51 | 52 | (define (map list proc) 53 | (if (null? list) 54 | '() 55 | (cons (proc (car list)) 56 | (map (cdr list) proc)))) 57 | 58 | (define (inject list value proc) 59 | (if (null? list) 60 | value 61 | (inject 62 | (cdr list) 63 | (proc value (car list)) 64 | proc))) 65 | """; 66 | } 67 | -------------------------------------------------------------------------------- /web/smalltalk/smalltalk.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Smalltalk Parser 6 | 8 | 13 | 14 | 15 |

    Smalltalk Parser

    16 | 17 |

    Program

    18 | 34 | 35 | 36 |

    Output

    37 |

    38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /lib/src/regexp/parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/definition.dart'; 2 | import 'package:petitparser/expression.dart'; 3 | import 'package:petitparser/parser.dart'; 4 | 5 | import 'node.dart'; 6 | 7 | final nodeParser = () { 8 | final builder = ExpressionBuilder(); 9 | 10 | const meta = r'\.()!*+?|&'; 11 | builder 12 | ..primitive(noneOf(meta).map(LiteralNode.new)) 13 | ..primitive(anyOf(meta).skip(before: char(r'\')).map(LiteralNode.new)) 14 | ..primitive(char('.').map((_) => DotNode())); 15 | 16 | builder.group().wrapper(char('('), char(')'), (_, value, _) => value); 17 | 18 | final integer = digit().plusString().trim().map(int.parse); 19 | final range = 20 | seq3(integer.optional(), char(',').trim().optional(), integer.optional()) 21 | .skip(before: char('{'), after: char('}')) 22 | .map3( 23 | (min, comma, max) => 24 | (min ?? 0, max ?? (comma == null ? min ?? 0 : null)), 25 | ); 26 | 27 | builder.group() 28 | ..prefix(char('!'), (_, exp) => ComplementNode(exp)) 29 | ..postfix(char('*'), (exp, _) => QuantificationNode(exp, 0)) 30 | ..postfix(char('+'), (exp, _) => QuantificationNode(exp, 1)) 31 | ..postfix(char('?'), (exp, _) => QuantificationNode(exp, 0, 1)) 32 | ..postfix( 33 | range, 34 | (exp, range) => QuantificationNode(exp, range.$1, range.$2), 35 | ); 36 | 37 | builder.group() 38 | ..left(epsilon(), (left, _, right) => ConcatenationNode(left, right)) 39 | ..optional(EmptyNode()); 40 | 41 | builder.group() 42 | ..left(char('|'), (left, _, right) => AlternationNode(left, right)) 43 | ..left(char('&'), (left, _, right) => IntersectionNode(left, right)); 44 | 45 | return resolve(builder.build()).end(); 46 | }(); 47 | -------------------------------------------------------------------------------- /bin/benchmark/suites/repeat_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | import '../utils/runner.dart'; 4 | 5 | final length = defaultStringInput.length; 6 | final half = length ~/ 2; 7 | final last = defaultStringInput[length - 1]; 8 | 9 | void main() { 10 | runString('repeat - star', any().star()); 11 | runString('repeat - plus', any().plus()); 12 | runString('repeat - times', any().times(length)); 13 | runString('repeat - repeat', any().repeat(half, length)); 14 | 15 | runString( 16 | 'repeat - starGreedy', 17 | any().starGreedy(char(last)), 18 | position: length - 1, 19 | ); 20 | runString( 21 | 'repeat - plusGreedy', 22 | any().plusGreedy(char(last)), 23 | position: length - 1, 24 | ); 25 | runString( 26 | 'repeat - timesGreedy', 27 | any().repeatGreedy(char(last), half, length), 28 | position: length - 1, 29 | ); 30 | 31 | runString( 32 | 'repeat - starLazy', 33 | any().starLazy(char(last)), 34 | position: length - 1, 35 | ); 36 | runString( 37 | 'repeat - plusLazy', 38 | any().plusLazy(char(last)), 39 | position: length - 1, 40 | ); 41 | runString( 42 | 'repeat - timesLazy', 43 | any().repeatLazy(char(last), half, length), 44 | position: length - 1, 45 | ); 46 | 47 | runString('repeat - starSeparated', any().starSeparated(epsilon())); 48 | runString('repeat - plusSeparated', any().plusSeparated(epsilon())); 49 | runString('repeat - timesSeparated', any().timesSeparated(epsilon(), length)); 50 | runString( 51 | 'repeat - repeatSeparated', 52 | any().repeatSeparated(epsilon(), half, length), 53 | ); 54 | 55 | runString('repeat - starString', any().starString()); 56 | runString('repeat - plusString', any().plusString()); 57 | runString('repeat - timesString', any().timesString(length)); 58 | runString('repeat - repeatString', any().repeatString(half, length)); 59 | } 60 | -------------------------------------------------------------------------------- /web/json/json.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSON Parser 6 | 8 | 13 | 14 | 15 |

    JSON Parser

    16 | 17 |

    Input

    18 |

    19 | 43 | 44 |

    45 | 46 |

    Output

    47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
    PetitParserNative
    Timing:
    Output:
    68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /web/lisp/lisp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:js_interop'; 2 | 3 | import 'package:petitparser_examples/lisp.dart'; 4 | import 'package:web/web.dart'; 5 | 6 | final input = document.querySelector('#input') as HTMLInputElement; 7 | final output = document.querySelector('#output') as HTMLElement; 8 | final console = document.querySelector('#console') as HTMLElement; 9 | final environment = document.querySelector('#environment') as HTMLElement; 10 | final evaluate = document.querySelector('#evaluate') as HTMLButtonElement; 11 | 12 | final root = NativeEnvironment(); 13 | final standard = StandardEnvironment(root); 14 | final user = standard.create(); 15 | 16 | void main() { 17 | printer = (object) { 18 | console.append(document.createTextNode(object.toString())); 19 | console.append(document.createElement('br')); 20 | }; 21 | evaluate.onClick.listen((event) { 22 | output.textContent = 'Evaluating...'; 23 | output.classList.value = ''; 24 | console.textContent = ''; 25 | try { 26 | final result = evalString(lispParser, user, input.value); 27 | output.textContent = result.toString(); 28 | } on Object catch (exception) { 29 | output.textContent = exception.toString(); 30 | output.classList.add('error'); 31 | } 32 | inspect(environment, user); 33 | }); 34 | inspect(environment, user); 35 | } 36 | 37 | void inspect(Element element, Environment? environment) { 38 | final buffer = StringBuffer(); 39 | while (environment != null) { 40 | if (environment.keys.isNotEmpty) { 41 | buffer.write('
      '); 42 | for (final symbol in environment.keys) { 43 | var object = environment[symbol]; 44 | if (object is Function) { 45 | object = '($symbol ...)'; 46 | } 47 | buffer.write('
    • $symbol: $object
    • '); 48 | } 49 | buffer.write('
    '); 50 | buffer.write('
    '); 51 | } 52 | environment = environment.owner; 53 | } 54 | element.innerHTML = buffer.toString().toJS; 55 | } 56 | -------------------------------------------------------------------------------- /web/json/json.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' as convert; 2 | 3 | import 'package:petitparser_examples/json.dart'; 4 | import 'package:web/web.dart'; 5 | 6 | final parser = JsonDefinition().build(); 7 | 8 | void execute( 9 | String value, 10 | HTMLElement timingElement, 11 | HTMLElement outputElement, 12 | dynamic Function(String value) parse, 13 | ) { 14 | Object? result; 15 | var count = 0, elapsed = 0; 16 | final watch = Stopwatch()..start(); 17 | while (elapsed < 100000) { 18 | try { 19 | result = parse(value); 20 | } on Exception catch (exception) { 21 | result = exception; 22 | } 23 | elapsed = watch.elapsedMicroseconds; 24 | count++; 25 | } 26 | final timing = (elapsed / count).round(); 27 | 28 | timingElement.innerText = '$timingμs'; 29 | if (result is Exception) { 30 | outputElement.classList.add('error'); 31 | outputElement.innerText = result is FormatException 32 | ? result.message 33 | : result.toString(); 34 | } else { 35 | outputElement.classList.remove('error'); 36 | outputElement.innerText = convert.json.encode(result); 37 | } 38 | } 39 | 40 | final input = document.querySelector('#input') as HTMLTextAreaElement; 41 | final action = document.querySelector('#action') as HTMLButtonElement; 42 | 43 | final timingCustom = document.querySelector('#timing .custom') as HTMLElement; 44 | final timingNative = document.querySelector('#timing .native') as HTMLElement; 45 | final outputCustom = document.querySelector('#output .custom') as HTMLElement; 46 | final outputNative = document.querySelector('#output .native') as HTMLElement; 47 | 48 | void update() { 49 | execute( 50 | input.value, 51 | timingCustom, 52 | outputCustom, 53 | (input) => parser.parse(input).value, 54 | ); 55 | execute( 56 | input.value, 57 | timingNative, 58 | outputNative, 59 | (input) => convert.json.decode(input), 60 | ); 61 | } 62 | 63 | void main() { 64 | action.onClick.listen((event) => update()); 65 | update(); 66 | } 67 | -------------------------------------------------------------------------------- /bin/benchmark/suites/combinator_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | import '../utils/runner.dart'; 4 | 5 | void main() { 6 | runChars('combinator - and', any().and(), success: 351); 7 | for (var i = 2; i < 10; i++) { 8 | final parsers = [...List.filled(i - 1, failure()), any()]; 9 | runChars('combinator - or [$i]', parsers.toChoiceParser(), success: 351); 10 | } 11 | runChars('combinator - not', any().not(), success: 0); 12 | runChars('combinator - neg', any().neg(), success: 0); 13 | runChars('combinator - optional', any().optional(), success: 351); 14 | runChars('combinator - optionalWith', any().optionalWith('!'), success: 351); 15 | for (var i = 2; i < 256; i *= 2) { 16 | runString( 17 | 'combinator - seq [${i.toString().padLeft(3, '0')}]', 18 | List.filled(i, any()).toSequenceParser(), 19 | position: i, 20 | ); 21 | } 22 | runString('combinator - seq2', seq2(any(), any()), position: 2); 23 | runString('combinator - seq3', seq3(any(), any(), any()), position: 3); 24 | runString('combinator - seq4', seq4(any(), any(), any(), any()), position: 4); 25 | runString( 26 | 'combinator - seq5', 27 | seq5(any(), any(), any(), any(), any()), 28 | position: 5, 29 | ); 30 | runString( 31 | 'combinator - seq6', 32 | seq6(any(), any(), any(), any(), any(), any()), 33 | position: 6, 34 | ); 35 | runString( 36 | 'combinator - seq7', 37 | seq7(any(), any(), any(), any(), any(), any(), any()), 38 | position: 7, 39 | ); 40 | runString( 41 | 'combinator - seq8', 42 | seq8(any(), any(), any(), any(), any(), any(), any(), any()), 43 | position: 8, 44 | ); 45 | runString( 46 | 'combinator - seq9', 47 | seq9(any(), any(), any(), any(), any(), any(), any(), any(), any()), 48 | position: 9, 49 | ); 50 | runChars('combinator - settable', any().settable(), success: 351); 51 | runChars( 52 | 'combinator - skip', 53 | any().skip(before: epsilon(), after: epsilon()), 54 | success: 351, 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/lisp/grammar.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | /// LISP grammar definition. 4 | class LispGrammarDefinition extends GrammarDefinition { 5 | @override 6 | Parser start() => ref0(atom).star().end(); 7 | 8 | Parser atom() => ref0(atomChoice).trim(ref0(space)); 9 | Parser atomChoice() => 10 | ref0(list) | 11 | ref0(number) | 12 | ref0(string) | 13 | ref0(symbol) | 14 | ref0(quote) | 15 | ref0(quasiquote) | 16 | ref0(unquote) | 17 | ref0(splice); 18 | 19 | Parser list() => 20 | ref2(bracket, '()', ref0(cells)) | 21 | ref2(bracket, '[]', ref0(cells)) | 22 | ref2(bracket, '{}', ref0(cells)); 23 | Parser cells() => ref0(cell) | ref0(empty); 24 | Parser cell() => ref0(atom) & ref0(cells); 25 | Parser empty() => ref0(space).star(); 26 | 27 | Parser number() => ref0(numberToken).flatten(message: 'Number expected'); 28 | Parser numberToken() => 29 | anyOf('-+').optional() & 30 | char('0').or(digit().plus()) & 31 | char('.').seq(digit().plus()).optional() & 32 | anyOf('eE').seq(anyOf('-+').optional()).seq(digit().plus()).optional(); 33 | 34 | Parser string() => ref2(bracket, '""', ref0(character).star()); 35 | Parser character() => ref0(characterEscape) | ref0(characterRaw); 36 | Parser characterEscape() => char('\\') & any(); 37 | Parser characterRaw() => pattern('^"'); 38 | 39 | Parser symbol() => ref0(symbolToken).flatten(message: 'Symbol expected'); 40 | Parser symbolToken() => 41 | pattern('a-zA-Z!#\$%&*/:<=>?@\\^_|~+-') & 42 | pattern('a-zA-Z0-9!#\$%&*/:<=>?@\\^_|~+-').star(); 43 | 44 | Parser quote() => char("'") & ref0(atom); 45 | Parser quasiquote() => char('`') & ref0(list); 46 | Parser unquote() => char(',') & ref0(list); 47 | Parser splice() => char('@') & ref0(list); 48 | 49 | Parser space() => whitespace() | ref0(comment); 50 | Parser comment() => char(';') & Token.newlineParser().neg().star(); 51 | Parser bracket(String brackets, Parser parser) => 52 | char(brackets[0]) & parser & char(brackets[1]); 53 | } 54 | -------------------------------------------------------------------------------- /web/uri/uri.dart: -------------------------------------------------------------------------------- 1 | import 'dart:js_interop'; 2 | 3 | import 'package:petitparser/petitparser.dart'; 4 | import 'package:petitparser_examples/uri.dart'; 5 | import 'package:web/web.dart'; 6 | 7 | final input = document.querySelector('#input') as HTMLInputElement; 8 | final output = document.querySelector('#output') as HTMLElement; 9 | 10 | void update() { 11 | final result = uri.parse(input.value); 12 | if (result is Success) { 13 | output.innerHTML = 14 | ''' 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ${result.value[#params].map((each) => ''' 49 | 50 | 51 | 52 | 53 | ''').join()} 54 | 55 | 56 | 57 | 58 |
    Scheme${result.value[#scheme]}
    Authority${result.value[#authority]}
    Username${result.value[#username]}
    Password${result.value[#password]}
    Hostname${result.value[#hostname]}
    Port${result.value[#port]}
    Path${result.value[#path]}
    Query${result.value[#query]}
    ${each[0]}${each[1]}
    Fragment${result.value[#fragment]}
    59 | ''' 60 | .toJS; 61 | } else { 62 | output.innerHTML = 63 | ''' 64 | 65 | Error at ${result.position}: ${result.message} 66 | 67 | ''' 68 | .toJS; 69 | } 70 | } 71 | 72 | void main() { 73 | input.onInput.listen((event) => update()); 74 | input.value = window.location.href; 75 | update(); 76 | } 77 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PetitParser Examples 6 | 7 | 8 | 9 |

    Interactive PetitParser Examples

    10 |
    11 |
    JSON Parser
    12 |
    13 | Compares the native JSON parser against the one written with PetitParser. 14 |
    15 | 16 |
    Lisp Interpreter
    17 |
    18 | A reasonably complete interpreter that is able to parse and evaluate complex programs. 19 |
    20 | 21 |
    Math Evaluator
    22 |
    23 | Parses and evaluates mathematical expressions. 24 |
    25 | 26 |
    Math Plotter
    27 |
    28 | Parses and plots mathematical expressions. 29 |
    30 | 31 |
    Prolog Interpreter
    32 |
    33 | A reasonably complete interpreter that is able to parse and evaluate basic logic programs. 34 |
    35 | 36 |
    Smalltalk Parser
    37 |
    38 | Contains the beginnings of a Smalltalk Interpreter. Currently only the parsed AST is printed. 39 |
    40 | 41 |
    Tabular Text Parser
    42 |
    43 | Parseres comma-separated or tab-separated tabular textual data. 44 |
    45 | 46 |
    URI Parser
    47 |
    48 | Parses and decomposes URIs into their individual components. 49 |
    50 | 51 |
    XML Parser
    52 |
    53 | Parses XML files to events, creates and pretty-prints a DOM tree, and evaluates XPath expressions. 54 |
    55 |
    56 | 57 | 58 | -------------------------------------------------------------------------------- /lib/src/prolog/parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | import 'evaluator.dart'; 4 | import 'grammar.dart'; 5 | 6 | /// The standard prolog parser definition. 7 | final _definition = PrologParserDefinition(); 8 | 9 | /// The standard prolog parser to read rules. 10 | final Parser> rulesParser = _definition 11 | .buildFrom(_definition.rules()) 12 | .end(); 13 | 14 | /// The standard prolog parser to read queries. 15 | final Parser termParser = _definition.buildFrom(_definition.term()).end(); 16 | 17 | /// Prolog parser definition. 18 | class PrologParserDefinition extends PrologGrammarDefinition { 19 | final Map scope = {}; 20 | 21 | @override 22 | Parser> rules() => super.rules().castList(); 23 | 24 | @override 25 | Parser rule() => super.rule().map((each) { 26 | scope.clear(); 27 | final head = each[0]; 28 | final rest = each[1]; 29 | if (rest == null) { 30 | return Rule(head, const True()); 31 | } 32 | final List terms = rest[1]; 33 | if (terms.isEmpty) { 34 | return Rule(head, const True()); 35 | } else if (terms.length == 1) { 36 | return Rule(head, terms[0]); 37 | } else { 38 | return Rule(head, Conjunction(terms.cast())); 39 | } 40 | }); 41 | 42 | @override 43 | Parser term() => super.term().map((each) { 44 | final name = each[0]; 45 | final rest = each[1]; 46 | if (rest == null) { 47 | return Term(name.toString(), const []); 48 | } 49 | final List terms = rest[1]; 50 | return Term(name.toString(), terms.cast()); 51 | }); 52 | 53 | @override 54 | Parser parameter() => super.parameter().map((each) { 55 | final name = each[0]; 56 | final rest = each[1]; 57 | if (rest == null) { 58 | return name; 59 | } 60 | final List terms = rest[1]; 61 | return Term(name.toString(), terms.cast()); 62 | }); 63 | 64 | @override 65 | Parser variable() => super.variable().map((name) { 66 | if (name == '_') return Variable(name); 67 | return scope.putIfAbsent(name, () => Variable(name)); 68 | }); 69 | 70 | @override 71 | Parser value() => super.value().map((name) => Value(name)); 72 | } 73 | -------------------------------------------------------------------------------- /web/tabular/tabular.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/core.dart'; 2 | import 'package:petitparser_examples/tabular.dart'; 3 | import 'package:web/web.dart'; 4 | 5 | final options = document.querySelector('#options') as HTMLSelectElement; 6 | final example = document.querySelector('#example') as HTMLButtonElement; 7 | final input = document.querySelector('#input') as HTMLTextAreaElement; 8 | final output = document.querySelector('#output') as HTMLDivElement; 9 | 10 | const examples = { 11 | 'CSV': 12 | 'Los Angeles,34°03′N,118°15′W\n' 13 | 'New York City,40°42′46″N,74°00′21″W\n' 14 | 'Paris,48°51′24″N,2°21′03″E', 15 | 'TSV': 16 | 'Sepal length Sepal width Petal length Petal width Species\n' 17 | '5.1 3.5 1.4 0.2 I. setosa\n' 18 | '4.9 3.0 1.4 0.2 I. setosa\n' 19 | '4.7 3.2 1.3 0.2 I. setosa\n' 20 | '4.6 3.1 1.5 0.2 I. setosa\n' 21 | '5.0 3.6 1.4 0.2 I. setosa', 22 | }; 23 | final parsers = { 24 | 'CSV': TabularDefinition.csv().build(), 25 | 'TSV': TabularDefinition.tsv().build(), 26 | }; 27 | 28 | T getOption(Map mapping) { 29 | for (final MapEntry(:key, :value) in mapping.entries) { 30 | if (options.value.startsWith(key)) { 31 | return value; 32 | } 33 | } 34 | throw ArgumentError.value(mapping, 'mapping'); 35 | } 36 | 37 | void loadExample() { 38 | input.value = getOption(examples); 39 | updateOutput(); 40 | } 41 | 42 | void updateOutput() { 43 | final parser = getOption(parsers); 44 | try { 45 | final result = parser.parse(input.value).value; 46 | final table = document.createElement('table'); 47 | for (final row in result) { 48 | final tableRow = document.createElement('tr'); 49 | for (final cell in row) { 50 | final tableCell = document.createElement('td'); 51 | tableCell.textContent = cell; 52 | tableRow.appendChild(tableCell); 53 | } 54 | table.appendChild(tableRow); 55 | } 56 | output.textContent = ''; 57 | output.appendChild(table); 58 | output.classList.remove('error'); 59 | } on ParserException catch (exception) { 60 | output.textContent = 61 | '${exception.message} at ${exception.failure.toPositionString()}'; 62 | output.classList.add('error'); 63 | } 64 | } 65 | 66 | void main() { 67 | options.onChange.listen((_) => updateOutput()); 68 | example.onClick.listen((_) => loadExample()); 69 | input.onInput.listen((_) => updateOutput()); 70 | loadExample(); 71 | } 72 | -------------------------------------------------------------------------------- /web/xml/xml.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | XML Parser 6 | 8 | 18 | 19 | 20 | 21 | 22 |

    XML Parser

    23 | 24 |

    Input

    25 |
    26 | 27 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
    51 | 52 |

    Output

    53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
    Events (SAX)DOM with XPath
    67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /test/math_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:petitparser/reflection.dart'; 4 | import 'package:petitparser_examples/math.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | void verify( 8 | String input, 9 | num result, { 10 | Map variables = const {}, 11 | double epsilon = 0.00001, 12 | }) { 13 | final ast = parser.parse(input).value; 14 | expect(ast.eval(variables), closeTo(result, epsilon)); 15 | expect(ast.toString(), isNotNull); 16 | } 17 | 18 | void main() { 19 | test('linter', () { 20 | expect(linter(parser, excludedTypes: {}), isEmpty); 21 | }); 22 | test('number', () { 23 | verify('0', 0); 24 | verify('42', 42); 25 | verify('3.141', 3.141); 26 | verify('1.2e5', 1.2e5); 27 | verify('3.4e-1', 3.4e-1); 28 | }); 29 | test('variable', () { 30 | verify('x', 42, variables: {'x': 42}); 31 | verify('x / y', 0.5, variables: {'x': 1, 'y': 2}); 32 | expect(() => verify('x', double.nan, variables: {}), throwsArgumentError); 33 | }); 34 | test('constants', () { 35 | verify('pi', pi); 36 | verify('e', e); 37 | }); 38 | test('functions (1 arg)', () { 39 | verify('acos(0.5)', acos(0.5)); 40 | verify('asin(0.5)', asin(0.5)); 41 | verify('atan(0.5)', atan(0.5)); 42 | verify('cos(7)', cos(7)); 43 | verify('exp(7)', exp(7)); 44 | verify('log(7)', log(7)); 45 | verify('sin(7)', sin(7)); 46 | verify('sqrt(2)', sqrt(2)); 47 | verify('tan(7)', tan(7)); 48 | verify('abs(-1)', 1); 49 | verify('ceil(1.2)', 2); 50 | verify('floor(1.2)', 1); 51 | verify('round(1.6)', 2); 52 | verify('sign(-2)', -1); 53 | verify('truncate(-1.2)', -1); 54 | }); 55 | test('functions (2 args)', () { 56 | verify('atan2(2, 3)', atan2(2, 3)); 57 | verify('max(2, 3)', max(2, 3)); 58 | verify('min(2, 3)', min(2, 3)); 59 | verify('pow(2, 3)', pow(2, 3)); 60 | }); 61 | test('prefix', () { 62 | verify('+2', 2); 63 | verify('-pi', -pi); 64 | }); 65 | test('power', () { 66 | verify('2 ^ 3', 8); 67 | verify('2 ^ -3', 0.125); 68 | verify('-2 ^ 3', -8); 69 | verify('-2 ^ -3', -0.125); 70 | }); 71 | test('multiply', () { 72 | verify('2 * 3', 6); 73 | verify('2 * 3 * 4', 24); 74 | verify('6 / 3', 2); 75 | verify('6 / 3 / 2', 1); 76 | }); 77 | test('addition', () { 78 | verify('2 + 3', 5); 79 | verify('2 + 3 + 4', 9); 80 | verify('5 - 3', 2); 81 | verify('5 - 3 - 2', 0); 82 | }); 83 | test('priority', () { 84 | verify('1 + 2 * 3', 7); 85 | verify('1 + (2 * 3)', 7); 86 | verify('(1 + 2) * 3', 9); 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/prolog/grammar.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | /// Prolog grammar definition. 4 | class PrologGrammarDefinition extends GrammarDefinition { 5 | @override 6 | Parser start() => throw UnsupportedError('Either parse rules or terms.'); 7 | 8 | Parser rules() => ref0(rule).star(); 9 | Parser rule() => 10 | ref0(term) & 11 | (ref0(definitionToken) & 12 | ref0( 13 | term, 14 | ).plusSeparated(ref0(commaToken)).map((list) => list.elements)) 15 | .optional() & 16 | ref0(terminatorToken); 17 | Parser term() => 18 | ref0(atom) & 19 | (ref0(openParenToken) & 20 | ref0( 21 | parameter, 22 | ).plusSeparated(ref0(commaToken)).map((list) => list.elements) & 23 | ref0(closeParentToken)) 24 | .optional(); 25 | Parser parameter() => 26 | ref0(atom) & 27 | (ref0(openParenToken) & 28 | ref0( 29 | parameter, 30 | ).plusSeparated(ref0(commaToken)).map((list) => list.elements) & 31 | ref0(closeParentToken)) 32 | .optional(); 33 | Parser atom() => ref0(variable) | ref0(value); 34 | 35 | Parser variable() => ref0(variableToken); 36 | Parser value() => ref0(valueToken); 37 | 38 | Parser space() => whitespace() | ref0(commentSingle) | ref0(commentMulti); 39 | Parser commentSingle() => char('%') & Token.newlineParser().neg().star(); 40 | Parser commentMulti() => string('/*').starLazy(string('*/')) & string('*/'); 41 | 42 | Parser token(Object parser, [String? message]) { 43 | if (parser is Parser) { 44 | return parser.flatten(message: message).trim(ref0(space)); 45 | } else if (parser is String) { 46 | return parser 47 | .toParser(message: message ?? '$parser expected') 48 | .trim(ref0(space)); 49 | } else { 50 | throw ArgumentError.value(parser, 'parser', 'Invalid parser type'); 51 | } 52 | } 53 | 54 | Parser variableToken() => ref2( 55 | token, 56 | pattern('A-Z_') & pattern('A-Za-z0-9_').star(), 57 | 'Variable expected', 58 | ); 59 | Parser valueToken() => ref2( 60 | token, 61 | pattern('a-z') & pattern('A-Za-z0-9_').star(), 62 | 'Value expected', 63 | ); 64 | Parser openParenToken() => ref1(token, '('); 65 | Parser closeParentToken() => ref1(token, ')'); 66 | Parser commaToken() => ref1(token, ','); 67 | Parser terminatorToken() => ref1(token, '.'); 68 | Parser definitionToken() => ref1(token, ':-'); 69 | } 70 | -------------------------------------------------------------------------------- /lib/tabular.dart: -------------------------------------------------------------------------------- 1 | /// A simple CSV (comma seperated text) parser. 2 | library; 3 | 4 | import 'package:petitparser/petitparser.dart'; 5 | import 'package:petitparser/petitparser.dart' as pp; 6 | 7 | /// Customizable parser defintion for tabular text files. 8 | class TabularDefinition extends GrammarDefinition>> { 9 | /// Definition for "Comma-separated values" (CSV) input. 10 | factory TabularDefinition.csv() => TabularDefinition( 11 | quote: '"'.toParser(), 12 | escape: '""'.toParser().map((_) => '"'), 13 | delimiter: ','.toParser(), 14 | newline: pp.newline(), 15 | ); 16 | 17 | /// Definition for "Tab-separated values" (TSV) input. 18 | factory TabularDefinition.tsv() => TabularDefinition( 19 | quote: failure(), 20 | escape: seq2(char(r'\'), any()).map2( 21 | (_, value) => switch (value) { 22 | 't' => '\t', 23 | 'n' => '\n', 24 | 'r' => '\r', 25 | _ => value, 26 | }, 27 | ), 28 | delimiter: '\t'.toParser(), 29 | newline: pp.newline(), 30 | ); 31 | 32 | /// Generic constructor for tabular text files. 33 | TabularDefinition({ 34 | required this.quote, 35 | required this.escape, 36 | required this.delimiter, 37 | required this.newline, 38 | }); 39 | 40 | /// Specifies how values are quoted. 41 | final Parser quote; 42 | 43 | /// Specifies how characters are escaped. 44 | final Parser escape; 45 | 46 | /// Sepcifies how values are delimited in a row. 47 | final Parser delimiter; 48 | 49 | /// Specifies how rows are delimited in a file. 50 | final Parser newline; 51 | 52 | @override 53 | Parser>> start() => ref0(_lines).end(); 54 | 55 | Parser>> _lines() => 56 | ref0(_records).starSeparated(newline).map((list) => list.elements); 57 | Parser> _records() => 58 | ref0(_field).starSeparated(delimiter).map((list) => list.elements); 59 | 60 | Parser _field() => 61 | [ref0(_quotedField), ref0(_plainField)].toChoiceParser(); 62 | 63 | Parser _quotedField() => 64 | ref0(_quotedFieldContent).skip(before: quote, after: quote); 65 | Parser _quotedFieldContent() => 66 | ref0(_quotedFieldChar).star().map((list) => list.join()); 67 | Parser _quotedFieldChar() => [escape, quote.neg()].toChoiceParser(); 68 | 69 | Parser _plainField() => ref0(_plainFieldContent); 70 | Parser _plainFieldContent() => 71 | ref0(_plainFieldChar).star().map((list) => list.join()); 72 | Parser _plainFieldChar() => [ 73 | escape, 74 | [delimiter, newline].toChoiceParser().neg(), 75 | ].toChoiceParser(); 76 | } 77 | -------------------------------------------------------------------------------- /bin/benchmark/suites/json_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' as convert; 2 | 3 | import 'package:petitparser/petitparser.dart'; 4 | import 'package:petitparser_examples/json.dart' as json_example; 5 | 6 | import '../utils/runner.dart'; 7 | 8 | final jsonParser = json_example.JsonDefinition().build(); 9 | 10 | const jsonArray = '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]'; 11 | const jsonObject = '{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6, "g": 7}'; 12 | const jsonEvent = ''' 13 | {"type": "change", "eventPhase": 2, "bubbles": true, "cancelable": true, 14 | "timeStamp": 0, "CAPTURING_PHASE": 1, "AT_TARGET": 2, "BUBBLING_PHASE": 3, 15 | "isTrusted": true, "MOUSEDOWN": 1, "MOUSEUP": 2, "MOUSEOVER": 4, "MOUSEOUT": 8, 16 | "MOUSEMOVE": 16, "MOUSEDRAG": 32, "CLICK": 64, "DBLCLICK": 128, "KEYDOWN": 256, 17 | "KEYUP": 512, "KEYPRESS": 1024, "DRAGDROP": 2048, "FOCUS": 4096, "BLUR": 8192, 18 | "SELECT": 16384, "CHANGE": 32768, "RESET": 65536, "SUBMIT": 131072, 19 | "SCROLL": 262144, "LOAD": 524288, "UNLOAD": 1048576, "XFER_DONE": 2097152, 20 | "ABORT": 4194304, "ERROR": 8388608, "LOCATE": 16777216, "MOVE": 33554432, 21 | "RESIZE": 67108864, "FORWARD": 134217728, "HELP": 268435456, "BACK": 536870912, 22 | "TEXT": 1073741824, "ALT_MASK": 1, "CONTROL_MASK": 2, "SHIFT_MASK": 4, 23 | "META_MASK": 8} 24 | '''; 25 | const jsonNested = ''' 26 | {"items":{"item":[{"id": "0001","type": "donut", 27 | "name": "Cake","ppu": 0.55,"batters":{"batter":[{ "id": "1001", "type": 28 | "Regular" },{ "id": "1002", "type": "Chocolate" },{ "id": "1003", "type": 29 | "Blueberry" },{ "id": "1004", "type": "Devil's Food" }]},"topping":[{ "id": 30 | "5001", "type": "None" },{ "id": "5002", "type": "Glazed" },{ "id": "5005", 31 | "type": "Sugar" },{ "id": "5007", "type": "Powdered Sugar" },{ "id": "5006", 32 | "type": "Chocolate with Sprinkles" },{ "id": "5003", "type": "Chocolate" }, 33 | { "id": "5004", "type": "Maple" }]}]}} 34 | '''; 35 | 36 | void runJson(String name, String input) => run( 37 | name, 38 | verify: () { 39 | final parserResult = jsonParser.parse(input).value; 40 | final nativeResult = convert.json.decode(input); 41 | if (parserResult.toString() != nativeResult.toString()) { 42 | throw StateError('Parsers provide inconsistent results'); 43 | } 44 | }, 45 | parse: () => jsonParser.parse(input), 46 | accept: () => jsonParser.accept(input), 47 | native: () => convert.json.decode(input), 48 | ); 49 | 50 | void main() { 51 | runJson('json - string', '"abcdef"'); 52 | runJson('json - integer', '33550336'); 53 | runJson('json - floating', '3.14159265359'); 54 | runJson('json - true', 'true'); 55 | runJson('json - false', 'false'); 56 | runJson('json - null', 'null'); 57 | runJson('json - array', jsonArray); 58 | runJson('json - object', jsonObject); 59 | runJson('json - event', jsonEvent); 60 | runJson('json - nested', jsonNested); 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/math/parser.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:petitparser/petitparser.dart'; 4 | 5 | import 'ast.dart'; 6 | import 'common.dart'; 7 | 8 | final parser = () { 9 | final builder = ExpressionBuilder(); 10 | builder 11 | ..primitive( 12 | (digit().plus() & 13 | (char('.') & digit().plus()).optional() & 14 | (pattern('eE') & pattern('+-').optional() & digit().plus()) 15 | .optional()) 16 | .flatten(message: 'number expected') 17 | .trim() 18 | .map(_createValue), 19 | ) 20 | ..primitive( 21 | seq2( 22 | seq2(letter(), word().star()).flatten(message: 'name expected').trim(), 23 | seq3( 24 | char('(').trim(), 25 | builder.loopback 26 | .starSeparated(char(',').trim()) 27 | .map((list) => list.elements), 28 | char(')').trim(), 29 | ).map3((_, list, _) => list).optionalWith(const []), 30 | ).map2((name, args) => _createBinding(name, args)), 31 | ); 32 | builder.group().wrapper( 33 | char('(').trim(), 34 | char(')').trim(), 35 | (left, value, right) => value, 36 | ); 37 | builder.group() 38 | ..prefix(char('+').trim(), (op, a) => a) 39 | ..prefix(char('-').trim(), (op, a) => Application('-', [a], (x) => -x)); 40 | builder.group().right( 41 | char('^').trim(), 42 | (a, op, b) => Application('^', [a, b], math.pow), 43 | ); 44 | builder.group() 45 | ..left( 46 | char('*').trim(), 47 | (a, op, b) => Application('*', [a, b], (x, y) => x * y), 48 | ) 49 | ..left( 50 | char('/').trim(), 51 | (a, op, b) => Application('/', [a, b], (x, y) => x / y), 52 | ); 53 | builder.group() 54 | ..left( 55 | char('+').trim(), 56 | (a, op, b) => Application('+', [a, b], (x, y) => x + y), 57 | ) 58 | ..left( 59 | char('-').trim(), 60 | (a, op, b) => Application('-', [a, b], (x, y) => x - y), 61 | ); 62 | return resolve(builder.build()).end(); 63 | }(); 64 | 65 | Expression _createValue(String value) => Value(num.parse(value)); 66 | 67 | Expression _createBinding(String name, List arguments) { 68 | switch (arguments.length) { 69 | case 0: 70 | final value = constants[name]; 71 | return value == null ? Variable(name) : Value(value); 72 | case 1: 73 | final function = checkValue(name, functions1[name]); 74 | return Application(name, arguments, function); 75 | case 2: 76 | final function = checkValue(name, functions2[name]); 77 | return Application(name, arguments, function); 78 | default: 79 | throwUnknown(name); 80 | } 81 | } 82 | 83 | T checkValue(String name, T? value) => value ?? throwUnknown(name); 84 | 85 | Never throwUnknown(String name) => 86 | throw ArgumentError.value(name, 'Unknown function'); 87 | -------------------------------------------------------------------------------- /bin/benchmark/benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:args/args.dart'; 4 | 5 | import 'suites/action_benchmark.dart' as action_benchmark; 6 | import 'suites/character_benchmark.dart' as character_benchmark; 7 | import 'suites/combinator_benchmark.dart' as combinator_benchmark; 8 | import 'suites/example_benchmark.dart' as example_benchmark; 9 | import 'suites/json_benchmark.dart' as json_benchmark; 10 | import 'suites/misc_benchmark.dart' as misc_benchmark; 11 | import 'suites/predicate_benchmark.dart' as predicate_benchmark; 12 | import 'suites/regexp_benchmark.dart' as regexp_benchmark; 13 | import 'suites/repeat_benchmark.dart' as repeat_benchmark; 14 | import 'utils/runner.dart' as runner; 15 | 16 | final arguments = ArgParser() 17 | ..addFlag('help', abbr: 'h', help: 'show the help text') 18 | ..addFlag( 19 | 'verify', 20 | abbr: 'v', 21 | help: 'run the verification code', 22 | defaultsTo: runner.optionVerification, 23 | callback: (value) => runner.optionVerification = value, 24 | ) 25 | ..addFlag( 26 | 'benchmark', 27 | abbr: 'b', 28 | help: 'run the benchmark code', 29 | defaultsTo: runner.optionBenchmark, 30 | callback: (value) => runner.optionBenchmark = value, 31 | ) 32 | ..addFlag( 33 | 'stderr', 34 | abbr: 'e', 35 | help: 'print the standard error', 36 | defaultsTo: runner.optionPrintStandardError, 37 | callback: (value) => runner.optionPrintStandardError = value, 38 | ) 39 | ..addFlag( 40 | 'confidence', 41 | abbr: 'c', 42 | help: 'print the confidence intervals', 43 | defaultsTo: runner.optionPrintConfidenceIntervals, 44 | callback: (value) => runner.optionPrintConfidenceIntervals = value, 45 | ) 46 | ..addOption( 47 | 'filter', 48 | abbr: 'f', 49 | help: 'filter the benchmarks', 50 | defaultsTo: runner.optionFilter, 51 | callback: (value) => runner.optionFilter = value, 52 | ) 53 | ..addOption( 54 | 'separator', 55 | abbr: 's', 56 | help: 'separator between benchmark values', 57 | defaultsTo: runner.optionSeparator, 58 | callback: (value) => runner.optionSeparator = value, 59 | ) 60 | ..addFlag( 61 | 'human', 62 | abbr: 'u', 63 | help: 'print extras in human-readable format', 64 | defaultsTo: runner.optionHumanReadable, 65 | callback: (value) => runner.optionHumanReadable = value, 66 | ); 67 | 68 | void main(List args) { 69 | // Parse the command line arguments. 70 | final results = arguments.parse(args); 71 | if (results['help'] || results.rest.isNotEmpty) { 72 | stdout.writeln(arguments.usage); 73 | exit(1); 74 | } 75 | // Run the benchmark. 76 | action_benchmark.main(); 77 | character_benchmark.main(); 78 | combinator_benchmark.main(); 79 | example_benchmark.main(); 80 | json_benchmark.main(); 81 | misc_benchmark.main(); 82 | predicate_benchmark.main(); 83 | regexp_benchmark.main(); 84 | repeat_benchmark.main(); 85 | } 86 | -------------------------------------------------------------------------------- /bin/lisp/lisp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:petitparser/petitparser.dart'; 5 | import 'package:petitparser_examples/lisp.dart'; 6 | 7 | /// Read, evaluate, print loop. 8 | void evalInteractive( 9 | Parser parser, 10 | Environment env, 11 | Stream input, 12 | IOSink output, 13 | IOSink error, 14 | ) { 15 | output.write('>> '); 16 | input.listen((line) { 17 | try { 18 | output.writeln('=> ${evalString(parser, env, line)}'); 19 | } on ParserException catch (exception) { 20 | error.writeln('Parser error: ${exception.toString()}'); 21 | } on Exception catch (exception) { 22 | error.writeln(exception.toString()); 23 | } 24 | output.write('>> '); 25 | }); 26 | } 27 | 28 | /// Entry point for the command line interpreter. 29 | void main(List arguments) { 30 | // default options 31 | var standardLibrary = true; 32 | var interactiveMode = false; 33 | final files = []; 34 | 35 | // parse arguments 36 | for (final option in arguments) { 37 | if (option.startsWith('-') && files.isEmpty) { 38 | if (option == '-n') { 39 | standardLibrary = false; 40 | } else if (option == '-i') { 41 | interactiveMode = true; 42 | } else if (option == '-?') { 43 | stdout.writeln('${Platform.executable} smalltalk.dart -n -i [files]'); 44 | stdout.writeln(' -i enforces the interactive mode'); 45 | stdout.writeln(' -n does not load the standard library'); 46 | exit(0); 47 | } else { 48 | stdout.writeln('Unknown option: $option'); 49 | exit(1); 50 | } 51 | } else { 52 | final file = File(option); 53 | if (file.existsSync()) { 54 | files.add(file); 55 | } else { 56 | stdout.writeln('File not found: $option'); 57 | exit(2); 58 | } 59 | } 60 | } 61 | 62 | // evaluation context 63 | Environment environment = NativeEnvironment(); 64 | 65 | // add additional primitives 66 | environment.define( 67 | Name('exit'), 68 | (Environment env, dynamic args) => exit(args == null ? 0 : args.head), 69 | ); 70 | environment.define( 71 | Name('sleep'), 72 | (Environment env, dynamic args) => sleep(Duration(milliseconds: args.head)), 73 | ); 74 | 75 | // process standard library 76 | if (standardLibrary) { 77 | environment = StandardEnvironment(environment); 78 | } 79 | 80 | // create empty context 81 | environment = environment.create(); 82 | 83 | // process files given as argument 84 | for (final file in files) { 85 | evalString(lispParser, environment, file.readAsStringSync()); 86 | } 87 | 88 | // process console input 89 | if (interactiveMode || files.isEmpty) { 90 | final input = stdin 91 | .transform(systemEncoding.decoder) 92 | .transform(const LineSplitter()); 93 | evalInteractive(lispParser, environment, input, stdout, stderr); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/src/bibtex/definition.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/definition.dart'; 2 | import 'package:petitparser/parser.dart'; 3 | 4 | import 'model.dart'; 5 | 6 | /// Grammar definition for BibTeX files. 7 | /// 8 | /// Adapted from the grammar published by Oscar Nierstrasz at 9 | /// https://twitter.com/onierstrasz/status/1621600391946289155. 10 | class BibTeXDefinition extends GrammarDefinition> { 11 | @override 12 | Parser> start() => ref0(entries).end(); 13 | 14 | // Entries 15 | Parser> entries() => ref0( 16 | entry, 17 | ).starSeparated(whitespace().star()).map((list) => list.elements); 18 | Parser entry() => 19 | seq6( 20 | type.trim(), 21 | char('{').trim(), 22 | citeKey.trim(), 23 | char(',').trim(), 24 | ref0(fields), 25 | char('}').trim(), 26 | ).map6( 27 | (type, _, key, _, fields, _) => 28 | BibTeXEntry(type: type, key: key, fields: fields), 29 | ); 30 | 31 | // Fields 32 | Parser> fields() => ref0(field) 33 | .starSeparated(char(',').trim()) 34 | .map((list) => Map.fromEntries(list.elements)); 35 | Parser> field() => seq3( 36 | fieldName.trim(), 37 | char('=').trim(), 38 | ref0(fieldValue), 39 | ).map3((name, _, value) => MapEntry(name, value)); 40 | Parser fieldValue() => [ 41 | ref0(fieldValueInQuotes), 42 | ref0(fieldValueInBraces), 43 | rawString, 44 | ].toChoiceParser(); 45 | 46 | // Quoted strings 47 | Parser fieldValueInQuotes() => seq3( 48 | char('"'), 49 | ref0(fieldStringWithinQuotes), 50 | char('"'), 51 | ).flatten(message: "quoted string expected"); 52 | Parser fieldStringWithinQuotes() => 53 | [ref0(fieldCharWithinQuotes), escapeChar].toChoiceParser().star(); 54 | Parser fieldCharWithinQuotes() => pattern(r'^\"'); 55 | 56 | // Braced strings 57 | Parser fieldValueInBraces() => seq3( 58 | char('{'), 59 | ref0(fieldStringWithinBraces), 60 | char('}'), 61 | ).flatten(message: "braced string expected"); 62 | 63 | Parser fieldStringWithinBraces() => [ 64 | ref0(fieldCharWithinBraces), 65 | escapeChar, 66 | seq3(char('{'), ref0(fieldStringWithinBraces), char('}')), 67 | ].toChoiceParser().star(); 68 | 69 | Parser fieldCharWithinBraces() => pattern(r'^\{}'); 70 | 71 | // Basic strings 72 | final type = letter() 73 | .plusString(message: "type expected") 74 | .skip(before: char('@')); 75 | final citeKey = pattern( 76 | 'a-zA-Z0-9_:-', 77 | ).plusString(message: "citation key expected"); 78 | final fieldName = pattern( 79 | 'a-zA-Z0-9_-', 80 | ).plusString(message: "field name expected"); 81 | final rawString = pattern( 82 | 'a-zA-Z0-9', 83 | ).plusString(message: "raw string expected"); 84 | 85 | // Other tokens 86 | final escapeChar = seq2(char(r'\'), any()); 87 | } 88 | -------------------------------------------------------------------------------- /lib/src/json/definition.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/definition.dart'; 2 | import 'package:petitparser/parser.dart'; 3 | 4 | import 'encoding.dart'; 5 | import 'types.dart'; 6 | 7 | /// JSON grammar definition. 8 | class JsonDefinition extends GrammarDefinition { 9 | @override 10 | Parser start() => ref0(value).end(); 11 | Parser value() => [ 12 | ref0(object), 13 | ref0(array), 14 | ref0(stringToken), 15 | ref0(numberToken), 16 | ref0(trueToken), 17 | ref0(falseToken), 18 | ref0(nullToken), 19 | failure(message: 'value expected'), 20 | ].toChoiceParser(); 21 | 22 | Parser> object() => seq3( 23 | char('{').trim(), 24 | ref0(objectElements), 25 | char('}').trim(), 26 | ).map3((_, elements, _) => elements); 27 | Parser> objectElements() => ref0(objectElement) 28 | .starSeparated(char(',').trim()) 29 | .map((list) => Map.fromEntries(list.elements)); 30 | Parser> objectElement() => seq3( 31 | ref0(stringToken), 32 | char(':').trim(), 33 | ref0(value), 34 | ).map3((key, _, value) => MapEntry(key, value)); 35 | 36 | Parser> array() => seq3( 37 | char('[').trim(), 38 | ref0(arrayElements), 39 | char(']').trim(), 40 | ).map3((_, elements, _) => elements); 41 | Parser> arrayElements() => 42 | ref0(value).starSeparated(char(',').trim()).map((list) => list.elements); 43 | 44 | Parser trueToken() => string('true').trim().map((_) => true); 45 | Parser falseToken() => string('false').trim().map((_) => false); 46 | Parser nullToken() => string('null').trim().map((_) => null); 47 | 48 | Parser stringToken() => seq3( 49 | char('"'), 50 | ref0(characterPrimitive).star(), 51 | char('"'), 52 | ).trim().map3((_, chars, _) => chars.join()); 53 | Parser characterPrimitive() => [ 54 | ref0(characterNormal), 55 | ref0(characterEscape), 56 | ref0(characterUnicode), 57 | ].toChoiceParser(); 58 | Parser characterNormal() => pattern('^"\\'); 59 | Parser characterEscape() => seq2( 60 | char('\\'), 61 | anyOf(jsonEscapeChars.keys.join()), 62 | ).map2((_, char) => jsonEscapeChars[char]!); 63 | Parser characterUnicode() => seq2( 64 | string('\\u'), 65 | pattern('0-9A-Fa-f').timesString(4, message: '4-digit hex number expected'), 66 | ).map2((_, value) => String.fromCharCode(int.parse(value, radix: 16))); 67 | 68 | Parser numberToken() => ref0( 69 | numberPrimitive, 70 | ).flatten(message: 'number expected').trim().map(num.parse); 71 | Parser numberPrimitive() => >[ 72 | char('-').optional(), 73 | [char('0'), digit().plus()].toChoiceParser(), 74 | [char('.'), digit().plus()].toSequenceParser().optional(), 75 | [ 76 | anyOf('eE'), 77 | anyOf('-+').optional(), 78 | digit().plus(), 79 | ].toSequenceParser().optional(), 80 | ].toSequenceParser(); 81 | } 82 | -------------------------------------------------------------------------------- /test/bibtex_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:http/http.dart' as http; 2 | import 'package:petitparser/reflection.dart'; 3 | import 'package:petitparser_examples/bibtex.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | Matcher isBibTextEntry({ 7 | dynamic type = anything, 8 | dynamic key = anything, 9 | dynamic fields = anything, 10 | }) => const TypeMatcher() 11 | .having((entry) => entry.type, 'type', type) 12 | .having((entry) => entry.key, 'key', key) 13 | .having((entry) => entry.fields, 'fields', fields); 14 | 15 | void main() { 16 | final parser = BibTeXDefinition().build(); 17 | test('linter', () { 18 | expect(linter(parser, excludedTypes: {}), isEmpty); 19 | }); 20 | group('basic', () { 21 | const input = 22 | '@inproceedings{Reng10c,\n' 23 | '\tTitle = "Practical Dynamic Grammars for Dynamic Languages",\n' 24 | '\tAuthor = {Lukas Renggli and St\\\'ephane Ducasse and Tudor G\\^irba and Oscar Nierstrasz},\n' 25 | '\tMonth = jun,\n' 26 | '\tYear = 2010,\n' 27 | '\tUrl = {http://scg.unibe.ch/archive/papers/Reng10cDynamicGrammars.pdf}}'; 28 | final entry = parser.parse(input).value.single; 29 | test('parsing', () { 30 | expect( 31 | entry, 32 | isBibTextEntry( 33 | type: 'inproceedings', 34 | key: 'Reng10c', 35 | fields: { 36 | 'Title': '"Practical Dynamic Grammars for Dynamic Languages"', 37 | 'Author': 38 | '{Lukas Renggli and St\\\'ephane Ducasse and ' 39 | 'Tudor G\\^irba and Oscar Nierstrasz}', 40 | 'Month': 'jun', 41 | 'Year': '2010', 42 | 'Url': 43 | '{http://scg.unibe.ch/archive/papers/' 44 | 'Reng10cDynamicGrammars.pdf}', 45 | }, 46 | ), 47 | ); 48 | }); 49 | test('serializing', () { 50 | expect(entry.toString(), input); 51 | }); 52 | }); 53 | group( 54 | 'scg.bib', 55 | () { 56 | late final List entries; 57 | setUpAll(() async { 58 | final body = await http.read( 59 | Uri.parse( 60 | 'https://raw.githubusercontent.com/scgbern/scgbib/main/scg.bib', 61 | ), 62 | ); 63 | entries = parser.parse(body).value; 64 | }); 65 | test('size', () { 66 | expect(entries.length, greaterThan(9600)); 67 | expect( 68 | entries 69 | .where( 70 | (entry) => entry.fields['Author']?.contains('Renggli') ?? false, 71 | ) 72 | .length, 73 | greaterThan(35), 74 | ); 75 | }); 76 | test('round-trip', () { 77 | for (final entry in entries) { 78 | expect( 79 | parser.parse(entry.toString()).value.single, 80 | isBibTextEntry( 81 | type: entry.type, 82 | key: entry.key, 83 | fields: entry.fields, 84 | ), 85 | ); 86 | } 87 | }); 88 | }, 89 | onPlatform: const { 90 | 'js': [Skip('http.get is unsupported in JavaScript')], 91 | }, 92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /bin/benchmark/suites/regexp_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:petitparser/petitparser.dart'; 3 | 4 | import '../utils/runner.dart'; 5 | 6 | const equality = ListEquality(); 7 | 8 | void runRegExp(String regexp, Parser parser, String input) { 9 | final parserPattern = parser.toPattern(); 10 | final nativePattern = RegExp(regexp); 11 | run( 12 | 'regexp - $regexp', 13 | verify: () { 14 | final parserResult = parserPattern 15 | .allMatches(input) 16 | .map((matcher) => matcher.group(0)) 17 | .toList(); 18 | final nativeResult = nativePattern 19 | .allMatches(input) 20 | .map((matcher) => matcher.group(0)) 21 | .toList(); 22 | if (parserResult.isEmpty || 23 | nativeResult.isEmpty || 24 | !equality.equals(parserResult, nativeResult)) { 25 | throw StateError('Expressions provide inconsistent results'); 26 | } 27 | }, 28 | parse: () => parserPattern.allMatches(input).toList(), 29 | accept: () => parserPattern.allMatches(input).isNotEmpty, 30 | native: () => nativePattern.allMatches(input).toList(), 31 | ); 32 | } 33 | 34 | void main() { 35 | runRegExp( 36 | r'[0-9]', 37 | digit(), 38 | '!1!12!123!1234!12345!123456!1234567!12345678!123456789!', 39 | ); 40 | runRegExp( 41 | r'[^0-9]', 42 | seq2(digit().not(), any()), 43 | '!1!12!123!1234!12345!123456!1234567!12345678!123456789!', 44 | ); 45 | runRegExp( 46 | r'[0-9]+', 47 | digit().plus(), 48 | '!1!12!123!1234!12345!123456!1234567!12345678!123456789!', 49 | ); 50 | runRegExp( 51 | r'[0-9]*!', 52 | seq2(digit().star(), char('!')), 53 | '!1!12!123!1234!12345!123456!1234567!12345678!123456789!', 54 | ); 55 | runRegExp( 56 | r'![0-9]*', 57 | seq2(char('!'), digit().star()), 58 | '!1!12!123!1234!12345!123456!1234567!12345678!123456789!', 59 | ); 60 | runRegExp( 61 | r'#([a-f0-9]{6}|[a-f0-9]{3})', 62 | seq3( 63 | char('#'), 64 | pattern('a-f0-9').repeat(3), 65 | pattern('a-f0-9').repeat(3).optional(), 66 | ), 67 | '#419527 #0839c4 #a95ba4 #da3e9e #15b331 #cafe00 #a7f #20c #46f #bb5', 68 | ); 69 | runRegExp( 70 | r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', 71 | digit().repeat(1, 3).timesSeparated(char('.'), 4), 72 | '127.0.0.1, 73.60.124.136, 192.88.99.12, 203.0.113.0, 10.1.2.3, 0.0.0.0', 73 | ); 74 | runRegExp( 75 | r'[a-z]+@[a-z]+\.[a-z]{2,3}', 76 | seq5( 77 | letter().plus(), 78 | char('@'), 79 | letter().plus(), 80 | char('.'), 81 | letter().repeat(2, 3), 82 | ), 83 | 'a@b.c, de@fg.hi, jkl@mno.pqr, stuv@wxyz.abcd, efghi@jklmn.opqrs', 84 | ); 85 | runRegExp( 86 | r'[+-]?\d+(\.\d+)?([eE][+-]?\d+)?', 87 | seq4( 88 | pattern('+-').optional(), 89 | digit().plus(), 90 | seq2(char('.'), digit().plus()).optional(), 91 | seq3(pattern('eE'), pattern('+-').optional(), digit().plus()).optional(), 92 | ), 93 | '1, -2, 3.4, -5.6, 7e8, 9E0, 0e+1, 2E-3, -4.567e-890', 94 | ); 95 | runRegExp( 96 | r'\n|\r\n?', 97 | newline(), 98 | '1\n12\n123\n1234\n12345\n123456\n1234567\n12345678\r\n123456789\r', 99 | ); 100 | } 101 | -------------------------------------------------------------------------------- /bin/benchmark/suites/example_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | import 'package:petitparser_examples/bibtex.dart' as bibtex_example; 3 | import 'package:petitparser_examples/json.dart' as json_example; 4 | import 'package:petitparser_examples/lisp.dart' as lisp_example; 5 | import 'package:petitparser_examples/math.dart' as math_example; 6 | import 'package:petitparser_examples/prolog.dart' as prolog_example; 7 | import 'package:petitparser_examples/tabular.dart' as csv_example; 8 | import 'package:petitparser_examples/uri.dart' as uri_example; 9 | import 'package:xml/src/xml_events/parser.dart' as xml_example; 10 | import 'package:xml/xml.dart'; 11 | 12 | import '../utils/runner.dart'; 13 | 14 | final bibtexParser = bibtex_example.BibTeXDefinition().build(); 15 | const bibtexInput = 16 | '@inproceedings{Reng10c,\n' 17 | '\tTitle = "Practical Dynamic Grammars for Dynamic Languages",\n' 18 | '\tAuthor = {Lukas Renggli and St\\\'ephane Ducasse and Tudor G\\^irba and Oscar Nierstrasz},\n' 19 | '\tMonth = jun,\n' 20 | '\tYear = 2010,\n' 21 | '\tUrl = {http://scg.unibe.ch/archive/papers/Reng10cDynamicGrammars.pdf}}'; 22 | 23 | final csvParser = csv_example.TabularDefinition.csv().build(); 24 | const csvInput = 25 | 'Los Angeles,34°03′N,118°15′W\n' 26 | 'New York City,40°42′46″N,74°00′21″W\n' 27 | 'Paris,48°51′24″N,2°21′03″E'; 28 | 29 | final tsvParser = csv_example.TabularDefinition.tsv().build(); 30 | const tsvInput = 31 | 'Sepal length Sepal width Petal length Petal width Species\n' 32 | '5.1 3.5 1.4 0.2 I. setosa\n' 33 | '4.9 3.0 1.4 0.2 I. setosa\n' 34 | '4.7 3.2 1.3 0.2 I. setosa\n' 35 | '4.6 3.1 1.5 0.2 I. setosa\n' 36 | '5.0 3.6 1.4 0.2 I. setosa\n'; 37 | 38 | final lispParser = lisp_example.LispParserDefinition().build(); 39 | const lispInput = 40 | '(define (fib n)\n' 41 | ' (if (<= n 1)\n' 42 | ' 1\n' 43 | ' (+ (fib (- n 1)) (fib (- n 2)))))'; 44 | 45 | final jsonParser = json_example.JsonDefinition().build(); 46 | const jsonInput = 47 | '{"type": "change", "eventPhase": 2, "bubbles": true, "cancelable": true, ' 48 | '"timeStamp": 0, "CAPTURING_PHASE": 1, "AT_TARGET": 2, ' 49 | '"BUBBLING_PHASE": 3, "isTrusted": true, "MOUSEDOWN": 1, "MOUSEUP": 2, ' 50 | '"MOUSEOVER": 4, "MOUSEOUT": 8, "MOUSEMOVE": 16, "MOUSEDRAG": 32, ' 51 | '"CLICK": 64, "DBLCLICK": 128, "KEYDOWN": 256, "KEYUP": 512, ' 52 | '"KEYPRESS": 1024, "DRAGDROP": 2048, "FOCUS": 4096, "BLUR": 8192, ' 53 | '"SELECT": 16384, "CHANGE": 32768, "RESET": 65536, "SUBMIT": 131072, ' 54 | '"SCROLL": 262144, "LOAD": 524288, "UNLOAD": 1048576, ' 55 | '"XFER_DONE": 2097152, "ABORT": 4194304, "ERROR": 8388608, ' 56 | '"LOCATE": 16777216, "MOVE": 33554432, "RESIZE": 67108864, ' 57 | '"FORWARD": 134217728, "HELP": 268435456, "BACK": 536870912, ' 58 | '"TEXT": 1073741824, "ALT_MASK": 1, "CONTROL_MASK": 2, ' 59 | '"SHIFT_MASK": 4, "META_MASK": 8}'; 60 | 61 | final prologParser = prolog_example.rulesParser; 62 | const prologInput = 63 | 'foo(bar, zork) :- true.\n' 64 | 'bok(X, Y) :- foo(X, Y), foo(Y, X).'; 65 | 66 | final mathParser = math_example.parser; 67 | const mathInput = '1 + 2 - (3 * 4 / sqrt(5 ^ pi)) - e'; 68 | 69 | final uriParser = uri_example.uri; 70 | const uriInput = 71 | 'https://www.lukas-renggli.ch/blog/petitparser-1?_s=Q5vcT_xEIhxf2Z4Q&_k=4pr02qyT&_n&42'; 72 | 73 | final xmlParser = xml_example.XmlEventParser( 74 | defaultEntityMapping, 75 | ).build().star().end(); 76 | const xmlInput = 77 | '\n' 78 | ' ]>\n' 79 | '\n' 81 | ' Plain text contents!' 82 | ' \n' 83 | ' \n' 84 | ' \n' 85 | ' \n' 86 | ' \n' 87 | ''; 88 | 89 | void main() { 90 | runString('example - bibtex', bibtexParser, input: bibtexInput); 91 | runString('example - csv', csvParser, input: csvInput); 92 | runString('example - json', jsonParser, input: jsonInput); 93 | runString('example - lisp', lispParser, input: lispInput); 94 | runString('example - math', mathParser, input: mathInput); 95 | runString('example - prolog', prologParser, input: prologInput); 96 | runString('example - tsv', tsvParser, input: tsvInput); 97 | runString('example - uri', uriParser, input: uriInput); 98 | runString('example - xml', xmlParser, input: xmlInput); 99 | } 100 | -------------------------------------------------------------------------------- /web/smalltalk/smalltalk.dart: -------------------------------------------------------------------------------- 1 | import 'dart:js_interop'; 2 | 3 | import 'package:petitparser_examples/smalltalk.dart'; 4 | import 'package:web/web.dart'; 5 | 6 | final input = document.querySelector('#input') as HTMLInputElement; 7 | final output = document.querySelector('#output') as HTMLElement; 8 | final parse = document.querySelector('#parse') as HTMLButtonElement; 9 | 10 | final parserDefinition = SmalltalkParserDefinition(); 11 | final methodParser = parserDefinition.build(); 12 | 13 | void main() { 14 | parse.onClick.listen((event) { 15 | output.textContent = 'Evaluating...'; 16 | output.classList.value = ''; 17 | try { 18 | final result = methodParser.parse(input.value); 19 | final visitor = PrintVisitor()..visit(result.value); 20 | output.innerHTML = visitor.buffer.toString().toJS; 21 | } on Object catch (exception) { 22 | output.textContent = exception.toString(); 23 | output.classList.add('error'); 24 | } 25 | }); 26 | } 27 | 28 | class PrintVisitor extends Visitor { 29 | final buffer = StringBuffer(); 30 | var _indent = ''; 31 | 32 | void print(Object? object) => buffer.writeln('$_indent$object
    '); 33 | 34 | void indent(void Function() callback) { 35 | final previous = _indent; 36 | _indent = '$previous  '; 37 | callback(); 38 | _indent = previous; 39 | } 40 | 41 | String variable(VariableNode node) => 42 | '${node.name}'; 43 | 44 | String selector(HasSelector node) => 45 | '${node.selector}'; 46 | 47 | @override 48 | void visitMethodNode(MethodNode node) { 49 | print('Method: ${selector(node)}'); 50 | indent(() { 51 | if (node.arguments.isNotEmpty) { 52 | print('Arguments: ${node.arguments.map(variable).join(', ')}'); 53 | } 54 | if (node.pragmas.isNotEmpty) { 55 | print('Pragmas'); 56 | indent(() => node.pragmas.forEach(visit)); 57 | } 58 | visit(node.body); 59 | }); 60 | } 61 | 62 | @override 63 | void visitPragmaNode(PragmaNode node) { 64 | print('Pragma: ${selector(node)}'); 65 | indent(() => node.arguments.forEach(visit)); 66 | } 67 | 68 | @override 69 | void visitReturnNode(ReturnNode node) { 70 | print('Return'); 71 | indent(() => visit(node.value)); 72 | } 73 | 74 | @override 75 | void visitSequenceNode(SequenceNode node) { 76 | print('Sequence'); 77 | indent(() { 78 | if (node.temporaries.isNotEmpty) { 79 | print('Temporaries: ${node.temporaries.map(variable).join(', ')}'); 80 | } 81 | node.statements.forEach(visit); 82 | }); 83 | } 84 | 85 | @override 86 | void visitArrayNode(ArrayNode node) { 87 | print('Array'); 88 | indent(() => node.statements.forEach(visit)); 89 | } 90 | 91 | @override 92 | void visitAssignmentNode(AssignmentNode node) { 93 | print('Assignment: ${variable(node.variable)}'); 94 | indent(() => visit(node.value)); 95 | } 96 | 97 | @override 98 | void visitBlockNode(BlockNode node) { 99 | print('Block'); 100 | indent(() { 101 | if (node.arguments.isNotEmpty) { 102 | print('Arguments: ${node.arguments.map(variable).join(', ')}'); 103 | } 104 | visit(node.body); 105 | }); 106 | } 107 | 108 | @override 109 | void visitCascadeNode(CascadeNode node) { 110 | print('Cascade'); 111 | indent(() { 112 | visit(node.receiver); 113 | for (final message in node.messages) { 114 | print('Selector: ${selector(message)}'); 115 | if (message.arguments.isNotEmpty) { 116 | print('Arguments'); 117 | indent(() => message.arguments.forEach(visit)); 118 | } 119 | } 120 | }); 121 | } 122 | 123 | @override 124 | void visitLiteralArrayNode(LiteralArrayNode node) { 125 | print('Literal Array: ${node.value}'); 126 | } 127 | 128 | @override 129 | void visitLiteralValueNode(LiteralValueNode node) { 130 | print('Literal Value: ${node.value}'); 131 | } 132 | 133 | @override 134 | void visitMessageNode(MessageNode node) { 135 | print('Message: ${selector(node)}'); 136 | indent(() { 137 | visit(node.receiver); 138 | if (node.arguments.isNotEmpty) { 139 | print('Arguments'); 140 | indent(() => node.arguments.forEach(visit)); 141 | } 142 | }); 143 | } 144 | 145 | @override 146 | void visitVariableNode(VariableNode node) { 147 | print('Variable: ${variable(node)}'); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PetitParser Examples 2 | 3 | [![Pub Package](https://img.shields.io/pub/v/petitparser_examples.svg)](https://pub.dev/packages/petitparser_examples) 4 | [![Build Status](https://github.com/petitparser/dart-petitparser-examples/actions/workflows/dart.yml/badge.svg?branch=main)](https://github.com/petitparser/dart-petitparser-examples/actions/workflows/dart.yml) 5 | [![Code Coverage](https://codecov.io/gh/petitparser/dart-petitparser-examples/branch/main/graph/badge.svg?token=NVECPJQD87)](https://codecov.io/gh/petitparser/dart-petitparser-examples) 6 | [![GitHub Issues](https://img.shields.io/github/issues/petitparser/dart-petitparser-examples.svg)](https://github.com/petitparser/dart-petitparser-examples/issues) 7 | [![GitHub Forks](https://img.shields.io/github/forks/petitparser/dart-petitparser-examples.svg)](https://github.com/petitparser/dart-petitparser-examples/network) 8 | [![GitHub Stars](https://img.shields.io/github/stars/petitparser/dart-petitparser-examples.svg)](https://github.com/petitparser/dart-petitparser-examples/stargazers) 9 | [![GitHub License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/petitparser/dart-petitparser-examples/main/LICENSE) 10 | 11 | This package contains examples to illustrate the use of [PetitParser](https://github.com/petitparser/dart-petitparser). A tutorial and full documentation is contained in the [package description](https://pub.dev/packages/petitparser) and [API documentation](https://pub.dev/documentation/petitparser/latest/). [petitparser.github.io](https://petitparser.github.io/) contains more information about PetitParser, running examples in the browser, and links to ports to other languages. 12 | 13 | ## Examples 14 | 15 | ### BibTeX 16 | 17 | A simple parser that reads a [BibTeX](https://en.wikipedia.org/wiki/BibTeX) file into a list of BibTeX entries with a list of fields. 18 | 19 | ### Dart 20 | 21 | This example contains the grammar of the Dart programming language. This is based on an early Dart 1.0 grammar specification and unfortunately does not support all valid Dart programs yet. 22 | 23 | ### JSON 24 | 25 | This example contains a complete implementation of [JSON](https://json.org/). It is a simple grammar that can be used for benchmarking with the native implementation. 26 | 27 | ### Lisp 28 | 29 | This example contains a simple grammar and evaluator for LISP. The code is reasonably complete to run and evaluate complex programs. Binaries for a Read–Eval–Print Loop (REPL) are provided for the console and the web browser. 30 | 31 | ```bash 32 | dart run bin/lisp/lisp.dart 33 | ``` 34 | 35 | ### Math 36 | 37 | This example contains a simple evaluator for mathematical expressions, it builds a parse-tree that can then be used to print or evaluate expressions. 38 | 39 | ### Pascal 40 | 41 | A complete pascal grammar following the Apple Pascal Standard from 1978. 42 | 43 | ### Prolog 44 | 45 | This example contains a simple grammar and evaluator for Prolog programs. The code is reasonably complete to run and evaluate basic prolog programs. Binaries for a Read–Eval–Print Loop (REPL) are provided for the console and the web browser. 46 | 47 | ```bash 48 | dart run bin/prolog/prolog.dart 49 | ``` 50 | 51 | ### Smalltalk 52 | 53 | This example contains a complete implementation of the Smalltalk grammar. This is a verbatim export of a grammar that was originally developed for the PetitParser infrastructure in Smalltalk and that was the base of the [Helvetia Language Workbench](https://www.lukas-renggli.ch/smalltalk/helvetia). 54 | 55 | ### URI 56 | 57 | This is a simple grammar that takes an URL string and decomposes it into scheme, authority (including username, password, hostname, and port), path, query (including parameters as key-value pairs), and fragment. 58 | 59 | ### XML 60 | 61 | This examples parses XML files to events, creates and pretty-prints a DOM tree, and evaluates XPath expressions. Depends on [xml](https://github.com/renggli/dart-xml) package. 62 | 63 | ## Web 64 | 65 | To run the web examples execute the following commands from the command line and navigate to http://localhost:8080/: 66 | 67 | ```bash 68 | dart pub global activate webdev 69 | webdev serve --release 70 | ``` 71 | 72 | ## Benchmarks 73 | 74 | To run the benchmarks execute the following command from the command line: 75 | 76 | ```bash 77 | dart run --no-enable-asserts bin/benchmark/benchmark.dart 78 | ``` 79 | 80 | Each benchmark prints the standard parsing performance, then the fast parsing performance (where return results are ignored), and possibly a native comparison (i.e. with regular expressions or native JSON parser). With the arguments `--confidence` or `--stderr` the respective variations are printed. 81 | 82 | To only run the verification code use: 83 | 84 | ```bash 85 | dart run bin/benchmark/benchmark.dart --no-benchmark 86 | ``` 87 | -------------------------------------------------------------------------------- /lib/src/smalltalk/ast.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | import 'visitor.dart'; 4 | 5 | abstract class Node { 6 | void accept(Visitor visitor); 7 | } 8 | 9 | mixin HasStatements implements Node { 10 | final List statements = []; 11 | final List periods = []; 12 | } 13 | 14 | mixin IsStatement implements Node {} 15 | 16 | mixin IsSurrounded implements Node { 17 | final List beforeToken = []; 18 | final List afterToken = []; 19 | 20 | void surroundWith(Token before, Token after) { 21 | beforeToken.add(before); 22 | afterToken.add(after); 23 | } 24 | } 25 | 26 | enum SelectorType { unary, binary, keyword } 27 | 28 | mixin HasSelector implements Node { 29 | final List selectorToken = []; 30 | 31 | List get arguments; 32 | 33 | String get selector => selectorToken.map((token) => token.input).join(); 34 | 35 | SelectorType get selectorType => arguments.isEmpty 36 | ? SelectorType.unary 37 | : selectorToken.first.value.endsWith(':') 38 | ? SelectorType.keyword 39 | : SelectorType.binary; 40 | } 41 | 42 | class MethodNode extends Node with HasSelector { 43 | MethodNode(); 44 | 45 | @override 46 | final List arguments = []; 47 | final List pragmas = []; 48 | final SequenceNode body = SequenceNode(); 49 | 50 | @override 51 | void accept(Visitor visitor) => visitor.visitMethodNode(this); 52 | } 53 | 54 | class PragmaNode extends Node with HasSelector, IsSurrounded { 55 | PragmaNode(); 56 | 57 | @override 58 | final List arguments = []; 59 | 60 | @override 61 | void accept(Visitor visitor) => visitor.visitPragmaNode(this); 62 | } 63 | 64 | class SequenceNode extends Node with HasStatements { 65 | SequenceNode(); 66 | 67 | final List temporaries = []; 68 | 69 | @override 70 | void accept(Visitor visitor) => visitor.visitSequenceNode(this); 71 | } 72 | 73 | class ReturnNode extends Node with IsStatement { 74 | ReturnNode(this.caret, this.value); 75 | 76 | final Token caret; 77 | final ValueNode value; 78 | 79 | @override 80 | void accept(Visitor visitor) => visitor.visitReturnNode(this); 81 | } 82 | 83 | abstract class ValueNode extends Node with IsStatement, IsSurrounded { 84 | ValueNode(); 85 | } 86 | 87 | class ArrayNode extends ValueNode with HasStatements { 88 | ArrayNode(); 89 | 90 | @override 91 | void accept(Visitor visitor) => visitor.visitArrayNode(this); 92 | } 93 | 94 | class AssignmentNode extends ValueNode { 95 | AssignmentNode(this.variable, this.assignment, this.value); 96 | 97 | final VariableNode variable; 98 | final Token assignment; 99 | final ValueNode value; 100 | 101 | @override 102 | void accept(Visitor visitor) => visitor.visitAssignmentNode(this); 103 | } 104 | 105 | class BlockNode extends ValueNode { 106 | BlockNode(this.body); 107 | 108 | final List arguments = []; 109 | final List separators = []; 110 | final SequenceNode body; 111 | 112 | @override 113 | void accept(Visitor visitor) => visitor.visitBlockNode(this); 114 | } 115 | 116 | class CascadeNode extends ValueNode { 117 | CascadeNode(); 118 | 119 | final List messages = []; 120 | final List semicolons = []; 121 | 122 | ValueNode get receiver => messages.first.receiver; 123 | 124 | @override 125 | void accept(Visitor visitor) => visitor.visitCascadeNode(this); 126 | } 127 | 128 | abstract class LiteralNode extends ValueNode { 129 | LiteralNode(this.value); 130 | 131 | final T value; 132 | } 133 | 134 | class LiteralArrayNode extends LiteralNode> { 135 | LiteralArrayNode(this.values) 136 | : super(values.map((value) => value.value).toList()); 137 | 138 | final List> values; 139 | 140 | @override 141 | void accept(Visitor visitor) => visitor.visitLiteralArrayNode(this); 142 | } 143 | 144 | class LiteralValueNode extends LiteralNode { 145 | LiteralValueNode(this.token, T value) : super(value); 146 | 147 | final Token token; 148 | 149 | @override 150 | void accept(Visitor visitor) => visitor.visitLiteralValueNode(this); 151 | } 152 | 153 | class MessageNode extends ValueNode with HasSelector { 154 | MessageNode(this.receiver); 155 | 156 | final ValueNode receiver; 157 | @override 158 | final List arguments = []; 159 | 160 | @override 161 | void accept(Visitor visitor) => visitor.visitMessageNode(this); 162 | } 163 | 164 | class VariableNode extends ValueNode { 165 | VariableNode(this.token); 166 | 167 | final Token token; 168 | 169 | String get name => token.input; 170 | 171 | @override 172 | void accept(Visitor visitor) => visitor.visitVariableNode(this); 173 | } 174 | -------------------------------------------------------------------------------- /lib/src/smalltalk/objects.dart: -------------------------------------------------------------------------------- 1 | // Basic classes 2 | final objectBehavior = Behavior('Object'); 3 | final classBehavior = Behavior('Class'); 4 | 5 | // Native classes 6 | final arrayBehavior = Behavior('Array'); 7 | final falseBehavior = Behavior('False'); 8 | final numberBehavior = Behavior('Number'); 9 | final stringBehavior = Behavior('String'); 10 | final trueBehavior = Behavior('True'); 11 | final undefinedBehavior = Behavior('Undefined'); 12 | 13 | extension BehaviorAccessor on dynamic { 14 | Behavior get behavior { 15 | final self = this; 16 | if (self == null) { 17 | return undefinedBehavior; 18 | } else if (self == true) { 19 | return trueBehavior; 20 | } else if (self == false) { 21 | return falseBehavior; 22 | } else if (self is num) { 23 | return numberBehavior; 24 | } else if (self is String) { 25 | return stringBehavior; 26 | } else if (self is List) { 27 | return arrayBehavior; 28 | } else if (self is SmalltalkObject) { 29 | return self.behavior; 30 | } else { 31 | throw UnsupportedError('Unsupported object type: ${self.runtimeType}'); 32 | } 33 | } 34 | } 35 | 36 | class SmalltalkObject { 37 | SmalltalkObject(this.behavior); 38 | 39 | Behavior behavior; 40 | Map fields = {}; 41 | } 42 | 43 | class Behavior extends SmalltalkObject { 44 | Behavior(this.name) : super(classBehavior); 45 | 46 | String name; 47 | Map methods = {}; 48 | 49 | void addMethod(String selector, Function function) => 50 | methods[selector] = function; 51 | } 52 | 53 | void bootstrap() { 54 | objectBehavior.addMethod('printString', (self) => self.toString()); 55 | objectBehavior.addMethod('class', (self) => self.behavior); 56 | objectBehavior.addMethod('isNil', (self) => false); 57 | 58 | undefinedBehavior.addMethod('isNil', (self) => true); 59 | 60 | trueBehavior.addMethod('ifTrue:', (self, trueBranch) => trueBranch()); 61 | trueBehavior.addMethod( 62 | 'ifTrue:ifFalse:', 63 | (self, trueBranch, falseBranch) => trueBranch(), 64 | ); 65 | trueBehavior.addMethod('ifFalse:', (self, falseBranch) => null); 66 | trueBehavior.addMethod( 67 | 'ifFalse:ifTrue:', 68 | (self, falseBranch, trueBranch) => trueBranch(), 69 | ); 70 | trueBehavior.addMethod('not', (self) => false); 71 | trueBehavior.addMethod('and:', (self, other) => other()); 72 | trueBehavior.addMethod('or:', (self, other) => true); 73 | 74 | falseBehavior.addMethod('ifTrue:', (self, trueBranch) => null); 75 | falseBehavior.addMethod( 76 | 'ifTrue:ifFalse:', 77 | (self, trueBranch, falseBranch) => falseBranch(), 78 | ); 79 | falseBehavior.addMethod('ifFalse:', (self, falseBranch) => falseBranch()); 80 | falseBehavior.addMethod( 81 | 'ifFalse:ifTrue:', 82 | (self, falseBranch, trueBranch) => falseBranch(), 83 | ); 84 | falseBehavior.addMethod('not', (self) => true); 85 | falseBehavior.addMethod('and:', (self, other) => false); 86 | falseBehavior.addMethod('or:', (self, other) => other()); 87 | 88 | numberBehavior.addMethod('+', (self, other) => self + other); 89 | numberBehavior.addMethod('-', (self, other) => self - other); 90 | numberBehavior.addMethod('*', (self, other) => self * other); 91 | numberBehavior.addMethod('/', (self, other) => self / other); 92 | numberBehavior.addMethod('//', (self, other) => self ~/ other); 93 | numberBehavior.addMethod('\\', (self, other) => self % other); 94 | numberBehavior.addMethod('negate', (self) => -self); 95 | numberBehavior.addMethod('<', (self, other) => self < other); 96 | numberBehavior.addMethod('<=', (self, other) => self <= other); 97 | numberBehavior.addMethod('>', (self, other) => self > other); 98 | numberBehavior.addMethod('>=', (self, other) => self >= other); 99 | numberBehavior.addMethod('=', (self, other) => self < other); 100 | 101 | stringBehavior.addMethod('+', (self, other) => self + other); 102 | stringBehavior.addMethod('size', (self, other) => self.length); 103 | stringBehavior.addMethod('at:', (self, index) => self[index]); 104 | stringBehavior.addMethod('<', (self, other) => self < other); 105 | stringBehavior.addMethod('<=', (self, other) => self <= other); 106 | stringBehavior.addMethod('>', (self, other) => self > other); 107 | stringBehavior.addMethod('>=', (self, other) => self >= other); 108 | stringBehavior.addMethod('=', (self, other) => self < other); 109 | 110 | arrayBehavior.addMethod('size', (self) => self.length); 111 | arrayBehavior.addMethod('at:', (self, index) => self[index]); 112 | arrayBehavior.addMethod( 113 | 'at:put:', 114 | (self, index, object) => self[index] = object, 115 | ); 116 | 117 | classBehavior.addMethod('new', SmalltalkObject.new); 118 | } 119 | -------------------------------------------------------------------------------- /web/math/plot.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:js_interop'; 3 | 4 | import 'package:petitparser_examples/math.dart'; 5 | import 'package:web/web.dart'; 6 | 7 | class Viewport { 8 | Viewport( 9 | this.canvas, { 10 | required this.minX, 11 | required this.maxX, 12 | required this.minY, 13 | required this.maxY, 14 | }) : context = canvas.context2D, 15 | width = canvas.offsetWidth, 16 | height = canvas.offsetHeight; 17 | 18 | final HTMLCanvasElement canvas; 19 | final CanvasRenderingContext2D context; 20 | 21 | final num minX; 22 | final num maxX; 23 | final num minY; 24 | final num maxY; 25 | 26 | num width; 27 | num height; 28 | 29 | /// Resizes the canvas. 30 | void resize(num width, num height) { 31 | final scale = window.devicePixelRatio; 32 | this.width = width; 33 | this.height = height; 34 | canvas.style.width = '${width}px'; 35 | canvas.style.height = '${height}px'; 36 | canvas.width = (width * scale).truncate(); 37 | canvas.height = (height * scale).truncate(); 38 | context.scale(scale, scale); 39 | } 40 | 41 | /// Clears the viewport. 42 | void clear() { 43 | context.beginPath(); 44 | context.rect(0, 0, width, height); 45 | context.clip(); 46 | context.clearRect(0, 0, width, height); 47 | } 48 | 49 | /// Plots the grid and axis. 50 | void grid({String axisStyle = 'black', String gridStyle = 'gray'}) { 51 | context.lineWidth = 0.5; 52 | for (var x = minX.floor(); x <= maxX.ceil(); x++) { 53 | final pixelX = toPixelX(x); 54 | context.strokeStyle = x == 0 ? axisStyle.toJS : gridStyle.toJS; 55 | context.beginPath(); 56 | context.moveTo(pixelX, 0); 57 | context.lineTo(pixelX, height); 58 | context.stroke(); 59 | } 60 | for (var y = minY.floor(); y <= maxY.ceil(); y++) { 61 | final pixelY = toPixelY(y); 62 | context.strokeStyle = y == 0 ? axisStyle.toJS : gridStyle.toJS; 63 | context.beginPath(); 64 | context.moveTo(0, pixelY); 65 | context.lineTo(width, pixelY); 66 | context.stroke(); 67 | } 68 | } 69 | 70 | /// Plots a numeric function. 71 | void plot(num Function(num x) function, {String functionStyle = 'blue'}) { 72 | context.strokeStyle = functionStyle.toJS; 73 | context.lineWidth = 1.0; 74 | context.beginPath(); 75 | num lastY = double.infinity; 76 | for (var x = 0; x <= width; x++) { 77 | final currentY = function(fromPixelX(x)); 78 | if (lastY.isInfinite || 79 | currentY.isInfinite || 80 | (lastY.sign != currentY.sign && (lastY - currentY).abs() > 100)) { 81 | context.moveTo(x, toPixelY(currentY)); 82 | } else { 83 | context.lineTo(x, toPixelY(currentY)); 84 | } 85 | lastY = currentY; 86 | } 87 | context.stroke(); 88 | } 89 | 90 | /// Converts logical x-coordinate to pixel. 91 | num toPixelX(num value) => (value - minX) * width / (maxX - minX); 92 | 93 | /// Converts logical y-coordinate to pixel. 94 | num toPixelY(num value) => height - (value - minY) * height / (maxY - minY); 95 | 96 | /// Converts pixel to logical x-coordinate. 97 | num fromPixelX(num value) => value * (maxX - minX) / width + minX; 98 | } 99 | 100 | final input = document.querySelector('#input') as HTMLInputElement; 101 | final error = document.querySelector('#error') as HTMLElement; 102 | final canvas = document.querySelector('#canvas') as HTMLCanvasElement; 103 | 104 | final viewport = Viewport(canvas, minX: -5, maxX: 5, minY: -2.5, maxY: 2.5); 105 | 106 | Expression expression = Value(double.nan); 107 | 108 | void resize(Event event) { 109 | final rect = canvas.parentElement?.getBoundingClientRect(); 110 | if (rect != null) { 111 | viewport.resize(rect.width, rect.width / 2); 112 | } 113 | } 114 | 115 | void update() { 116 | final source = input.value; 117 | try { 118 | expression = parser.parse(source).value; 119 | expression.eval({'x': 0, 't': 0}); 120 | error.textContent = ''; 121 | } on Object catch (exception) { 122 | expression = Value(double.nan); 123 | error.textContent = exception.toString(); 124 | } 125 | window.location.hash = Uri.encodeComponent(source); 126 | } 127 | 128 | void refresh(int tick) { 129 | viewport.clear(); 130 | viewport.grid(); 131 | viewport.plot((x) => expression.eval({'x': x, 't': tick})); 132 | } 133 | 134 | void main() { 135 | if (window.location.hash.startsWith('#')) { 136 | input.value = Uri.decodeComponent(window.location.hash.substring(1)); 137 | } 138 | resize(Event('resize')); 139 | window.addEventListener('resize', resize.toJS); 140 | update(); 141 | input.onInput.listen((event) => update()); 142 | Timer.periodic( 143 | const Duration(milliseconds: 1000 ~/ 30), 144 | (Timer timer) => refresh(timer.tick), 145 | ); 146 | } 147 | -------------------------------------------------------------------------------- /test/uri_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | import 'package:petitparser/petitparser.dart'; 3 | import 'package:petitparser/reflection.dart'; 4 | import 'package:petitparser_examples/src/uri/authority.dart'; 5 | import 'package:petitparser_examples/src/uri/query.dart'; 6 | import 'package:petitparser_examples/uri.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | import 'utils/expect.dart'; 10 | 11 | final parser = uri.end(); 12 | 13 | @isTest 14 | void uriTest(String source, Map values) { 15 | test(source, () => expect(parser, isSuccess(source, value: values))); 16 | } 17 | 18 | void main() { 19 | test('linter', () { 20 | expect(linter(parser, excludedTypes: {}), isEmpty); 21 | expect(linter(authority, excludedTypes: {}), isEmpty); 22 | expect(linter(query, excludedTypes: {}), isEmpty); 23 | }); 24 | uriTest('http://www.ics.uci.edu/pub/ietf/uri/#Related', { 25 | #scheme: 'http', 26 | #authority: 'www.ics.uci.edu', 27 | #username: isNull, 28 | #password: isNull, 29 | #hostname: 'www.ics.uci.edu', 30 | #port: isNull, 31 | #path: '/pub/ietf/uri/', 32 | #query: isNull, 33 | #params: [], 34 | #fragment: 'Related', 35 | }); 36 | uriTest('http://a/b/c/d;e?f&g=h', { 37 | #scheme: 'http', 38 | #authority: 'a', 39 | #username: isNull, 40 | #password: isNull, 41 | #hostname: 'a', 42 | #port: isNull, 43 | #path: '/b/c/d;e', 44 | #query: 'f&g=h', 45 | #params: [ 46 | ['f', null], 47 | ['g', 'h'], 48 | ], 49 | #fragment: isNull, 50 | }); 51 | uriTest(r'ftp://www.example.org:22/foo bar/zork<>?\^`{|}', { 52 | #scheme: 'ftp', 53 | #authority: 'www.example.org:22', 54 | #username: isNull, 55 | #password: isNull, 56 | #hostname: 'www.example.org', 57 | #port: '22', 58 | #path: '/foo bar/zork<>', 59 | #query: r'\^`{|}', 60 | #params: [ 61 | ['\\^`{|}', isNull], 62 | ], 63 | #fragment: isNull, 64 | }); 65 | uriTest('data:text/plain;charset=iso-8859-7,hallo', { 66 | #scheme: 'data', 67 | #authority: isNull, 68 | #username: isNull, 69 | #password: isNull, 70 | #hostname: isNull, 71 | #port: isNull, 72 | #path: 'text/plain;charset=iso-8859-7,hallo', 73 | #query: isNull, 74 | #params: [], 75 | #fragment: isNull, 76 | }); 77 | uriTest('https://www.übermäßig.de/müßiggänger', { 78 | #scheme: 'https', 79 | #authority: 'www.übermäßig.de', 80 | #username: isNull, 81 | #password: isNull, 82 | #hostname: 'www.übermäßig.de', 83 | #port: isNull, 84 | #path: '/müßiggänger', 85 | #query: isNull, 86 | #params: [], 87 | #fragment: isNull, 88 | }); 89 | uriTest('http:test', { 90 | #scheme: 'http', 91 | #authority: isNull, 92 | #username: isNull, 93 | #password: isNull, 94 | #hostname: isNull, 95 | #port: isNull, 96 | #path: 'test', 97 | #query: isNull, 98 | #params: [], 99 | #fragment: isNull, 100 | }); 101 | uriTest(r'file:c:\\foo\\bar.html', { 102 | #scheme: 'file', 103 | #authority: isNull, 104 | #username: isNull, 105 | #password: isNull, 106 | #hostname: isNull, 107 | #port: isNull, 108 | #path: r'c:\\foo\\bar.html', 109 | #query: isNull, 110 | #params: [], 111 | #fragment: isNull, 112 | }); 113 | uriTest('file://foo:bar@localhost/test', { 114 | #scheme: 'file', 115 | #authority: 'foo:bar@localhost', 116 | #username: 'foo', 117 | #password: 'bar', 118 | #hostname: 'localhost', 119 | #port: isNull, 120 | #path: '/test', 121 | #query: isNull, 122 | #params: [], 123 | #fragment: isNull, 124 | }); 125 | group('https://mathiasbynens.be/demo/url-regex', () { 126 | for (final input in const [ 127 | 'http://foo.com/blah_blah', 128 | 'http://foo.com/blah_blah/', 129 | 'http://foo.com/blah_blah_(wikipedia)', 130 | 'http://foo.com/blah_blah_(wikipedia)_(again)', 131 | 'http://www.example.com/wpstyle/?p=364', 132 | 'https://www.example.com/foo/?bar=baz&inga=42&quux', 133 | 'http://✪df.ws/123', 134 | 'http://userid:password@example.com:8080', 135 | 'http://userid:password@example.com:8080/', 136 | 'http://userid@example.com', 137 | 'http://userid@example.com/', 138 | 'http://userid@example.com:8080', 139 | 'http://userid@example.com:8080/', 140 | 'http://userid:password@example.com', 141 | 'http://userid:password@example.com/', 142 | 'http://142.42.1.1/', 143 | 'http://142.42.1.1:8080/', 144 | 'http://➡.ws/䨹', 145 | 'http://⌘.ws', 146 | 'http://⌘.ws/', 147 | 'http://foo.com/blah_(wikipedia)#cite-1', 148 | 'http://foo.com/blah_(wikipedia)_blah#cite-1', 149 | 'http://foo.com/unicode_(✪)_in_parens', 150 | 'http://foo.com/(something)?after=parens', 151 | 'http://☺.damowmow.com/', 152 | 'http://code.google.com/events/#&product=browser', 153 | 'http://j.mp', 154 | 'ftp://foo.bar/baz', 155 | 'http://foo.bar/?q=Test%20URL-encoded%20stuff', 156 | 'http://مثال.إختبار', 157 | 'http://例子.测试', 158 | 'http://उदाहरण.परीक्षा', 159 | 'http://-.~_!\$&\'()*+,;=:%40:80%2f::::::@example.com', 160 | 'http://1337.net', 161 | 'http://a.b-c.de', 162 | 'http://223.255.255.254', 163 | ]) { 164 | test(input, () => expect(parser, isSuccess(input))); 165 | } 166 | }); 167 | } 168 | -------------------------------------------------------------------------------- /bin/benchmark/utils/runner.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | 5 | import 'package:collection/collection.dart'; 6 | import 'package:data/stats.dart'; 7 | import 'package:more/more.dart'; 8 | import 'package:petitparser/petitparser.dart'; 9 | 10 | import 'benchmark.dart'; 11 | 12 | /// Whether to run the actual benchmark. 13 | var optionBenchmark = true; 14 | 15 | /// Whether to run the verification code. 16 | var optionVerification = true; 17 | 18 | /// Whether to print the standard error. 19 | var optionPrintStandardError = false; 20 | 21 | /// Whether to print the confidence intervals. 22 | var optionPrintConfidenceIntervals = false; 23 | 24 | /// Whether to filter to a specific benchmark. 25 | String? optionFilter; 26 | 27 | /// Separator character for output. 28 | String? optionSeparator = '\t'; 29 | 30 | /// if the output should be human readable. 31 | var optionHumanReadable = true; 32 | 33 | final defaultCharsInput = [ 34 | // ASCII characters (265 times) 35 | ...List.generate(0xff, String.fromCharCode), 36 | // Mathematical symbols (32 times, UTF-16 chars) 37 | ...List.generate(0x20, (i) => String.fromCharCode(0x2200 + i)), 38 | // Alchemical symbols (32 times, UTF-32 chars) 39 | ...List.generate(0x20, (i) => String.fromCharCode(0x1f700 + i)), 40 | ].shuffled(Random(42)); 41 | final defaultStringInput = defaultCharsInput.join(); 42 | 43 | final List<({String name, Benchmark benchmark})> _benchmarkEntries = (() { 44 | Future.delayed(const Duration(milliseconds: 1)).then((_) { 45 | for (final (:name, :benchmark) in _benchmarkEntries) { 46 | if (optionFilter == null || optionFilter == name) benchmark(); 47 | } 48 | }); 49 | return SortedList<({String name, Benchmark benchmark})>( 50 | comparator: compareAsciiLowerCase.keyOf((entry) => entry.name), 51 | ); 52 | })(); 53 | 54 | final numberPrinter = FixedNumberPrinter(precision: 3); 55 | 56 | /// Generic benchmark runner. 57 | void run( 58 | String name, { 59 | required Benchmark verify, 60 | required Benchmark parse, 61 | required Benchmark accept, 62 | Benchmark? native, 63 | }) { 64 | _benchmarkEntries.add(( 65 | name: name, 66 | benchmark: () { 67 | // Print name. 68 | stdout.write(name); 69 | // Verification. 70 | if (optionVerification) { 71 | try { 72 | verify(); 73 | if (!optionBenchmark) { 74 | stdout.write('\tOK'); 75 | } 76 | } catch (error) { 77 | stdout.writeln('\t$error'); 78 | return; 79 | } 80 | } 81 | // Benchmark. 82 | if (optionBenchmark) { 83 | final benchmarks = [ 84 | benchmark(parse), 85 | benchmark(accept), 86 | native?.also(benchmark), 87 | ].whereType>(); 88 | for (final benchmark in benchmarks) { 89 | stdout.write(optionSeparator); 90 | stdout.write(numberPrinter(benchmark.estimate)); 91 | if (optionPrintStandardError) { 92 | if (optionHumanReadable) { 93 | stdout.write(' ± '); 94 | } else { 95 | stdout.write(optionSeparator); 96 | } 97 | stdout.writeln(numberPrinter(benchmark.standardError)); 98 | } 99 | if (optionPrintConfidenceIntervals) { 100 | if (optionHumanReadable) { 101 | stdout.write( 102 | ' [${numberPrinter(benchmark.lowerBound)}; ' 103 | '${numberPrinter(benchmark.upperBound)}]', 104 | ); 105 | } else { 106 | stdout.write(optionSeparator); 107 | stdout.write(numberPrinter(benchmark.lowerBound)); 108 | stdout.write(optionSeparator); 109 | stdout.write(numberPrinter(benchmark.upperBound)); 110 | } 111 | } 112 | } 113 | } 114 | // Complete. 115 | stdout.writeln(); 116 | }, 117 | )); 118 | } 119 | 120 | /// Generic character benchmark runner. 121 | void runChars(String name, Parser parser, {int? success, String? input}) { 122 | final input_ = input ?? defaultStringInput; 123 | final inputLength = input_.length; 124 | final success_ = success ?? input_.length; 125 | run( 126 | name, 127 | verify: () { 128 | var count = 0; 129 | for (var i = 0; i < inputLength; i++) { 130 | if (parser.accept(input_, start: i)) count++; 131 | } 132 | if (success_ != count) { 133 | throw StateError('Expected $success_ successes, but got $count'); 134 | } 135 | }, 136 | parse: () { 137 | for (var i = 0; i < inputLength; i++) { 138 | parser.parse(input_, start: i); 139 | } 140 | }, 141 | accept: () { 142 | for (var i = 0; i < inputLength; i++) { 143 | parser.accept(input_, start: i); 144 | } 145 | }, 146 | ); 147 | } 148 | 149 | /// Generic string benchmark runner. 150 | void runString( 151 | String name, 152 | Parser parser, { 153 | int? position, 154 | String? input, 155 | }) { 156 | final input_ = input ?? defaultStringInput; 157 | final position_ = position ?? input_.length; 158 | run( 159 | name, 160 | verify: () { 161 | final result = parser.parse(input_); 162 | if (result is Failure) { 163 | throw StateError( 164 | 'Expected parse success, but got ${result.message} ' 165 | 'at ${result.position}', 166 | ); 167 | } 168 | if (result.position != position_) { 169 | throw StateError( 170 | 'Expected parse success at $position_, but succeeded ' 171 | 'at ${result.position}', 172 | ); 173 | } 174 | }, 175 | parse: () => parser.parse(input_), 176 | accept: () => parser.accept(input_), 177 | ); 178 | } 179 | -------------------------------------------------------------------------------- /lib/src/regexp/node.dart: -------------------------------------------------------------------------------- 1 | import 'nfa.dart'; 2 | import 'parser.dart'; 3 | 4 | abstract class Node { 5 | static Node fromString(String regexp) => nodeParser.parse(regexp).value; 6 | 7 | Nfa toNfa(); 8 | } 9 | 10 | class EmptyNode extends Node { 11 | @override 12 | Nfa toNfa() { 13 | final start = NfaState(isEnd: false); 14 | final end = NfaState(isEnd: true); 15 | start.epsilons.add(end); 16 | return Nfa(start: start, end: end); 17 | } 18 | 19 | @override 20 | String toString() => 'EmptyNode()'; 21 | 22 | @override 23 | bool operator ==(Object other) => other is EmptyNode; 24 | 25 | @override 26 | int get hashCode => runtimeType.hashCode; 27 | } 28 | 29 | class DotNode extends Node { 30 | @override 31 | Nfa toNfa() { 32 | final start = NfaState(isEnd: false); 33 | final end = NfaState(isEnd: true); 34 | start.dots.add(end); 35 | return Nfa(start: start, end: end); 36 | } 37 | 38 | @override 39 | String toString() => 'DotNode()'; 40 | 41 | @override 42 | bool operator ==(Object other) => other is DotNode; 43 | 44 | @override 45 | int get hashCode => runtimeType.hashCode; 46 | } 47 | 48 | class LiteralNode extends Node { 49 | LiteralNode(String literal) : codePoint = literal.runes.single; 50 | 51 | final int codePoint; 52 | 53 | @override 54 | Nfa toNfa() { 55 | final start = NfaState(isEnd: false); 56 | final end = NfaState(isEnd: true); 57 | start.transitions[codePoint] = end; 58 | return Nfa(start: start, end: end); 59 | } 60 | 61 | @override 62 | String toString() => 'LiteralNode(${String.fromCharCode(codePoint)})'; 63 | 64 | @override 65 | bool operator ==(Object other) => 66 | other is LiteralNode && other.codePoint == codePoint; 67 | 68 | @override 69 | int get hashCode => Object.hash(runtimeType, codePoint); 70 | } 71 | 72 | class ConcatenationNode extends Node { 73 | ConcatenationNode(this.left, this.right); 74 | 75 | final Node left; 76 | final Node right; 77 | 78 | @override 79 | Nfa toNfa() { 80 | final leftNfa = left.toNfa(); 81 | final rightNfa = right.toNfa(); 82 | leftNfa.end.epsilons.add(rightNfa.start); 83 | leftNfa.end.isEnd = false; 84 | return Nfa(start: leftNfa.start, end: rightNfa.end); 85 | } 86 | 87 | @override 88 | String toString() => 'ConcatenationNode($left, $right)'; 89 | 90 | @override 91 | bool operator ==(Object other) => 92 | other is ConcatenationNode && other.left == left && other.right == right; 93 | 94 | @override 95 | int get hashCode => Object.hash(runtimeType, left, right); 96 | } 97 | 98 | class AlternationNode extends Node { 99 | AlternationNode(this.left, this.right); 100 | 101 | final Node left; 102 | final Node right; 103 | 104 | @override 105 | Nfa toNfa() { 106 | final start = NfaState(isEnd: false); 107 | final end = NfaState(isEnd: true); 108 | 109 | final leftNfa = left.toNfa(); 110 | start.epsilons.add(leftNfa.start); 111 | leftNfa.end.epsilons.add(end); 112 | leftNfa.end.isEnd = false; 113 | 114 | final rightNfa = right.toNfa(); 115 | start.epsilons.add(rightNfa.start); 116 | rightNfa.end.epsilons.add(end); 117 | rightNfa.end.isEnd = false; 118 | 119 | return Nfa(start: start, end: end); 120 | } 121 | 122 | @override 123 | String toString() => 'AlternationNode($left, $right)'; 124 | 125 | @override 126 | bool operator ==(Object other) => 127 | other is AlternationNode && other.left == left && other.right == right; 128 | 129 | @override 130 | int get hashCode => Object.hash(runtimeType, left, right); 131 | } 132 | 133 | class IntersectionNode extends Node { 134 | IntersectionNode(this.left, this.right); 135 | 136 | final Node left; 137 | final Node right; 138 | 139 | @override 140 | Nfa toNfa() => throw UnsupportedError(toString()); 141 | 142 | @override 143 | String toString() => 'IntersectionNode($left, $right)'; 144 | 145 | @override 146 | bool operator ==(Object other) => 147 | other is IntersectionNode && other.left == left && other.right == right; 148 | 149 | @override 150 | int get hashCode => Object.hash(runtimeType, left, right); 151 | } 152 | 153 | class QuantificationNode extends Node { 154 | QuantificationNode(this.child, this.min, [this.max]); 155 | 156 | final Node child; 157 | final int min; 158 | final int? max; 159 | 160 | @override 161 | Nfa toNfa() { 162 | final start = NfaState(isEnd: false); 163 | final end = NfaState(isEnd: true); 164 | final childNfa = child.toNfa(); 165 | if (min == 0 && max == null) { 166 | start.epsilons.add(end); 167 | start.epsilons.add(childNfa.start); 168 | childNfa.end.epsilons.add(end); 169 | childNfa.end.epsilons.add(childNfa.start); 170 | childNfa.end.isEnd = false; 171 | } else if (min == 0 && max == 1) { 172 | start.epsilons.add(end); 173 | start.epsilons.add(childNfa.start); 174 | childNfa.end.epsilons.add(end); 175 | childNfa.end.isEnd = false; 176 | } else if (min == 1 && max == null) { 177 | start.epsilons.add(childNfa.start); 178 | childNfa.end.epsilons.add(end); 179 | childNfa.end.epsilons.add(childNfa.start); 180 | childNfa.end.isEnd = false; 181 | } else { 182 | throw UnsupportedError(toString()); 183 | } 184 | return Nfa(start: start, end: end); 185 | } 186 | 187 | @override 188 | String toString() => 'QuantifierNode($child, $min, $max)'; 189 | 190 | @override 191 | bool operator ==(Object other) => 192 | other is QuantificationNode && 193 | other.child == child && 194 | other.min == min && 195 | other.max == max; 196 | 197 | @override 198 | int get hashCode => Object.hash(runtimeType, child, min, max); 199 | } 200 | 201 | class ComplementNode extends Node { 202 | ComplementNode(this.child); 203 | 204 | final Node child; 205 | 206 | @override 207 | Nfa toNfa() => throw UnsupportedError(toString()); 208 | 209 | @override 210 | String toString() => 'ComplementNode($child)'; 211 | 212 | @override 213 | bool operator ==(Object other) => 214 | other is ComplementNode && other.child == child; 215 | 216 | @override 217 | int get hashCode => Object.hash(runtimeType, child); 218 | } 219 | -------------------------------------------------------------------------------- /web/xml/xml.dart: -------------------------------------------------------------------------------- 1 | import 'dart:js_interop'; 2 | 3 | import 'package:more/collection.dart'; 4 | import 'package:web/web.dart'; 5 | import 'package:xml/xml.dart'; 6 | import 'package:xml/xml_events.dart'; 7 | import 'package:xml/xpath.dart'; 8 | 9 | final xmlInput = document.querySelector('#xml-input') as HTMLInputElement; 10 | final xpathInput = document.querySelector('#xpath-input') as HTMLInputElement; 11 | final xpathError = document.querySelector('#xpath-error') as HTMLElement; 12 | final domPretty = document.querySelector('#dom-pretty') as HTMLInputElement; 13 | final saxOutput = document.querySelector('#sax-output') as HTMLElement; 14 | final domOutput = document.querySelector('#dom-output') as HTMLElement; 15 | 16 | Element appendString(Element element, String object) { 17 | object 18 | .split('\n') 19 | .where((data) => data.trim().isNotEmpty) 20 | .map((data) => document.createTextNode(data)) 21 | .separatedBy(() => document.createElement('br')) 22 | .forEach((node) => element.append(node)); 23 | return element; 24 | } 25 | 26 | void appendLine(Element target, String? data, {Iterable? classes}) { 27 | final element = document.createElement('div'); 28 | if (classes != null) element.classList.value = classes.join(' '); 29 | element.append(document.createTextNode(data.toString())); 30 | target.append(element); 31 | } 32 | 33 | void appendSax(String type, [String? first, String? second]) { 34 | final element = document.createElement('div'); 35 | element.append(appendString(document.createElement('span'), type)); 36 | element.append(appendString(document.createElement('span'), first ?? '')); 37 | element.append(appendString(document.createElement('span'), second ?? '')); 38 | saxOutput.append(element); 39 | } 40 | 41 | void update() { 42 | // Clear the output 43 | domOutput.innerText = ''; 44 | saxOutput.innerText = ''; 45 | 46 | // Process the XML event stream 47 | final eventStream = Stream.value(xmlInput.value) 48 | .toXmlEvents(withLocation: true) 49 | .tapEachEvent( 50 | onCDATA: (event) => appendSax('CDATA', event.value), 51 | onComment: (event) => appendSax('Comment', event.value), 52 | onDeclaration: (event) => appendSax( 53 | 'Declaration', 54 | event.attributes 55 | .map((attr) => '${attr.name}=${attr.value}') 56 | .join('\n'), 57 | ), 58 | onDoctype: (event) => 59 | appendSax('Doctype', event.name, event.externalId?.toString()), 60 | onEndElement: (event) => appendSax('End Element', event.name), 61 | onProcessing: (event) => 62 | appendSax('Processing', event.target, event.value), 63 | onStartElement: (event) => appendSax( 64 | 'Element${event.isSelfClosing ? ' (self-closing)' : ''}', 65 | event.name, 66 | event.attributes 67 | .map((attr) => '${attr.name}=${attr.value}') 68 | .join('\n'), 69 | ), 70 | onText: (event) => appendSax('Text', event.value), 71 | ) 72 | .handleError( 73 | (error) => appendLine(saxOutput, error.toString(), classes: ['error']), 74 | ); 75 | 76 | // Process the DOM stream 77 | eventStream.toXmlNodes().flatten().toList().then( 78 | (elements) => updateDom(XmlDocument(elements)..normalize()), 79 | onError: (error) => 80 | appendLine(saxOutput, error.toString(), classes: ['error']), 81 | ); 82 | } 83 | 84 | void updateDom(XmlDocument document) { 85 | // If desired, pretty print the document. 86 | if (domPretty.checked == true) { 87 | document = XmlDocument.parse(document.toXmlString(pretty: true)); 88 | } 89 | // Find the XPath matches. 90 | final matches = {}; 91 | try { 92 | matches.addAll(document.xpath(xpathInput.value)); 93 | xpathError.innerText = ''; 94 | } catch (error) { 95 | xpathError.innerText = error.toString(); 96 | } 97 | // Render the highlighted document. 98 | HighlightWriter(HtmlBuffer(domOutput), matches).visit(document); 99 | } 100 | 101 | void selectDom(MouseEvent event) { 102 | for ( 103 | var node = event.target as Node?; 104 | node != null && node != domOutput; 105 | node = node.parentNode 106 | ) { 107 | if (node.isA()) { 108 | final element = node as HTMLElement; 109 | final path = element.getAttribute('title'); 110 | if (path != null && path.isNotEmpty) { 111 | xpathInput.value = path; 112 | update(); 113 | break; 114 | } 115 | } 116 | } 117 | } 118 | 119 | class HtmlBuffer implements StringSink { 120 | HtmlBuffer(Element root) { 121 | stack.add(root); 122 | } 123 | 124 | final List stack = []; 125 | 126 | void nest(Map attributes, void Function() function) { 127 | final element = document.createElement('span'); 128 | for (final MapEntry(:key, :value) in attributes.entries) { 129 | if (value != null && value.isNotEmpty) { 130 | element.setAttribute(key, value); 131 | } 132 | } 133 | stack.last.appendChild(element); 134 | stack.add(element); 135 | function(); 136 | stack.removeLast(); 137 | } 138 | 139 | @override 140 | void write(Object? object) { 141 | object 142 | .toString() 143 | .split('\n') 144 | .map((data) => document.createTextNode(data)) 145 | .separatedBy(() => document.createElement('br')) 146 | .forEach((node) => stack.last.appendChild(node)); 147 | } 148 | 149 | @override 150 | void writeAll(Iterable objects, [String separator = ""]) => 151 | throw UnimplementedError(); 152 | 153 | @override 154 | void writeCharCode(int charCode) => throw UnimplementedError(); 155 | 156 | @override 157 | void writeln([Object? object = ""]) => throw UnimplementedError(); 158 | } 159 | 160 | class HighlightWriter extends XmlWriter { 161 | HighlightWriter(this.htmlBuffer, this.matches) : super(htmlBuffer); 162 | 163 | final HtmlBuffer htmlBuffer; 164 | 165 | final Set matches; 166 | 167 | @override 168 | void visit(XmlHasVisitor node) => htmlBuffer.nest({ 169 | 'class': matches.contains(node) ? 'selection' : null, 170 | 'title': node is XmlNode ? node.xpathGenerate() : null, 171 | }, () => super.visit(node)); 172 | } 173 | 174 | void main() { 175 | xmlInput.onInput.listen((event) => update()); 176 | xpathInput.onInput.listen((event) => update()); 177 | domPretty.onInput.listen((event) => update()); 178 | domOutput.onClick.listen(selectDom); 179 | update(); 180 | } 181 | -------------------------------------------------------------------------------- /lib/src/prolog/evaluator.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:meta/meta.dart'; 3 | import 'package:more/collection.dart'; 4 | 5 | import 'parser.dart'; 6 | 7 | const Equality> argumentEquality = ListEquality(); 8 | 9 | Map newBindings() => Map.identity(); 10 | 11 | Map? mergeBindings( 12 | Map? first, 13 | Map? second, 14 | ) { 15 | if (first == null || second == null) { 16 | return null; 17 | } 18 | final result = newBindings()..addAll(first); 19 | for (final key in second.keys) { 20 | final value = second[key]!; 21 | final other = result[key]; 22 | if (other != null) { 23 | final subs = other.match(value); 24 | if (subs == null) { 25 | return null; 26 | } else { 27 | result.addAll(subs); 28 | } 29 | } else { 30 | result[key] = value; 31 | } 32 | } 33 | return result; 34 | } 35 | 36 | @immutable 37 | final class Database { 38 | factory Database.parse(String rules) => 39 | Database(rulesParser.parse(rules).value); 40 | 41 | Database(Iterable rules) { 42 | for (final rule in rules) { 43 | this.rules.putIfAbsent(rule.head.name, () => []).add(rule); 44 | } 45 | } 46 | 47 | final Map> rules = {}; 48 | 49 | Iterable query(Term goal) { 50 | final candidates = rules[goal.name]; 51 | if (candidates == null) return const []; 52 | return candidates.expand((rule) => rule.query(this, goal)); 53 | } 54 | 55 | @override 56 | String toString() => 57 | rules.values.map((rules) => rules.join('\n')).join('\n\n'); 58 | } 59 | 60 | @immutable 61 | final class Rule { 62 | const Rule(this.head, this.body); 63 | 64 | final Term head; 65 | final Term body; 66 | 67 | Iterable query(Database database, Term goal) { 68 | final match = head.match(goal); 69 | if (match == null) return const []; 70 | final newHead = head.substitute(match); 71 | final newBody = body.substitute(match); 72 | return newBody 73 | .query(database) 74 | .map((item) => newHead.substitute(newBody.match(item))); 75 | } 76 | 77 | @override 78 | String toString() => '$head :- $body.'; 79 | } 80 | 81 | @immutable 82 | abstract class Node { 83 | const Node(); 84 | 85 | Map? match(Node other); 86 | 87 | Node substitute(Map? bindings); 88 | } 89 | 90 | @immutable 91 | class Variable extends Node { 92 | const Variable(this.name); 93 | 94 | final String name; 95 | 96 | @override 97 | Map? match(Node other) { 98 | final bindings = newBindings(); 99 | if (this != other) { 100 | bindings[this] = other; 101 | } 102 | return bindings; 103 | } 104 | 105 | @override 106 | Node substitute(Map? bindings) { 107 | if (bindings != null) { 108 | final value = bindings[this]; 109 | if (value != null) { 110 | return value.substitute(bindings); 111 | } 112 | } 113 | return this; 114 | } 115 | 116 | @override 117 | bool operator ==(Object other) => other is Variable && name == other.name; 118 | 119 | @override 120 | int get hashCode => name.hashCode; 121 | 122 | @override 123 | String toString() => name; 124 | } 125 | 126 | @immutable 127 | class Term extends Node { 128 | factory Term.parse(String rules) => termParser.parse(rules).value; 129 | 130 | factory Term(String name, Iterable list) => 131 | Term._(name, list.toList(growable: false)); 132 | 133 | const Term._(this.name, this.arguments); 134 | 135 | final String name; 136 | final List arguments; 137 | 138 | Iterable query(Database database) => database.query(this); 139 | 140 | @override 141 | Map? match(Node other) { 142 | if (other is Term) { 143 | if (name != other.name) { 144 | return null; 145 | } 146 | if (arguments.length != other.arguments.length) { 147 | return null; 148 | } 149 | return [arguments, other.arguments] 150 | .zip() 151 | .map((arg) => arg[0].match(arg[1])) 152 | .fold(newBindings(), mergeBindings); 153 | } 154 | return other.match(this); 155 | } 156 | 157 | @override 158 | Term substitute(Map? bindings) => 159 | Term(name, arguments.map((arg) => arg.substitute(bindings))); 160 | 161 | @override 162 | bool operator ==(Object other) => 163 | other is Term && 164 | name == other.name && 165 | argumentEquality.equals(arguments, other.arguments); 166 | 167 | @override 168 | int get hashCode => name.hashCode ^ argumentEquality.hash(arguments); 169 | 170 | @override 171 | String toString() => 172 | arguments.isEmpty ? name : '$name(${arguments.join(', ')})'; 173 | } 174 | 175 | @immutable 176 | class True extends Term { 177 | const True() : super._('true', const []); 178 | 179 | @override 180 | Term substitute(Map? bindings) => this; 181 | 182 | @override 183 | Iterable query(Database database) => [this]; 184 | } 185 | 186 | @immutable 187 | class Value extends Term { 188 | const Value(String name) : super._(name, const []); 189 | 190 | @override 191 | Iterable query(Database database) => [this]; 192 | 193 | @override 194 | Value substitute(Map? bindings) => this; 195 | 196 | @override 197 | bool operator ==(Object other) => other is Value && name == other.name; 198 | 199 | @override 200 | int get hashCode => name.hashCode; 201 | 202 | @override 203 | String toString() => name; 204 | } 205 | 206 | @immutable 207 | class Conjunction extends Term { 208 | factory Conjunction(Iterable list) => 209 | Conjunction._(list.toList(growable: false)); 210 | 211 | const Conjunction._(List args) : super._(',', args); 212 | 213 | @override 214 | Iterable query(Database database) { 215 | Iterable solutions(int index, Map bindings) sync* { 216 | if (index < arguments.length) { 217 | final arg = arguments[index]; 218 | final subs = arg.substitute(bindings) as Term; 219 | for (final item in database.query(subs)) { 220 | final unified = mergeBindings(arg.match(item), bindings); 221 | if (unified != null) { 222 | yield* solutions(index + 1, unified); 223 | } 224 | } 225 | } else { 226 | yield substitute(bindings); 227 | } 228 | } 229 | 230 | return solutions(0, newBindings()); 231 | } 232 | 233 | @override 234 | Conjunction substitute(Map? bindings) => 235 | Conjunction(arguments.map((arg) => arg.substitute(bindings))); 236 | 237 | @override 238 | bool operator ==(Object other) => 239 | other is Conjunction && 240 | argumentEquality.equals(arguments, other.arguments); 241 | 242 | @override 243 | int get hashCode => argumentEquality.hash(arguments); 244 | 245 | @override 246 | String toString() => arguments.join(', '); 247 | } 248 | -------------------------------------------------------------------------------- /bin/benchmark/suites/character_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | import '../utils/runner.dart'; 4 | 5 | void main() { 6 | runChars('character - any', any(), success: 351); 7 | runChars('character - any (unicode)', any(unicode: true), success: 351); 8 | 9 | runChars('character - anyOf', anyOf('uncopyrightable'), success: 15); 10 | runChars( 11 | 'character - anyOf (unicode)', 12 | anyOf('uncopyrightable', unicode: true), 13 | success: 15, 14 | ); 15 | runChars( 16 | 'character - anyOf (ignore case)', 17 | anyOf('uncopyrightable', ignoreCase: true), 18 | success: 30, 19 | ); 20 | runChars( 21 | 'character - anyOf (ignore case, unicode)', 22 | anyOf('uncopyrightable', ignoreCase: true, unicode: true), 23 | success: 30, 24 | ); 25 | 26 | runChars('character - char', char('a'), success: 1); 27 | runChars('character - char (unicode)', char('a', unicode: true), success: 1); 28 | runChars( 29 | 'character - char (ignore case)', 30 | char('a', ignoreCase: true), 31 | success: 2, 32 | ); 33 | runChars( 34 | 'character - char (ignore case, unicode)', 35 | char('a', ignoreCase: true, unicode: true), 36 | success: 2, 37 | ); 38 | 39 | runChars('character - digit', digit(), success: 10); 40 | runChars('character - letter', letter(), success: 52); 41 | runChars('character - lowercase', lowercase(), success: 26); 42 | 43 | runChars('character - noneOf', noneOf('uncopyrightable'), success: 336); 44 | runChars( 45 | 'character - noneOf (unicode)', 46 | noneOf('uncopyrightable', unicode: true), 47 | success: 336, 48 | ); 49 | runChars( 50 | 'character - noneOf (ignore case)', 51 | noneOf('uncopyrightable', ignoreCase: true), 52 | success: 321, 53 | ); 54 | runChars( 55 | 'character - noneOf (ignore case, unicode)', 56 | noneOf('uncopyrightable', ignoreCase: true, unicode: true), 57 | success: 321, 58 | ); 59 | 60 | runChars('character - pattern - ^a', pattern('^a'), success: 350); 61 | runChars( 62 | 'character - pattern - ^a (ignore case)', 63 | pattern('^a', ignoreCase: true), 64 | success: 349, 65 | ); 66 | runChars( 67 | 'character - pattern - ^a (unicode)', 68 | pattern('^a', unicode: true), 69 | success: 350, 70 | ); 71 | runChars( 72 | 'character - pattern - ^a (ignore case, unicode)', 73 | pattern('^a', ignoreCase: true, unicode: true), 74 | success: 349, 75 | ); 76 | 77 | runChars( 78 | 'character - pattern - ^a-cx-zA-CX-Z1-37-9', 79 | pattern('^a-cx-zA-CX-Z1-37-9'), 80 | success: 333, 81 | ); 82 | runChars( 83 | 'character - pattern - ^a-cx-zA-CX-Z1-37-9 (ignore case)', 84 | pattern('^a-cx-zA-CX-Z1-37-9', ignoreCase: true), 85 | success: 333, 86 | ); 87 | runChars( 88 | 'character - pattern - ^a-cx-zA-CX-Z1-37-9 (unicode)', 89 | pattern('^a-cx-zA-CX-Z1-37-9', unicode: true), 90 | success: 333, 91 | ); 92 | runChars( 93 | 'character - pattern - ^a-cx-zA-CX-Z1-37-9 (ignore case, unicode)', 94 | pattern('^a-cx-zA-CX-Z1-37-9', ignoreCase: true, unicode: true), 95 | success: 333, 96 | ); 97 | 98 | runChars('character - pattern - ^a-z', pattern('^a-z'), success: 325); 99 | runChars( 100 | 'character - pattern - ^a-z (ignore case)', 101 | pattern('^a-z', ignoreCase: true), 102 | success: 299, 103 | ); 104 | runChars( 105 | 'character - pattern - ^a-z (unicode)', 106 | pattern('^a-z', unicode: true), 107 | success: 325, 108 | ); 109 | runChars( 110 | 'character - pattern - ^a-z (ignore case, unicode)', 111 | pattern('^a-z', ignoreCase: true, unicode: true), 112 | success: 299, 113 | ); 114 | 115 | runChars('character - pattern - ^acegik', pattern('^acegik'), success: 345); 116 | runChars( 117 | 'character - pattern - ^acegik (ignore case)', 118 | pattern('^acegik', ignoreCase: true), 119 | success: 339, 120 | ); 121 | runChars( 122 | 'character - pattern - ^acegik (unicode)', 123 | pattern('^acegik', unicode: true), 124 | success: 345, 125 | ); 126 | runChars( 127 | 'character - pattern - ^acegik (ignore case, unicode)', 128 | pattern('^acegik', ignoreCase: true, unicode: true), 129 | success: 339, 130 | ); 131 | 132 | runChars('character - pattern - a', pattern('a'), success: 1); 133 | runChars( 134 | 'character - pattern - a (ignore case)', 135 | pattern('a', ignoreCase: true), 136 | success: 2, 137 | ); 138 | runChars( 139 | 'character - pattern - a (unicode)', 140 | pattern('a', unicode: true), 141 | success: 1, 142 | ); 143 | runChars( 144 | 'character - pattern - a (ignore case, unicode)', 145 | pattern('a', ignoreCase: true, unicode: true), 146 | success: 2, 147 | ); 148 | 149 | runChars( 150 | 'character - pattern - a-cx-zA-CX-Z1-37-9', 151 | pattern('a-cx-zA-CX-Z1-37-9'), 152 | success: 18, 153 | ); 154 | runChars( 155 | 'character - pattern - a-cx-zA-CX-Z1-37-9 (ignore case)', 156 | pattern('a-cx-zA-CX-Z1-37-9', ignoreCase: true), 157 | success: 18, 158 | ); 159 | runChars( 160 | 'character - pattern - a-cx-zA-CX-Z1-37-9 (unicode)', 161 | pattern('a-cx-zA-CX-Z1-37-9', unicode: true), 162 | success: 18, 163 | ); 164 | runChars( 165 | 'character - pattern - a-cx-zA-CX-Z1-37-9 (ignore case, unicode)', 166 | pattern('a-cx-zA-CX-Z1-37-9', ignoreCase: true, unicode: true), 167 | success: 18, 168 | ); 169 | 170 | runChars('character - pattern - a-z', pattern('a-z'), success: 26); 171 | runChars( 172 | 'character - pattern - a-z (ignore case)', 173 | pattern('a-z', ignoreCase: true), 174 | success: 52, 175 | ); 176 | runChars( 177 | 'character - pattern - a-z (unicode)', 178 | pattern('a-z', unicode: true), 179 | success: 26, 180 | ); 181 | runChars( 182 | 'character - pattern - a-z (ignore case, unicode)', 183 | pattern('a-z', ignoreCase: true, unicode: true), 184 | success: 52, 185 | ); 186 | 187 | runChars('character - pattern - acegik', pattern('acegik'), success: 6); 188 | runChars( 189 | 'character - pattern - acegik (ignore case)', 190 | pattern('acegik', ignoreCase: true), 191 | success: 12, 192 | ); 193 | runChars( 194 | 'character - pattern - acegik (unicode)', 195 | pattern('acegik', unicode: true), 196 | success: 6, 197 | ); 198 | runChars( 199 | 'character - pattern - acegik (ignore case, unicode)', 200 | pattern('acegik', ignoreCase: true, unicode: true), 201 | success: 12, 202 | ); 203 | 204 | runChars('character - pattern - any', pattern('\u0000-\uffff')); 205 | runChars( 206 | 'character - pattern - any (unicode)', 207 | pattern('\u0000-\u{10ffff}', unicode: true), 208 | ); 209 | 210 | runChars('character - pattern - none', pattern('^\u0000-\uffff'), success: 0); 211 | runChars( 212 | 'character - pattern - none (unicode)', 213 | pattern('^\u0000-\u{10ffff}', unicode: true), 214 | success: 0, 215 | ); 216 | 217 | runChars('character - range', range('a', 'z'), success: 26); 218 | runChars( 219 | 'character - range (unicode)', 220 | range('a', 'z', unicode: true), 221 | success: 26, 222 | ); 223 | 224 | runChars('character - uppercase', uppercase(), success: 26); 225 | runChars('character - whitespace', whitespace(), success: 8); 226 | runChars('character - word', word(), success: 63); 227 | } 228 | -------------------------------------------------------------------------------- /lib/src/smalltalk/parser.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unnecessary_overrides 2 | import 'package:petitparser/petitparser.dart'; 3 | 4 | import 'ast.dart'; 5 | import 'grammar.dart'; 6 | 7 | /// Smalltalk parser definition. 8 | class SmalltalkParserDefinition extends SmalltalkGrammarDefinition { 9 | @override 10 | Parser array() => super.array().map( 11 | (input) => buildArray(input[1])..surroundWith(input[0], input[2]), 12 | ); 13 | 14 | @override 15 | Parser arrayLiteral() => super.arrayLiteral().map( 16 | (input) => 17 | LiteralArrayNode(input[1].cast().toList()) 18 | ..surroundWith(input[0], input[2]), 19 | ); 20 | 21 | @override 22 | Parser arrayLiteralArray() => super.arrayLiteralArray().map( 23 | (input) => 24 | LiteralArrayNode(input[1].cast().toList()) 25 | ..surroundWith(input[0], input[2]), 26 | ); 27 | 28 | @override 29 | Parser binaryExpression() => 30 | super.binaryExpression().map((input) => buildMessage(input[0], input[1])); 31 | 32 | @override 33 | Parser block() => 34 | super.block().map((input) => input[1]..surroundWith(input[0], input[2])); 35 | 36 | @override 37 | Parser blockArgument() => super.blockArgument(); 38 | 39 | @override 40 | Parser blockBody() => 41 | super.blockBody().map((input) => buildBlock(input[0], input[1])); 42 | 43 | @override 44 | Parser byteLiteral() => super.byteLiteral().map( 45 | (input) => 46 | LiteralArrayNode(input[1].cast>().toList()) 47 | ..surroundWith(input[0], input[2]), 48 | ); 49 | 50 | @override 51 | Parser byteLiteralArray() => super.byteLiteralArray().map( 52 | (input) => 53 | LiteralArrayNode(input[1].cast>().toList()) 54 | ..surroundWith(input[0], input[2]), 55 | ); 56 | 57 | @override 58 | Parser characterLiteral() => super.characterLiteral().map( 59 | (input) => LiteralValueNode(input, input.value.substring(1)), 60 | ); 61 | 62 | @override 63 | Parser cascadeExpression() => super.cascadeExpression().map( 64 | (input) => buildCascade(input[0], input[1]), 65 | ); 66 | 67 | @override 68 | Parser expression() => 69 | super.expression().map((input) => buildAssignment(input[1], input[0])); 70 | 71 | @override 72 | Parser expressionReturn() => 73 | super.expressionReturn().map((input) => ReturnNode(input[0], input[1])); 74 | 75 | @override 76 | Parser falseLiteral() => 77 | super.falseLiteral().map((input) => LiteralValueNode(input, false)); 78 | 79 | @override 80 | Parser keywordExpression() => super.keywordExpression().map( 81 | (input) => buildMessage(input[0], [input[1]]), 82 | ); 83 | 84 | @override 85 | Parser method() => super.method().map((input) => buildMethod(input)); 86 | 87 | @override 88 | Parser nilLiteral() => 89 | super.nilLiteral().map((input) => LiteralValueNode(input, null)); 90 | 91 | @override 92 | Parser numberLiteral() => super.numberLiteral().map( 93 | (input) => LiteralValueNode(input, buildNumber(input.value)), 94 | ); 95 | 96 | @override 97 | Parser parens() => 98 | super.parens().map((input) => input[1]..surroundWith(input[0], input[2])); 99 | 100 | @override 101 | Parser pragma() => super.pragma().map( 102 | (input) => buildPragma(input[1])..surroundWith(input[0], input[2]), 103 | ); 104 | 105 | @override 106 | Parser sequence() => super.sequence().map( 107 | (input) => buildSequence(input[0], [input[1], input[2]]), 108 | ); 109 | 110 | @override 111 | Parser stringLiteral() => super.stringLiteral().map( 112 | (input) => LiteralValueNode(input, buildString(input.value)), 113 | ); 114 | 115 | @override 116 | Parser symbolLiteral() => super.symbolLiteral().map( 117 | (input) => LiteralValueNode( 118 | Token.join([...input[0], input[1]]), 119 | buildString(input[1].value), 120 | ), 121 | ); 122 | 123 | @override 124 | Parser symbolLiteralArray() => super.symbolLiteralArray().map( 125 | (input) => LiteralValueNode(input, buildString(input.value)), 126 | ); 127 | 128 | @override 129 | Parser unaryExpression() => 130 | super.unaryExpression().map((input) => buildMessage(input[0], input[1])); 131 | 132 | @override 133 | Parser trueLiteral() => 134 | super.trueLiteral().map((input) => LiteralValueNode(input, true)); 135 | 136 | @override 137 | Parser variable() => super.variable().map((input) => VariableNode(input)); 138 | } 139 | 140 | // Build different node types 141 | 142 | ArrayNode buildArray(List statements) { 143 | final result = ArrayNode(); 144 | addTo(result.statements, statements); 145 | addTo(result.periods, statements); 146 | return result; 147 | } 148 | 149 | ValueNode buildAssignment(ValueNode node, List parts) => parts.reversed.fold( 150 | node, 151 | (result, variableAndToken) => 152 | AssignmentNode(variableAndToken[0], variableAndToken[1], result), 153 | ); 154 | 155 | ValueNode buildBlock(List arguments, SequenceNode body) { 156 | final result = BlockNode(body); 157 | addTo(result.arguments, arguments); 158 | addTo(result.separators, arguments); 159 | return result; 160 | } 161 | 162 | ValueNode buildCascade(ValueNode value, List parts) { 163 | if (parts.isNotEmpty) { 164 | final result = CascadeNode(); 165 | result.messages.add(value as MessageNode); 166 | for (final part in parts) { 167 | final message = buildMessage(result.receiver, [part[1]]); 168 | result.messages.add(message as MessageNode); 169 | result.semicolons.add(part[0]); 170 | } 171 | return result; 172 | } 173 | return value; 174 | } 175 | 176 | ValueNode buildMessage(ValueNode receiver, List parts) => parts 177 | .where((selectorAndArguments) => selectorAndArguments.isNotEmpty) 178 | .fold(receiver, (receiver, selectorAndArguments) { 179 | final message = MessageNode(receiver); 180 | addTo(message.selectorToken, selectorAndArguments); 181 | addTo(message.arguments, selectorAndArguments); 182 | return message; 183 | }); 184 | 185 | MethodNode buildMethod(List parts) { 186 | final result = MethodNode(); 187 | addTo(result.selectorToken, parts[0]); 188 | addTo(result.arguments, parts[0]); 189 | addTo(result.pragmas, parts[1]); 190 | addTo(result.body.temporaries, parts[1][3]); 191 | addTo(result.body.statements, parts[1][7]); 192 | addTo(result.body.periods, parts[1][7]); 193 | return result; 194 | } 195 | 196 | PragmaNode buildPragma(List parts) { 197 | final result = PragmaNode(); 198 | addTo(result.selectorToken, parts); 199 | addTo(result.arguments, parts); 200 | return result; 201 | } 202 | 203 | SequenceNode buildSequence(List temporaries, List statements) { 204 | final result = SequenceNode(); 205 | addTo(result.temporaries, temporaries); 206 | addTo(result.statements, statements); 207 | addTo(result.periods, statements); 208 | return result; 209 | } 210 | 211 | // Various other helpers. 212 | 213 | void addTo(List target, List parts) { 214 | for (final part in parts) { 215 | if (part is T) { 216 | target.add(part); 217 | } else if (part is List) { 218 | addTo(target, part); 219 | } 220 | } 221 | } 222 | 223 | num buildNumber(String input) { 224 | final values = input.split('r'); 225 | return values.length == 1 226 | ? num.parse(values[0]) 227 | : values.length == 2 228 | ? int.parse(values[1], radix: int.parse(values[0])) 229 | : throw ArgumentError.value(input, 'number', 'Unable to parse'); 230 | } 231 | 232 | String buildString(String input) => 233 | input.isNotEmpty && input.startsWith("'") && input.startsWith("'") 234 | ? input.substring(1, input.length - 1).replaceAll("''", "'") 235 | : input; 236 | -------------------------------------------------------------------------------- /test/prolog_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | import 'package:petitparser/reflection.dart'; 3 | import 'package:petitparser_examples/prolog.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | group('database', () { 8 | test('linter', () { 9 | expect(linter(rulesParser, excludedTypes: {}), isEmpty); 10 | }); 11 | test('empty', () { 12 | final db = Database.parse('foo.'); 13 | expect(db.toString(), 'foo :- true.'); 14 | }); 15 | test('single', () { 16 | final db = Database.parse('foo(a, b).'); 17 | expect(db.toString(), 'foo(a, b) :- true.'); 18 | }); 19 | test('multiple', () { 20 | final db = Database.parse(''' 21 | foo(X, Y) :- foo(Y, X). 22 | foo(X, Z) :- foo(X, Y), foo(Y, Z). 23 | '''); 24 | expect( 25 | db.toString(), 26 | 'foo(X, Y) :- foo(Y, X).\n' 27 | 'foo(X, Z) :- foo(X, Y), foo(Y, Z).', 28 | ); 29 | }); 30 | test('two', () { 31 | final db = Database.parse(''' 32 | foo(a, b). 33 | foo(X, Y) :- foo(Y, X). 34 | foo(X, Z) :- foo(X, Y), foo(Y, Z). 35 | '''); 36 | expect( 37 | db.toString(), 38 | 'foo(a, b) :- true.\n' 39 | 'foo(X, Y) :- foo(Y, X).\n' 40 | 'foo(X, Z) :- foo(X, Y), foo(Y, Z).', 41 | ); 42 | }); 43 | test('parse error', () { 44 | expect( 45 | () => Database.parse('1'), 46 | throwsA( 47 | isA() 48 | .having((e) => e.message, 'message', 'end of input expected') 49 | .having((e) => e.offset, 'offset', 0), 50 | ), 51 | ); 52 | }); 53 | }); 54 | group('term', () { 55 | test('linter', () { 56 | expect(linter(termParser, excludedTypes: {}), isEmpty); 57 | }); 58 | test('empty', () { 59 | final query = Term.parse('foo'); 60 | expect(query.toString(), 'foo'); 61 | }); 62 | test('one', () { 63 | final query = Term.parse('foo(bar)'); 64 | expect(query.toString(), 'foo(bar)'); 65 | }); 66 | test('two', () { 67 | final query = Term.parse('foo(bar, zork)'); 68 | expect(query.toString(), 'foo(bar, zork)'); 69 | }); 70 | test('parse error', () { 71 | expect( 72 | () => Term.parse('1'), 73 | throwsA( 74 | isA() 75 | .having((e) => e.message, 'message', 'Value expected') 76 | .having((e) => e.offset, 'offset', 0), 77 | ), 78 | ); 79 | }); 80 | }); 81 | group('Forrester family', () { 82 | final db = Database.parse(''' 83 | father_child(massimo, ridge). 84 | father_child(eric, thorne). 85 | father_child(thorne, alexandria). 86 | 87 | mother_child(stephanie, thorne). 88 | mother_child(stephanie, kristen). 89 | mother_child(stephanie, felicia). 90 | 91 | parent_child(X, Y) :- father_child(X, Y). 92 | parent_child(X, Y) :- mother_child(X, Y). 93 | 94 | sibling(X, Y) :- parent_child(Z, X), parent_child(Z, Y). 95 | 96 | ancestor(X, Y) :- parent_child(X, Y). 97 | ancestor(X, Y) :- parent_child(X, Z), ancestor(Z, Y). 98 | '''); 99 | test('eric son of thorne', () async { 100 | final query = Term.parse('father_child(eric, thorne)'); 101 | expect(db.query(query), [Term.parse('father_child(eric, thorne)')]); 102 | }); 103 | test('children of stephanie', () async { 104 | final query = Term.parse('mother_child(stephanie, X)'); 105 | expect(db.query(query), [ 106 | Term.parse('mother_child(stephanie, thorne)'), 107 | Term.parse('mother_child(stephanie, kristen)'), 108 | Term.parse('mother_child(stephanie, felicia)'), 109 | ]); 110 | }); 111 | test('fathers and children', () async { 112 | final query = Term.parse('father_child(X, Y)'); 113 | expect(db.query(query), [ 114 | Term.parse('father_child(massimo, ridge)'), 115 | Term.parse('father_child(eric, thorne)'), 116 | Term.parse('father_child(thorne, alexandria)'), 117 | ]); 118 | }); 119 | test('parents of thorne', () async { 120 | final query = Term.parse('parent_child(X, thorne)'); 121 | expect(db.query(query), [ 122 | Term.parse('parent_child(eric, thorne)'), 123 | Term.parse('parent_child(stephanie, thorne)'), 124 | ]); 125 | }); 126 | test('parents and children', () async { 127 | final query = Term.parse('parent_child(X, Y)'); 128 | expect(db.query(query), [ 129 | Term.parse('parent_child(massimo, ridge)'), 130 | Term.parse('parent_child(eric, thorne)'), 131 | Term.parse('parent_child(thorne, alexandria)'), 132 | Term.parse('parent_child(stephanie, thorne)'), 133 | Term.parse('parent_child(stephanie, kristen)'), 134 | Term.parse('parent_child(stephanie, felicia)'), 135 | ]); 136 | }); 137 | test('siblings of felicia', () async { 138 | final query = Term.parse('sibling(X, felicia)'); 139 | expect(db.query(query), [ 140 | Term.parse('sibling(thorne, felicia)'), 141 | Term.parse('sibling(kristen, felicia)'), 142 | Term.parse('sibling(felicia, felicia)'), 143 | ]); 144 | }); 145 | test('ancestors of alexandria', () { 146 | final query = Term.parse('ancestor(X, alexandria)'); 147 | expect(db.query(query), [ 148 | Term.parse('ancestor(thorne, alexandria)'), 149 | Term.parse('ancestor(eric, alexandria)'), 150 | Term.parse('ancestor(stephanie, alexandria)'), 151 | ]); 152 | }); 153 | }); 154 | group("Einstein's Problem", () { 155 | // https://mathforum.org/library/drmath/view/60971.html 156 | final db = Database.parse(''' 157 | exists(A, list(A, _, _, _, _)). 158 | exists(A, list(_, A, _, _, _)). 159 | exists(A, list(_, _, A, _, _)). 160 | exists(A, list(_, _, _, A, _)). 161 | exists(A, list(_, _, _, _, A)). 162 | 163 | rightOf(R, L, list(L, R, _, _, _)). 164 | rightOf(R, L, list(_, L, R, _, _)). 165 | rightOf(R, L, list(_, _, L, R, _)). 166 | rightOf(R, L, list(_, _, _, L, R)). 167 | 168 | middle(A, list(_, _, A, _, _)). 169 | 170 | first(A, list(A, _, _, _, _)). 171 | 172 | nextTo(A, B, list(B, A, _, _, _)). 173 | nextTo(A, B, list(_, B, A, _, _)). 174 | nextTo(A, B, list(_, _, B, A, _)). 175 | nextTo(A, B, list(_, _, _, B, A)). 176 | nextTo(A, B, list(A, B, _, _, _)). 177 | nextTo(A, B, list(_, A, B, _, _)). 178 | nextTo(A, B, list(_, _, A, B, _)). 179 | nextTo(A, B, list(_, _, _, A, B)). 180 | 181 | puzzle(Houses) :- 182 | exists(house(red, british, _, _, _), Houses), 183 | exists(house(_, swedish, _, _, dog), Houses), 184 | exists(house(green, _, coffee, _, _), Houses), 185 | exists(house(_, danish, tea, _, _), Houses), 186 | rightOf(house(white, _, _, _, _), house(green, _, _, _, _), Houses), 187 | exists(house(_, _, _, pall_mall, bird), Houses), 188 | exists(house(yellow, _, _, dunhill, _), Houses), 189 | middle(house(_, _, milk, _, _), Houses), 190 | first(house(_, norwegian, _, _, _), Houses), 191 | nextTo(house(_, _, _, blend, _), house(_, _, _, _, cat), Houses), 192 | nextTo(house(_, _, _, dunhill, _),house(_, _, _, _, horse), Houses), 193 | exists(house(_, _, beer, bluemaster, _), Houses), 194 | exists(house(_, german, _, prince, _), Houses), 195 | nextTo(house(_, norwegian, _, _, _), house(blue, _, _, _, _), Houses), 196 | nextTo(house(_, _, _, blend, _), house(_, _, water_, _, _), Houses). 197 | 198 | solution(FishOwner) :- 199 | puzzle(Houses), 200 | exists(house(_, FishOwner, _, _, fish), Houses). 201 | '''); 202 | test('Who Owns the Fish?', () { 203 | final query = Term.parse('solution(FishOwner)'); 204 | expect(db.query(query), [Term.parse('solution(german)')]); 205 | }); 206 | }); 207 | } 208 | -------------------------------------------------------------------------------- /test/tabular_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/reflection.dart'; 2 | import 'package:petitparser_examples/tabular.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | import 'utils/expect.dart'; 6 | 7 | void main() { 8 | group('csv', () { 9 | final csv = TabularDefinition.csv().build(); 10 | test('linter', () { 11 | expect(linter(csv, excludedTypes: {}), isEmpty); 12 | }); 13 | test('basic string', () { 14 | expect( 15 | csv, 16 | isSuccess( 17 | 'a', 18 | value: [ 19 | ['a'], 20 | ], 21 | ), 22 | ); 23 | expect( 24 | csv, 25 | isSuccess( 26 | 'ab', 27 | value: [ 28 | ['ab'], 29 | ], 30 | ), 31 | ); 32 | expect( 33 | csv, 34 | isSuccess( 35 | 'abc', 36 | value: [ 37 | ['abc'], 38 | ], 39 | ), 40 | ); 41 | }); 42 | test('quoted string', () { 43 | expect( 44 | csv, 45 | isSuccess( 46 | '""', 47 | value: [ 48 | [''], 49 | ], 50 | ), 51 | ); 52 | expect( 53 | csv, 54 | isSuccess( 55 | '"a"', 56 | value: [ 57 | ['a'], 58 | ], 59 | ), 60 | ); 61 | expect( 62 | csv, 63 | isSuccess( 64 | '"ab"', 65 | value: [ 66 | ['ab'], 67 | ], 68 | ), 69 | ); 70 | expect( 71 | csv, 72 | isSuccess( 73 | '"abc"', 74 | value: [ 75 | ['abc'], 76 | ], 77 | ), 78 | ); 79 | expect( 80 | csv, 81 | isSuccess( 82 | '""""', 83 | value: [ 84 | ['"'], 85 | ], 86 | ), 87 | ); 88 | }); 89 | test('fields', () { 90 | expect( 91 | csv, 92 | isSuccess( 93 | 'a', 94 | value: [ 95 | ['a'], 96 | ], 97 | ), 98 | ); 99 | expect( 100 | csv, 101 | isSuccess( 102 | 'a,b', 103 | value: [ 104 | ['a', 'b'], 105 | ], 106 | ), 107 | ); 108 | expect( 109 | csv, 110 | isSuccess( 111 | 'a,b,c', 112 | value: [ 113 | ['a', 'b', 'c'], 114 | ], 115 | ), 116 | ); 117 | }); 118 | test('fields (empty)', () { 119 | expect( 120 | csv, 121 | isSuccess( 122 | '', 123 | value: [ 124 | [''], 125 | ], 126 | ), 127 | ); 128 | expect( 129 | csv, 130 | isSuccess( 131 | ',', 132 | value: [ 133 | ['', ''], 134 | ], 135 | ), 136 | ); 137 | expect( 138 | csv, 139 | isSuccess( 140 | ',,', 141 | value: [ 142 | ['', '', ''], 143 | ], 144 | ), 145 | ); 146 | }); 147 | test('lines', () { 148 | expect( 149 | csv, 150 | isSuccess( 151 | 'a', 152 | value: [ 153 | ['a'], 154 | ], 155 | ), 156 | ); 157 | expect( 158 | csv, 159 | isSuccess( 160 | 'a\nb', 161 | value: [ 162 | ['a'], 163 | ['b'], 164 | ], 165 | ), 166 | ); 167 | expect( 168 | csv, 169 | isSuccess( 170 | 'a\nb\nc', 171 | value: [ 172 | ['a'], 173 | ['b'], 174 | ['c'], 175 | ], 176 | ), 177 | ); 178 | }); 179 | test('lines (emtpy)', () { 180 | expect( 181 | csv, 182 | isSuccess( 183 | '\n', 184 | value: [ 185 | [''], 186 | [''], 187 | ], 188 | ), 189 | ); 190 | expect( 191 | csv, 192 | isSuccess( 193 | '\n\n', 194 | value: [ 195 | [''], 196 | [''], 197 | [''], 198 | ], 199 | ), 200 | ); 201 | }); 202 | }); 203 | group('tsv', () { 204 | final tsv = TabularDefinition.tsv().build(); 205 | test('linter', () { 206 | expect(linter(tsv, excludedTypes: {}), isEmpty); 207 | }); 208 | test('basic string', () { 209 | expect( 210 | tsv, 211 | isSuccess( 212 | 'a', 213 | value: [ 214 | ['a'], 215 | ], 216 | ), 217 | ); 218 | expect( 219 | tsv, 220 | isSuccess( 221 | 'ab', 222 | value: [ 223 | ['ab'], 224 | ], 225 | ), 226 | ); 227 | expect( 228 | tsv, 229 | isSuccess( 230 | 'abc', 231 | value: [ 232 | ['abc'], 233 | ], 234 | ), 235 | ); 236 | }); 237 | test('escaped string', () { 238 | expect( 239 | tsv, 240 | isSuccess( 241 | r'\t', 242 | value: [ 243 | ['\t'], 244 | ], 245 | ), 246 | ); 247 | expect( 248 | tsv, 249 | isSuccess( 250 | r'\n', 251 | value: [ 252 | ['\n'], 253 | ], 254 | ), 255 | ); 256 | expect( 257 | tsv, 258 | isSuccess( 259 | r'\r', 260 | value: [ 261 | ['\r'], 262 | ], 263 | ), 264 | ); 265 | expect( 266 | tsv, 267 | isSuccess( 268 | r'\\', 269 | value: [ 270 | ['\\'], 271 | ], 272 | ), 273 | ); 274 | }); 275 | test('fields', () { 276 | expect( 277 | tsv, 278 | isSuccess( 279 | 'a', 280 | value: [ 281 | ['a'], 282 | ], 283 | ), 284 | ); 285 | expect( 286 | tsv, 287 | isSuccess( 288 | 'a\tb', 289 | value: [ 290 | ['a', 'b'], 291 | ], 292 | ), 293 | ); 294 | expect( 295 | tsv, 296 | isSuccess( 297 | 'a\tb\tc', 298 | value: [ 299 | ['a', 'b', 'c'], 300 | ], 301 | ), 302 | ); 303 | }); 304 | test('fields (empty)', () { 305 | expect( 306 | tsv, 307 | isSuccess( 308 | '', 309 | value: [ 310 | [''], 311 | ], 312 | ), 313 | ); 314 | expect( 315 | tsv, 316 | isSuccess( 317 | '\t', 318 | value: [ 319 | ['', ''], 320 | ], 321 | ), 322 | ); 323 | expect( 324 | tsv, 325 | isSuccess( 326 | '\t\t', 327 | value: [ 328 | ['', '', ''], 329 | ], 330 | ), 331 | ); 332 | }); 333 | test('lines', () { 334 | expect( 335 | tsv, 336 | isSuccess( 337 | 'a', 338 | value: [ 339 | ['a'], 340 | ], 341 | ), 342 | ); 343 | expect( 344 | tsv, 345 | isSuccess( 346 | 'a\nb', 347 | value: [ 348 | ['a'], 349 | ['b'], 350 | ], 351 | ), 352 | ); 353 | expect( 354 | tsv, 355 | isSuccess( 356 | 'a\nb\nc', 357 | value: [ 358 | ['a'], 359 | ['b'], 360 | ['c'], 361 | ], 362 | ), 363 | ); 364 | }); 365 | test('lines (emtpy)', () { 366 | expect( 367 | tsv, 368 | isSuccess( 369 | '\n', 370 | value: [ 371 | [''], 372 | [''], 373 | ], 374 | ), 375 | ); 376 | expect( 377 | tsv, 378 | isSuccess( 379 | '\n\n', 380 | value: [ 381 | [''], 382 | [''], 383 | [''], 384 | ], 385 | ), 386 | ); 387 | }); 388 | }); 389 | } 390 | -------------------------------------------------------------------------------- /lib/src/lisp/native.dart: -------------------------------------------------------------------------------- 1 | import 'cons.dart'; 2 | import 'environment.dart'; 3 | import 'evaluator.dart'; 4 | import 'name.dart'; 5 | import 'types.dart'; 6 | 7 | class NativeEnvironment extends Environment { 8 | NativeEnvironment([super.owner]) { 9 | // basic functions 10 | define(Name('define'), _define); 11 | define(Name('lambda'), _lambda); 12 | define(Name('quote'), _quote); 13 | define(Name('eval'), _eval); 14 | define(Name('apply'), _apply); 15 | define(Name('let'), _let); 16 | define(Name('set!'), _set); 17 | define(Name('print'), _print); 18 | 19 | // control structures 20 | define(Name('if'), _if); 21 | define(Name('while'), _while); 22 | define(Name('and'), _and); 23 | define(Name('or'), _or); 24 | define(Name('not'), _not); 25 | 26 | // arithmetic operators 27 | define(Name('+'), _plus); 28 | define(Name('-'), _minus); 29 | define(Name('*'), _multiply); 30 | define(Name('/'), _divide); 31 | define(Name('%'), _modulo); 32 | 33 | // arithmetic comparators 34 | define(Name('<'), _smaller); 35 | define(Name('<='), _smallerOrEqual); 36 | define(Name('='), _equal); 37 | define(Name('!='), _notEqual); 38 | define(Name('>'), _larger); 39 | define(Name('>='), _largerOrEqual); 40 | 41 | // list operators 42 | define(Name('cons'), _cons); 43 | define(Name('car'), _car); 44 | define(Name('car!'), _carSet); 45 | define(Name('cdr'), _cdr); 46 | define(Name('cdr!'), _cdrSet); 47 | } 48 | 49 | static dynamic _define(Environment env, dynamic args) { 50 | if (args.head is Name) { 51 | return env.define(args.head, evalList(env, args.tail)); 52 | } else if (args.head is Cons) { 53 | final Cons head = args.head; 54 | if (head.head is Name) { 55 | return env.define(head.head, _lambda(env, Cons(head.tail, args.tail))); 56 | } 57 | } 58 | throw ArgumentError('Invalid define: $args'); 59 | } 60 | 61 | static Lambda _lambda(Environment lambdaEnv, dynamic lambdaArgs) => 62 | (evalEnv, evalArgs) { 63 | final inner = lambdaEnv.create(); 64 | var names = lambdaArgs.head; 65 | var values = evalArguments(evalEnv, evalArgs); 66 | while (names != null && values != null) { 67 | inner.define(names.head, values.head); 68 | names = names.tail; 69 | values = values.tail; 70 | } 71 | return evalList(inner, lambdaArgs.tail); 72 | }; 73 | 74 | static dynamic _quote(Environment env, dynamic args) => args.head; 75 | 76 | static dynamic _eval(Environment env, dynamic args) => 77 | eval(env.create(), eval(env, args.head)); 78 | 79 | static dynamic _apply(Environment env, dynamic args) { 80 | final Function function = eval(env, args.head); 81 | return function(env.create(), args.tail); 82 | } 83 | 84 | static dynamic _let(Environment env, dynamic args) { 85 | final inner = env.create(); 86 | var binding = args.head; 87 | while (binding is Cons) { 88 | final definition = binding.head; 89 | if (definition is Cons) { 90 | inner.define(definition.head, eval(env, definition.tail?.head)); 91 | } else { 92 | throw ArgumentError('Invalid let: $args'); 93 | } 94 | binding = binding.tail; 95 | } 96 | return evalList(inner, args.tail); 97 | } 98 | 99 | static dynamic _set(Environment env, dynamic args) => 100 | env[args.head] = eval(env, args.tail.head); 101 | 102 | static dynamic _print(Environment env, dynamic args) { 103 | final buffer = StringBuffer(); 104 | while (args != null) { 105 | buffer.write(eval(env, args.head)); 106 | args = args.tail; 107 | } 108 | printer(buffer.toString()); 109 | return null; 110 | } 111 | 112 | static dynamic _if(Environment env, dynamic args) { 113 | final condition = eval(env, args.head); 114 | if (condition) { 115 | if (args.tail != null) { 116 | return eval(env, args.tail.head); 117 | } 118 | } else { 119 | if (args.tail != null && args.tail.tail != null) { 120 | return eval(env, args.tail.tail.head); 121 | } 122 | } 123 | return null; 124 | } 125 | 126 | static dynamic _while(Environment env, dynamic args) { 127 | dynamic result; 128 | while (eval(env, args.head)) { 129 | result = evalList(env, args.tail); 130 | } 131 | return result; 132 | } 133 | 134 | static dynamic _and(Environment env, dynamic args) { 135 | while (args != null) { 136 | if (!eval(env, args.head)) { 137 | return false; 138 | } 139 | args = args.tail; 140 | } 141 | return true; 142 | } 143 | 144 | static dynamic _or(Environment env, dynamic args) { 145 | while (args != null) { 146 | if (eval(env, args.head)) { 147 | return true; 148 | } 149 | args = args.tail; 150 | } 151 | return false; 152 | } 153 | 154 | static dynamic _not(Environment env, dynamic args) => !eval(env, args.head); 155 | 156 | static dynamic _plus(Environment env, dynamic args) { 157 | num value = eval(env, args.head); 158 | for (args = args.tail; args != null; args = args.tail) { 159 | value += eval(env, args.head); 160 | } 161 | return value; 162 | } 163 | 164 | static dynamic _minus(Environment env, dynamic args) { 165 | num value = eval(env, args.head); 166 | if (args.tail == null) { 167 | return -value; 168 | } 169 | for (args = args.tail; args != null; args = args.tail) { 170 | value -= eval(env, args.head); 171 | } 172 | return value; 173 | } 174 | 175 | static dynamic _multiply(Environment env, dynamic args) { 176 | num value = eval(env, args.head); 177 | for (args = args.tail; args != null; args = args.tail) { 178 | value *= eval(env, args.head); 179 | } 180 | return value; 181 | } 182 | 183 | static dynamic _divide(Environment env, dynamic args) { 184 | num value = eval(env, args.head); 185 | for (args = args.tail; args != null; args = args.tail) { 186 | value /= eval(env, args.head); 187 | } 188 | return value; 189 | } 190 | 191 | static dynamic _modulo(Environment env, dynamic args) { 192 | num value = eval(env, args.head); 193 | for (args = args.tail; args != null; args = args.tail) { 194 | value %= eval(env, args.head); 195 | } 196 | return value; 197 | } 198 | 199 | static dynamic _smaller(Environment env, dynamic args) { 200 | final Comparable a = eval(env, args.head); 201 | final Comparable b = eval(env, args.tail.head); 202 | return a.compareTo(b) < 0; 203 | } 204 | 205 | static dynamic _smallerOrEqual(Environment env, dynamic args) { 206 | final Comparable a = eval(env, args.head); 207 | final Comparable b = eval(env, args.tail.head); 208 | return a.compareTo(b) <= 0; 209 | } 210 | 211 | static dynamic _equal(Environment env, dynamic args) { 212 | final a = eval(env, args.head); 213 | final b = eval(env, args.tail.head); 214 | return a == b; 215 | } 216 | 217 | static dynamic _notEqual(Environment env, dynamic args) { 218 | final a = eval(env, args.head); 219 | final b = eval(env, args.tail.head); 220 | return a != b; 221 | } 222 | 223 | static dynamic _larger(Environment env, dynamic args) { 224 | final Comparable a = eval(env, args.head); 225 | final Comparable b = eval(env, args.tail.head); 226 | return a.compareTo(b) > 0; 227 | } 228 | 229 | static dynamic _largerOrEqual(Environment env, dynamic args) { 230 | final Comparable a = eval(env, args.head); 231 | final Comparable b = eval(env, args.tail.head); 232 | return a.compareTo(b) >= 0; 233 | } 234 | 235 | static dynamic _cons(Environment env, dynamic args) { 236 | final head = eval(env, args.head); 237 | final tail = eval(env, args.tail.head); 238 | return Cons(head, tail); 239 | } 240 | 241 | static dynamic _car(Environment env, dynamic args) { 242 | final cons = eval(env, args.head); 243 | return cons is Cons ? cons.head : null; 244 | } 245 | 246 | static dynamic _carSet(Environment env, dynamic args) { 247 | final cons = eval(env, args.head); 248 | if (cons is Cons) { 249 | cons.car = eval(env, args.tail.head); 250 | } 251 | return cons; 252 | } 253 | 254 | static dynamic _cdr(Environment env, dynamic args) { 255 | final cons = eval(env, args.head); 256 | return cons is Cons ? cons.cdr : null; 257 | } 258 | 259 | static dynamic _cdrSet(Environment env, dynamic args) { 260 | final cons = eval(env, args.head); 261 | if (cons is Cons) { 262 | cons.cdr = eval(env, args.tail.head); 263 | } 264 | return cons; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /lib/src/smalltalk/grammar.dart: -------------------------------------------------------------------------------- 1 | import 'package:petitparser/petitparser.dart'; 2 | 3 | /// Smalltalk grammar definition. 4 | class SmalltalkGrammarDefinition extends GrammarDefinition { 5 | @override 6 | Parser start() => ref0(startMethod); 7 | 8 | // the original implementation used a handwritten parser to 9 | // build special token objects 10 | Parser token(Object source, [String? message]) { 11 | if (source is String) { 12 | return source 13 | .toParser(message: 'Expected ${message ?? source}') 14 | .token() 15 | .trim(ref0(spacer)); 16 | } else if (source is Parser) { 17 | ArgumentError.checkNotNull(message, 'message'); 18 | return source 19 | .flatten(message: 'Expected $message') 20 | .token() 21 | .trim(ref0(spacer)); 22 | } else { 23 | throw ArgumentError('Unknown token type: $source.'); 24 | } 25 | } 26 | 27 | // the original implementation uses a handwritten parser to 28 | // efficiently consume whitespace and comments 29 | Parser spacer() => whitespace().or(ref0(comment)); 30 | Parser comment() => char('"').seq(char('"').neg().star()).seq(char('"')); 31 | 32 | // the original implementation uses the hand written number 33 | // parser of the system, this is the spec of the ANSI standard 34 | Parser number() => char('-').optional().seq(ref0(positiveNumber)); 35 | Parser positiveNumber() => 36 | ref0(scaledDecimal).or(ref0(float)).or(ref0(integer)); 37 | 38 | Parser integer() => ref0(radixInteger).or(ref0(decimalInteger)); 39 | Parser decimalInteger() => ref0(digits); 40 | Parser digits() => digit().plus(); 41 | Parser radixInteger() => 42 | ref0(radixSpecifier).seq(char('r')).seq(ref0(radixDigits)); 43 | Parser radixSpecifier() => ref0(digits); 44 | Parser radixDigits() => pattern('0-9A-Z').plus(); 45 | 46 | Parser float() => 47 | ref0(mantissa).seq(ref0(exponentLetter).seq(ref0(exponent)).optional()); 48 | Parser mantissa() => ref0(digits).seq(char('.')).seq(ref0(digits)); 49 | Parser exponent() => char('-').seq(ref0(decimalInteger)); 50 | Parser exponentLetter() => pattern('edq'); 51 | 52 | Parser scaledDecimal() => ref0( 53 | scaledMantissa, 54 | ).seq(char('s')).seq(ref0(fractionalDigits).optional()); 55 | Parser scaledMantissa() => ref0(decimalInteger).or(ref0(mantissa)); 56 | Parser fractionalDigits() => ref0(decimalInteger); 57 | 58 | // the original smalltalk grammar 59 | Parser array() => ref1(token, '{') 60 | .seq( 61 | ref0(expression) 62 | .starSeparated(ref0(periodToken).plus()) 63 | .skip(after: ref0(periodToken).star()) 64 | .map((list) => list.elements), 65 | ) 66 | .seq(ref1(token, '}')); 67 | Parser arrayItem() => ref0(literal) 68 | .or(ref0(symbolLiteralArray)) 69 | .or(ref0(arrayLiteralArray)) 70 | .or(ref0(byteLiteralArray)); 71 | Parser arrayLiteral() => 72 | ref1(token, '#(').seq(ref0(arrayItem).star()).seq(ref1(token, ')')); 73 | Parser arrayLiteralArray() => 74 | ref1(token, '(').seq(ref0(arrayItem).star()).seq(ref1(token, ')')); 75 | Parser assignment() => ref0(variable).seq(ref0(assignmentToken)); 76 | Parser assignmentToken() => ref1(token, ':='); 77 | Parser binary() => anyOf(r'!%&*+,-/<=>?@\|~').plusString(); 78 | Parser binaryExpression() => 79 | ref0(unaryExpression).seq(ref0(binaryMessage).star()); 80 | Parser binaryMessage() => 81 | ref0(binaryToken).seq(ref0(unaryExpression)).map(buildBinary); 82 | Parser binaryMethod() => 83 | ref0(binaryToken).seq(ref0(variable)).map(buildBinary); 84 | Parser binaryPragma() => 85 | ref0(binaryToken).seq(ref0(arrayItem)).map(buildBinary); 86 | Parser binaryToken() => ref2(token, ref0(binary), 'binary selector'); 87 | Parser block() => ref1(token, '[') & ref0(blockBody) & ref1(token, ']'); 88 | Parser blockArgument() => ref1(token, ':').seq(ref0(variable)); 89 | Parser blockArguments() => 90 | ref0(blockArgumentsWith).or(ref0(blockArgumentsWithout)); 91 | Parser blockArgumentsWith() => ref0( 92 | blockArgument, 93 | ).plus().seq(ref1(token, '|').or(ref1(token, ']').and())); 94 | Parser blockArgumentsWithout() => epsilonWith([]); 95 | Parser blockBody() => ref0(blockArguments).seq(ref0(sequence)); 96 | Parser byteLiteral() => 97 | ref1(token, '#[').seq(ref0(numberLiteral).star()).seq(ref1(token, ']')); 98 | Parser byteLiteralArray() => 99 | ref1(token, '[').seq(ref0(numberLiteral).star()).seq(ref1(token, ']')); 100 | Parser cascadeExpression() => 101 | ref0(keywordExpression).seq(ref0(cascadeMessage).star()); 102 | Parser cascadeMessage() => ref1(token, ';').seq(ref0(message)); 103 | Parser character() => char('\$').seq(any()); 104 | Parser characterLiteral() => ref0(characterToken); 105 | Parser characterToken() => ref2(token, ref0(character), 'character'); 106 | Parser expression() => ref0(assignment).star().seq(ref0(cascadeExpression)); 107 | Parser expressionReturn() => ref1(token, '^').seq(ref0(expression)); 108 | Parser falseLiteral() => ref0(falseToken); 109 | Parser falseToken() => 110 | ref2(token, 'false'.toParser() & word().not(), 'false'); 111 | Parser identifier() => pattern('a-zA-Z_').seq(word().star()); 112 | Parser identifierToken() => ref2(token, ref0(identifier), 'identifier'); 113 | Parser keyword() => ref0(identifier).seq(char(':')); 114 | Parser keywordExpression() => 115 | ref0(binaryExpression).seq(ref0(keywordMessage).optionalWith([])); 116 | Parser keywordMessage() => 117 | ref0(keywordToken).seq(ref0(binaryExpression)).plus().map(buildKeyword); 118 | Parser keywordMethod() => 119 | ref0(keywordToken).seq(ref0(variable)).plus().map(buildKeyword); 120 | Parser keywordPragma() => 121 | ref0(keywordToken).seq(ref0(arrayItem)).plus().map(buildKeyword); 122 | Parser keywordToken() => ref2(token, ref0(keyword), 'keyword selector'); 123 | Parser literal() => ref0(numberLiteral) 124 | .or(ref0(stringLiteral)) 125 | .or(ref0(characterLiteral)) 126 | .or(ref0(arrayLiteral)) 127 | .or(ref0(byteLiteral)) 128 | .or(ref0(symbolLiteral)) 129 | .or(ref0(nilLiteral)) 130 | .or(ref0(trueLiteral)) 131 | .or(ref0(falseLiteral)); 132 | Parser message() => 133 | ref0(keywordMessage).or(ref0(binaryMessage)).or(ref0(unaryMessage)); 134 | Parser method() => ref0(methodDeclaration).seq(ref0(methodSequence)); 135 | Parser methodDeclaration() => 136 | ref0(keywordMethod).or(ref0(unaryMethod)).or(ref0(binaryMethod)); 137 | Parser methodSequence() => ref0(periodToken) 138 | .star() 139 | .seq(ref0(pragmas)) 140 | .seq(ref0(periodToken).star()) 141 | .seq(ref0(temporaries)) 142 | .seq(ref0(periodToken).star()) 143 | .seq(ref0(pragmas)) 144 | .seq(ref0(periodToken).star()) 145 | .seq(ref0(statements)); 146 | Parser multiword() => ref0(keyword).plus(); 147 | Parser nilLiteral() => ref0(nilToken); 148 | Parser nilToken() => ref2(token, 'nil'.toParser() & word().not(), 'nil'); 149 | Parser numberLiteral() => ref0(numberToken); 150 | Parser numberToken() => ref2(token, ref0(number), 'number'); 151 | Parser parens() => 152 | ref1(token, '(').seq(ref0(expression)).seq(ref1(token, ')')); 153 | Parser period() => char('.'); 154 | Parser periodToken() => ref2(token, ref0(period), 'period'); 155 | Parser pragma() => 156 | ref1(token, '<').seq(ref0(pragmaMessage)).seq(ref1(token, '>')); 157 | Parser pragmaMessage() => 158 | ref0(keywordPragma).or(ref0(unaryPragma)).or(ref0(binaryPragma)); 159 | Parser pragmas() => ref0(pragma).star(); 160 | Parser primary() => ref0( 161 | literal, 162 | ).or(ref0(variable)).or(ref0(block)).or(ref0(parens)).or(ref0(array)); 163 | Parser sequence() => 164 | ref0(temporaries).seq(ref0(periodToken).star()).seq(ref0(statements)); 165 | Parser startMethod() => ref0(method).end(); 166 | Parser statements() => ref0(expressionReturn) 167 | .or(ref0(expression)) 168 | .starSeparated(ref0(periodToken).plus()) 169 | .skip(after: ref0(periodToken).star()) 170 | .map((list) => list.elements); 171 | Parser _string() => 172 | char("'").seq(string("''").or(pattern("^'")).star()).seq(char("'")); 173 | Parser stringLiteral() => ref0(stringToken); 174 | Parser stringToken() => ref2(token, ref0(_string), 'string'); 175 | Parser symbol() => 176 | ref0(unary).or(ref0(binary)).or(ref0(multiword)).or(ref0(_string)); 177 | Parser symbolLiteral() => 178 | ref1(token, '#').plus().seq(ref2(token, ref0(symbol), 'symbol')); 179 | Parser symbolLiteralArray() => ref2(token, ref0(symbol), 'symbol'); 180 | Parser temporaries() => ref1( 181 | token, 182 | '|', 183 | ).seq(ref0(variable).star()).seq(ref1(token, '|')).optionalWith([]); 184 | Parser trueLiteral() => ref0(trueToken); 185 | Parser trueToken() => ref2(token, 'true'.toParser() & word().not(), 'true'); 186 | Parser unary() => ref0(identifier).seq(char(':').not()); 187 | Parser unaryExpression() => ref0(primary).seq(ref0(unaryMessage).star()); 188 | Parser unaryMessage() => ref0(unaryToken).map(buildUnary); 189 | Parser unaryMethod() => ref0(identifierToken).map(buildUnary); 190 | Parser unaryPragma() => ref0(identifierToken).map(buildUnary); 191 | Parser unaryToken() => ref2(token, ref0(unary), 'unary selector'); 192 | Parser variable() => ref0(identifierToken); 193 | } 194 | 195 | dynamic buildUnary(dynamic input) => [ 196 | [input], 197 | [], 198 | ]; 199 | dynamic buildBinary(dynamic input) => [ 200 | [input[0]], 201 | [input[1]], 202 | ]; 203 | dynamic buildKeyword(dynamic input) => [ 204 | input.map((each) => each[0]).toList(), 205 | input.map((each) => each[1]).toList(), 206 | ]; 207 | --------------------------------------------------------------------------------