├── analysis_options.yaml ├── .gitignore ├── test ├── run.sh ├── src │ └── parsers_model.dart └── parsers_test.dart ├── pubspec.yaml ├── CHANGELOG.md ├── example ├── full_arith.dart ├── simple_arith.dart ├── mini_lang.dart └── mini_ast.dart ├── LICENSE ├── README.md ├── tool └── gen_accumulators.dart └── lib ├── src └── accumulators.dart └── parsers.dart /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | example/packages 2 | packages/ 3 | pubspec.lock 4 | test/packages 5 | test/src/packages 6 | tool/packages 7 | doc/out 8 | doc/gen 9 | doc/tutorial/bin/packages 10 | .idea/ 11 | .packages 12 | -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" 4 | 5 | dartanalyzer $ROOT_DIR/lib/*.dart \ 6 | && dartanalyzer $ROOT_DIR/test/*.dart \ 7 | && dartanalyzer $ROOT_DIR/example/*.dart \ 8 | && dart --enable-checked-mode $ROOT_DIR/example/simple_arith.dart \ 9 | && dart --enable-checked-mode $ROOT_DIR/example/full_arith.dart \ 10 | && dart --enable-checked-mode $ROOT_DIR/test/parsers_test.dart 11 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: parsers 2 | version: 1.0.0 3 | author: Paul Brauner 4 | description: > 5 | Parser combinators for Dart. Parser combinators let you write complex parsers 6 | by combining smaller, less complex parsers. 7 | homepage: https://github.com/polux/parsers 8 | documentation: http://goo.gl/ZNqdEH 9 | environment: 10 | sdk: '>=0.8.10+6 <2.0.0' 11 | dependencies: 12 | persistent: '^1.0.0' 13 | dev_dependencies: 14 | unittest: '^0.11.0' 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Parsers Changelog 2 | 3 | ## 1.0.0 4 | 5 | - Make strong mode and dart 2 compatible. 6 | - Introduce non-infix generic methods for all the infix operators. 7 | 8 | ## 0.14.5 9 | 10 | - Lazy computation of the error messages (PR #18). 11 | 12 | ## 0.14.4 13 | 14 | - Support setting the value of a tabstop in position calculation (PR #16). 15 | 16 | ## 0.14.3 17 | 18 | - Move run_all_tests.sh to tests/run.sh to adhere to pub.drone.io's conventions. 19 | 20 | ## 0.14.2 21 | 22 | - Fix type annotation leading to failure in checked mode 23 | 24 | ## 0.14.1 25 | 26 | - Added `CHANGELOG.md` 27 | - Fix type annotation leading to failure in checked mode 28 | -------------------------------------------------------------------------------- /test/src/parsers_model.dart: -------------------------------------------------------------------------------- 1 | part of parsers_test; 2 | 3 | // Simple models of the most complex combinators (the one that use while loops 4 | // to avoid stack overflows). 5 | 6 | _cons(x) => (xs) => []..add(x)..addAll(xs); 7 | 8 | Parser manyModel(Parser p) { 9 | go () => success(_cons) * p * rec(go) | success([]); 10 | return go(); 11 | } 12 | 13 | Parser manyImpl(Parser p) => p.many; 14 | 15 | Parser skipManyModel(Parser p) => manyModel(p) > success(null); 16 | 17 | Parser skipManyImpl(Parser p) => p.skipMany; 18 | 19 | Parser manyUntilModel(Parser p, Parser end) { 20 | go () => (end > success([])) 21 | | success(_cons) * p * rec(go); 22 | return go(); 23 | } 24 | 25 | Parser manyUntilImpl(Parser p, Parser end) => p.manyUntil(end); 26 | 27 | Parser skipManyUntilModel(Parser p, Parser end) { 28 | return manyUntilModel(p, end) > success(null); 29 | } 30 | 31 | Parser skipManyUntilImpl(Parser p, Parser end) => p.skipManyUntil(end); 32 | 33 | Parser chainl1Model(Parser p, Parser sep) { 34 | rest(acc) { 35 | combine(f) => (x) => f(acc, x); 36 | return (success(combine) * sep * p) >> rest | success(acc); 37 | } 38 | return p >> rest; 39 | } 40 | 41 | Parser chainl1Impl(Parser p, Parser sep) => p.chainl1(sep); 42 | -------------------------------------------------------------------------------- /example/full_arith.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 2 | // is governed by a BSD-style license that can be found in the LICENSE file. 3 | 4 | // Author: Paul Brauner (polux@google.com) 5 | 6 | library example; 7 | 8 | import 'package:parsers/parsers.dart'; 9 | 10 | // Same example as example.dart, with the additional use of chainl1 which 11 | // helps handling infix operators with the same precedence. 12 | 13 | class Arith { 14 | 15 | digits2int(digits) => int.parse(digits.join()); 16 | 17 | lexeme(parser) => parser < spaces; 18 | token(str) => lexeme(string(str)); 19 | parens(parser) => parser.between(token('('), token(')')); 20 | 21 | get start => expr() < eof; 22 | 23 | get comma => token(','); 24 | get times => token('*'); 25 | get div => token('~/'); 26 | get plus => token('+'); 27 | get minus => token('-'); 28 | get number => lexeme(digit.many1) ^ digits2int; 29 | 30 | expr() => rec(term).chainl1(addop); 31 | term() => rec(atom).chainl1(mulop); 32 | atom() => number | parens(rec(expr)); 33 | 34 | get addop => (plus > success((x, y) => x + y)) 35 | | (minus > success((x, y) => x - y)); 36 | 37 | get mulop => (times > success((x, y) => x * y)) 38 | | (div > success((x, y) => x ~/ y)); 39 | } 40 | 41 | main() { 42 | final s = "1 * 2 ~/ 2 + 3 * (4 + 5 - 1)"; 43 | print(new Arith().start.parse(s)); // prints 25 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012, Google Inc. All rights reserved. Redistribution and 2 | use in source and binary forms, with or without modification, are 3 | permitted provided that the following conditions are met: 4 | * Redistributions of source code must retain the above copyright 5 | notice, this list of conditions and the following disclaimer. 6 | * Redistributions in binary form must reproduce the above 7 | copyright notice, this list of conditions and the following 8 | disclaimer in the documentation and/or other materials provided 9 | with the distribution. 10 | * Neither the name of Google Inc. nor the names of its 11 | contributors may be used to endorse or promote products derived 12 | from this software without specific prior written permission. 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 15 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 16 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 17 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 18 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parser Combinators for Dart 2 | 3 | Check out the [user guide](http://polux.github.io/parsers/) 4 | (work in progress) and the [API reference](http://polux.github.io/parsers/dartdoc/parsers.html). 5 | 6 | ## Quick Start 7 | 8 | ```dart 9 | import 'package:parsers/parsers.dart'; 10 | import 'dart:math'; 11 | 12 | // grammar 13 | 14 | final number = digit.many1 ^ digits2int 15 | | string('none') ^ none 16 | | string('answer') ^ answer; 17 | 18 | final comma = char(',') < spaces; 19 | 20 | final numbers = number.sepBy(comma) < eof; 21 | 22 | // actions 23 | 24 | digits2int(digits) => parseInt(Strings.concatAll(digits)); 25 | none(_) => null; 26 | answer(_) => 42; 27 | 28 | // parsing 29 | 30 | main() { 31 | print(numbers.parse('0,1, none, 3,answer')); 32 | // [0, 1, null, 3, 42] 33 | 34 | print(numbers.parse('0,1, boom, 3,answer')); 35 | // line 1, character 6: expected digit, 'none' or 'answer', got 'b'. 36 | } 37 | ``` 38 | 39 | See the 40 | [example](https://github.com/polux/parsers/tree/master/example) 41 | directory for advanced usage. 42 | 43 | ## About 44 | 45 | This library is heavily inspired by 46 | [Parsec](http://hackage.haskell.org/package/parsec), but differs on some 47 | points. In particular, the `|` operator has a sane backtracking semantics, as 48 | in [Polyparse](http://code.haskell.org/~malcolm/polyparse/docs/). As a 49 | consequence it is slower but also easier to use. I've also introduced some 50 | syntax for transforming parsing results that doesn't require any knowledge of 51 | monads or applicative functors and features uncurried functions, which are 52 | nicer-looking than curried ones in Dart. 53 | -------------------------------------------------------------------------------- /example/simple_arith.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 2 | // is governed by a BSD-style license that can be found in the LICENSE file. 3 | 4 | // Author: Paul Brauner (polux@google.com) 5 | 6 | library simple_arith; 7 | 8 | import 'package:parsers/parsers.dart'; 9 | 10 | class Arith { 11 | 12 | // Some combinators: functions that take parsers and return a parser. 13 | 14 | lexeme(parser) => parser < spaces; 15 | token(str) => lexeme(string(str)); 16 | parens(parser) => token('(') + parser + token(')') ^ (a,b,c) => b; 17 | 18 | // The axiom of the grammar is an expression followed by end of file. 19 | 20 | get start => expr() < eof; 21 | 22 | // We define some lexemes. 23 | 24 | get comma => token(','); 25 | get times => token('*'); 26 | get plus => token('+'); 27 | get number => (lexeme(digit.many1) ^ digits2int) % 'natural number'; 28 | 29 | // This is the gist of the grammar, the BNF-like rules. 30 | 31 | expr() => rec(mult).sepBy1(plus) ^ sum; 32 | mult() => rec(atom).sepBy1(times) ^ prod; 33 | atom() => number 34 | | parens(rec(expr)); 35 | 36 | // These are simple Dart functions used as "actions" above to transform the 37 | // results of intermediate parsing steps. 38 | 39 | digits2int(digits) => int.parse(digits.join()); 40 | prod(xs) => xs.fold(1, (a,b) => a * b); 41 | sum(xs) => xs.fold(0, (a,b) => a + b); 42 | } 43 | 44 | main() { 45 | final good = "1 * 2 + 3 * (4 + 5)"; 46 | print(new Arith().start.parse(good)); // prints 29 47 | 48 | final bad = "1 * x + 2"; 49 | try { 50 | new Arith().start.parse(bad); 51 | } catch(e) { 52 | print('parsing of 1 * x + 2 failed as expected: "$e"'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tool/gen_accumulators.dart: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dart 2 | 3 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 4 | // is governed by a BSD-style license that can be found in the LICENSE file. 5 | 6 | // Author: Paul Brauner (polux@google.com) 7 | 8 | main(List arguments) { 9 | final n = int.parse(arguments[0]); 10 | for (int i = 2; i <= n; i++) { 11 | final plist = []; 12 | final pdecllist = []; 13 | final xlist = []; 14 | final tlist = []; 15 | final typedxlist = []; 16 | for (int j = 1; j <= i; j++) { 17 | pdecllist.add(' final Parser p$j;'); 18 | plist.add('p$j'); 19 | xlist.add('x$j'); 20 | tlist.add('T$j'); 21 | typedxlist.add('T$j x$j'); 22 | } 23 | final newt = 'T${i+1}'; 24 | final newtlist = new List.from(tlist)..add(newt); 25 | final ts = tlist.join(', '); 26 | final newts = newtlist.join(', '); 27 | final ps = plist.join(', '); 28 | final pdecls = pdecllist.join('\n'); 29 | final these = plist.map((p) => 'this.$p').join(', '); 30 | final xs = xlist.join(', '); 31 | final typedxs = typedxlist.join(' , '); 32 | final curriedXs = typedxlist.map((x) => '($x)').join(' => '); 33 | final psProduct = plist.map((p) => '.apply($p)').join(''); 34 | 35 | print(''' 36 | class ParserAccumulator${i}<${ts}> { 37 | $pdecls 38 | ParserAccumulator$i($these); 39 | '''); 40 | 41 | if (i < n) { 42 | print(''' 43 | /// Parser sequencing: creates a parser accumulator 44 | ParserAccumulator${i+1}<${newts}> and<${newt}>(Parser<${newt}> p) => 45 | new ParserAccumulator${i+1}($ps, p); 46 | 47 | /// Alias for [and] 48 | ParserAccumulator${i+1} operator +(Parser p) => and(p); 49 | '''); 50 | } 51 | 52 | print(''' 53 | /// Action application 54 | Parser map(R f($typedxs)) => 55 | success($curriedXs => f($xs))$psProduct; 56 | 57 | /// Alias for map 58 | Parser operator ^(Object f($typedxs)) => map(f); 59 | 60 | /// Creates a [:Parser:] from [this]. 61 | Parser get list => 62 | success($curriedXs => [$xs])$psProduct; 63 | } 64 | '''); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/mini_lang.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 2 | // is governed by a BSD-style license that can be found in the LICENSE file. 3 | 4 | // Author: Paul Brauner (polux@google.com) 5 | 6 | library misc; 7 | 8 | import 'package:parsers/parsers.dart'; 9 | 10 | // We extend LanguageParsers to benefit from all the C-like language-specific 11 | // comment-aware, reserved names-aware, literals combinators. 12 | 13 | class MiniLang extends LanguageParsers { 14 | MiniLang() : super(reservedNames: ['var', 'if', 'else', 'true', 'false']); 15 | 16 | get start => stmts().between(spaces, eof); 17 | 18 | stmts() => stmt().endBy(semi); 19 | stmt() => declStmt() 20 | | assignStmt() 21 | | ifStmt(); 22 | // In a real parser, we would build AST nodes, but here we turn sequences 23 | // into lists via the list getter for simplicity. 24 | declStmt() => (reserved['var'] + identifier + symbol('=') + expr()).list; 25 | assignStmt() => (identifier + symbol('=') + expr()).list; 26 | ifStmt() => (reserved['if'] 27 | + parens(expr()) 28 | + braces(rec(stmts)) 29 | + reserved['else'] 30 | + braces(rec(stmts))).list; 31 | 32 | expr() => disj().sepBy1(symbol('||')); 33 | disj() => comp().sepBy1(symbol('&&')); 34 | comp() => arith().sepBy1(symbol('<') | symbol('>')); 35 | arith() => term().sepBy1(symbol('+') | symbol('-')); 36 | term() => atom().withPosition.sepBy1(symbol('*') | symbol('/')); 37 | 38 | atom() => floatLiteral 39 | | intLiteral 40 | | stringLiteral 41 | | reserved['true'] 42 | | reserved['false'] 43 | | identifier 44 | | parens(rec(expr)); 45 | } 46 | 47 | final test = """ 48 | var i = 14; // "vari = 14" is a parse error 49 | var j = 2.3e4; // using var instead of j is a parse error 50 | /* 51 | multi-line comments are 52 | supported and tunable 53 | */ 54 | if (i < j + 2 * 3 || true) { 55 | i = "foo\t"; 56 | } else { 57 | j = false; 58 | }; // we need a semicolon here because of endBy 59 | """; 60 | 61 | main() { 62 | print(new MiniLang().start.parse(test)); 63 | } 64 | -------------------------------------------------------------------------------- /example/mini_ast.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 2 | // is governed by a BSD-style license that can be found in the LICENSE file. 3 | 4 | // Authors: 5 | // Adam Singer (financecoding@gmail.com) 6 | // Paul Brauner (polux@google.com) 7 | 8 | library mini_ast; 9 | 10 | import 'package:parsers/parsers.dart'; 11 | 12 | final reservedNames = ["namespace", "interface", "dictionary", "void"]; 13 | 14 | class NamespaceDeclaration { 15 | final String name; 16 | final List body; 17 | final List doc; 18 | 19 | NamespaceDeclaration(this.name, this.body, [this.doc]); 20 | 21 | String toString() => "NamespaceDeclaration($name, $body, $doc)"; 22 | } 23 | 24 | class InterfaceDeclaration { 25 | final String name; 26 | final List body; 27 | final List doc; 28 | 29 | InterfaceDeclaration(this.name, this.body, [this.doc]); 30 | 31 | String toString() => "InterfaceDeclaration($name, $body, $doc)"; 32 | } 33 | 34 | class DictionaryDeclaration { 35 | final String name; 36 | final List body; 37 | final List doc; 38 | 39 | DictionaryDeclaration(this.name, this.body, [this.doc]); 40 | 41 | String toString() => "DictionaryDeclaration($name, $body, $doc)"; 42 | } 43 | 44 | class TypeAppl { 45 | final String name; 46 | final List arguments; 47 | 48 | TypeAppl(this.name, this.arguments); 49 | 50 | String toString() => "TypeAppl($name, $arguments)"; 51 | } 52 | 53 | class Parameter { 54 | final String name; 55 | final TypeAppl type; 56 | 57 | Parameter(this.type, this.name); 58 | 59 | String toString() => "Parameter($type, $name)"; 60 | } 61 | 62 | class MethodDeclaration { 63 | TypeAppl returnType; 64 | String name; 65 | List parameters; 66 | List doc; 67 | 68 | MethodDeclaration(this.returnType, this.name, this.parameters, [this.doc]); 69 | 70 | String toString() => 71 | "MethodDeclaration($returnType, $name, $parameters, $doc)"; 72 | } 73 | 74 | class FieldDeclaration { 75 | TypeAppl type; 76 | String name; 77 | List doc; 78 | 79 | FieldDeclaration(this.type, this.name, [this.doc]); 80 | 81 | String toString() => "FieldDeclaration($type, $name, $doc)"; 82 | } 83 | 84 | NamespaceDeclaration namespaceDeclarationMapping(doc, _, name, body, __) => 85 | new NamespaceDeclaration(name, body, doc); 86 | 87 | InterfaceDeclaration interfaceDeclarationMapping(doc, _, name, body, __) => 88 | new InterfaceDeclaration(name, body, doc); 89 | 90 | MethodDeclaration methodDeclarationRegularMapping( 91 | doc, returnType, name, parameters, _) => 92 | new MethodDeclaration(returnType, name, parameters, doc); 93 | 94 | MethodDeclaration methodDeclarationReservedMapping( 95 | doc, returnType, name, parameters, _) => 96 | new MethodDeclaration(new TypeAppl(returnType, null), name, parameters, doc); 97 | 98 | DictionaryDeclaration dictionaryDeclarationMapping( 99 | doc, _, name, body, __) => 100 | new DictionaryDeclaration(name, body, doc); 101 | 102 | FieldDeclaration fieldDeclarationMapping(doc, type, name, _) => 103 | new FieldDeclaration(type, name, doc); 104 | 105 | class DataCoreParser extends LanguageParsers { 106 | 107 | DataCoreParser() : super(reservedNames: reservedNames, 108 | // tells LanguageParsers to not handle comments 109 | commentStart: "", 110 | commentEnd: "", 111 | commentLine: ""); 112 | 113 | Parser get docString => lexeme(_docString).many; 114 | 115 | Parser get _docString => 116 | everythingBetween(string('//'), string('\n')) 117 | | everythingBetween(string('/*'), string('*/')) 118 | | everythingBetween(string('/**'), string('*/')); 119 | 120 | Parser get namespaceDeclaration => 121 | docString 122 | + reserved["namespace"] 123 | + identifier 124 | + braces(namespaceBody) 125 | + semi 126 | ^ namespaceDeclarationMapping; 127 | 128 | Parser get namespaceBody => body.many; 129 | 130 | Parser get body => interfaceDeclaration | dictionaryDeclaration; 131 | 132 | Parser get interfaceDeclaration => 133 | docString 134 | + reserved["interface"] 135 | + identifier 136 | + braces(interfaceBody) 137 | + semi 138 | ^ interfaceDeclarationMapping; 139 | 140 | Parser get interfaceBody => method.many; 141 | 142 | Parser get method => regularMethod | voidMethod; 143 | 144 | Parser typeAppl() => 145 | identifier 146 | + angles(rec(typeAppl).sepBy(comma)).orElse([]) 147 | ^ (c, args) => new TypeAppl(c, args); 148 | 149 | Parser get parameter => 150 | (typeAppl() % 'type') 151 | + (identifier % 'parameter') 152 | ^ (t, p) => new Parameter(t, p); 153 | 154 | Parser get regularMethod => 155 | docString 156 | + typeAppl() 157 | + identifier 158 | + parens(parameter.sepBy(comma)) 159 | + semi 160 | ^ methodDeclarationRegularMapping; 161 | 162 | Parser get voidMethod => 163 | docString 164 | + reserved['void'] 165 | + identifier 166 | + parens(parameter.sepBy(comma)) 167 | + semi 168 | ^ methodDeclarationReservedMapping; 169 | 170 | Parser get dictionaryDeclaration => 171 | docString 172 | + reserved["dictionary"] 173 | + identifier 174 | + braces(dictionaryBody) 175 | + semi 176 | ^ dictionaryDeclarationMapping; 177 | 178 | Parser get dictionaryBody => field.many; 179 | 180 | Parser get field => 181 | docString 182 | + typeAppl() 183 | + identifier 184 | + semi 185 | ^ fieldDeclarationMapping; 186 | } 187 | 188 | final test = """ 189 | // Data core processor package 190 | // Second comment line 191 | 192 | namespace datacore { 193 | // Defined interface of the processor 194 | interface DataProc { 195 | // Loads data for the processor, 196 | // pass the size of the loaded data 197 | bool loadData(array data, int size); 198 | 199 | // Executes the processor 200 | void run(); 201 | 202 | /* Returns the result of the processor */ 203 | DataProcResult result(); 204 | }; 205 | 206 | /** 207 | * A data type for the processor result 208 | * Multi line comment 209 | * With information. 210 | */ 211 | dictionary DataProcResult { 212 | // Time spent processing 213 | double timeSpent; 214 | 215 | // Value calculated from processing 216 | int value; 217 | }; 218 | }; 219 | """; 220 | 221 | void main() { 222 | DataCoreParser dataCoreParser = new DataCoreParser(); 223 | NamespaceDeclaration namespaceDeclaration = 224 | dataCoreParser.namespaceDeclaration.between(spaces, eof).parse(test); 225 | print(namespaceDeclaration); 226 | } 227 | -------------------------------------------------------------------------------- /lib/src/accumulators.dart: -------------------------------------------------------------------------------- 1 | part of parsers; 2 | 3 | class ParserAccumulator2 { 4 | final Parser p1; 5 | final Parser p2; 6 | ParserAccumulator2(this.p1, this.p2); 7 | 8 | /// Parser sequencing: creates a parser accumulator 9 | ParserAccumulator3 and(Parser p) => 10 | new ParserAccumulator3(p1, p2, p); 11 | 12 | /// Alias for [and] 13 | ParserAccumulator3 operator +(Parser p) => and(p); 14 | 15 | /// Action application 16 | Parser map(R f(T1 x1, T2 x2)) => 17 | success((T1 x1) => (T2 x2) => f(x1, x2)).apply(p1).apply(p2); 18 | 19 | /// Alias for map 20 | Parser operator ^(Object f(T1 x1, T2 x2)) => map(f); 21 | 22 | /// Creates a [:Parser:] from [this]. 23 | Parser get list => 24 | success((T1 x1) => (T2 x2) => [x1, x2]).apply(p1).apply(p2); 25 | } 26 | 27 | class ParserAccumulator3 { 28 | final Parser p1; 29 | final Parser p2; 30 | final Parser p3; 31 | ParserAccumulator3(this.p1, this.p2, this.p3); 32 | 33 | /// Parser sequencing: creates a parser accumulator 34 | ParserAccumulator4 and(Parser p) => 35 | new ParserAccumulator4(p1, p2, p3, p); 36 | 37 | /// Alias for [and] 38 | ParserAccumulator4 operator +(Parser p) => and(p); 39 | 40 | /// Action application 41 | Parser map(R f(T1 x1, T2 x2, T3 x3)) => 42 | success((T1 x1) => (T2 x2) => (T3 x3) => f(x1, x2, x3)) 43 | .apply(p1) 44 | .apply(p2) 45 | .apply(p3); 46 | 47 | /// Alias for map 48 | Parser operator ^(Object f(T1 x1, T2 x2, T3 x3)) => map(f); 49 | 50 | /// Creates a [:Parser:] from [this]. 51 | Parser get list => 52 | success((T1 x1) => (T2 x2) => (T3 x3) => [x1, x2, x3]) 53 | .apply(p1) 54 | .apply(p2) 55 | .apply(p3); 56 | } 57 | 58 | class ParserAccumulator4 { 59 | final Parser p1; 60 | final Parser p2; 61 | final Parser p3; 62 | final Parser p4; 63 | ParserAccumulator4(this.p1, this.p2, this.p3, this.p4); 64 | 65 | /// Parser sequencing: creates a parser accumulator 66 | ParserAccumulator5 and(Parser p) => 67 | new ParserAccumulator5(p1, p2, p3, p4, p); 68 | 69 | /// Alias for [and] 70 | ParserAccumulator5 operator +(Parser p) => and(p); 71 | 72 | /// Action application 73 | Parser map(R f(T1 x1, T2 x2, T3 x3, T4 x4)) => 74 | success((T1 x1) => (T2 x2) => (T3 x3) => (T4 x4) => f(x1, x2, x3, x4)) 75 | .apply(p1) 76 | .apply(p2) 77 | .apply(p3) 78 | .apply(p4); 79 | 80 | /// Alias for map 81 | Parser operator ^(Object f(T1 x1, T2 x2, T3 x3, T4 x4)) => map(f); 82 | 83 | /// Creates a [:Parser:] from [this]. 84 | Parser get list => 85 | success((T1 x1) => (T2 x2) => (T3 x3) => (T4 x4) => [x1, x2, x3, x4]) 86 | .apply(p1) 87 | .apply(p2) 88 | .apply(p3) 89 | .apply(p4); 90 | } 91 | 92 | class ParserAccumulator5 { 93 | final Parser p1; 94 | final Parser p2; 95 | final Parser p3; 96 | final Parser p4; 97 | final Parser p5; 98 | ParserAccumulator5(this.p1, this.p2, this.p3, this.p4, this.p5); 99 | 100 | /// Parser sequencing: creates a parser accumulator 101 | ParserAccumulator6 and(Parser p) => 102 | new ParserAccumulator6(p1, p2, p3, p4, p5, p); 103 | 104 | /// Alias for [and] 105 | ParserAccumulator6 operator +(Parser p) => and(p); 106 | 107 | /// Action application 108 | Parser map(R f(T1 x1, T2 x2, T3 x3, T4 x4, T5 x5)) => success((T1 x1) => 109 | (T2 x2) => (T3 x3) => (T4 x4) => (T5 x5) => f(x1, x2, x3, x4, x5)) 110 | .apply(p1) 111 | .apply(p2) 112 | .apply(p3) 113 | .apply(p4) 114 | .apply(p5); 115 | 116 | /// Alias for map 117 | Parser operator ^(Object f(T1 x1, T2 x2, T3 x3, T4 x4, T5 x5)) => map(f); 118 | 119 | /// Creates a [:Parser:] from [this]. 120 | Parser get list => success((T1 x1) => 121 | (T2 x2) => (T3 x3) => (T4 x4) => (T5 x5) => [x1, x2, x3, x4, x5]) 122 | .apply(p1) 123 | .apply(p2) 124 | .apply(p3) 125 | .apply(p4) 126 | .apply(p5); 127 | } 128 | 129 | class ParserAccumulator6 { 130 | final Parser p1; 131 | final Parser p2; 132 | final Parser p3; 133 | final Parser p4; 134 | final Parser p5; 135 | final Parser p6; 136 | ParserAccumulator6(this.p1, this.p2, this.p3, this.p4, this.p5, this.p6); 137 | 138 | /// Parser sequencing: creates a parser accumulator 139 | ParserAccumulator7 and(Parser p) => 140 | new ParserAccumulator7(p1, p2, p3, p4, p5, p6, p); 141 | 142 | /// Alias for [and] 143 | ParserAccumulator7 operator +(Parser p) => and(p); 144 | 145 | /// Action application 146 | Parser map(R f(T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6)) => 147 | success((T1 x1) => (T2 x2) => (T3 x3) => 148 | (T4 x4) => (T5 x5) => (T6 x6) => f(x1, x2, x3, x4, x5, x6)) 149 | .apply(p1) 150 | .apply(p2) 151 | .apply(p3) 152 | .apply(p4) 153 | .apply(p5) 154 | .apply(p6); 155 | 156 | /// Alias for map 157 | Parser operator ^(Object f(T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6)) => 158 | map(f); 159 | 160 | /// Creates a [:Parser:] from [this]. 161 | Parser get list => success((T1 x1) => (T2 x2) => 162 | (T3 x3) => (T4 x4) => (T5 x5) => (T6 x6) => [x1, x2, x3, x4, x5, x6]) 163 | .apply(p1) 164 | .apply(p2) 165 | .apply(p3) 166 | .apply(p4) 167 | .apply(p5) 168 | .apply(p6); 169 | } 170 | 171 | class ParserAccumulator7 { 172 | final Parser p1; 173 | final Parser p2; 174 | final Parser p3; 175 | final Parser p4; 176 | final Parser p5; 177 | final Parser p6; 178 | final Parser p7; 179 | ParserAccumulator7( 180 | this.p1, this.p2, this.p3, this.p4, this.p5, this.p6, this.p7); 181 | 182 | /// Parser sequencing: creates a parser accumulator 183 | ParserAccumulator8 and(Parser p) => 184 | new ParserAccumulator8(p1, p2, p3, p4, p5, p6, p7, p); 185 | 186 | /// Alias for [and] 187 | ParserAccumulator8 operator +(Parser p) => and(p); 188 | 189 | /// Action application 190 | Parser map(R f(T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6, T7 x7)) => 191 | success((T1 x1) => (T2 x2) => (T3 x3) => (T4 x4) => 192 | (T5 x5) => (T6 x6) => (T7 x7) => f(x1, x2, x3, x4, x5, x6, x7)) 193 | .apply(p1) 194 | .apply(p2) 195 | .apply(p3) 196 | .apply(p4) 197 | .apply(p5) 198 | .apply(p6) 199 | .apply(p7); 200 | 201 | /// Alias for map 202 | Parser operator ^( 203 | Object f(T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6, T7 x7)) => 204 | map(f); 205 | 206 | /// Creates a [:Parser:] from [this]. 207 | Parser get list => success((T1 x1) => (T2 x2) => (T3 x3) => (T4 x4) => 208 | (T5 x5) => (T6 x6) => (T7 x7) => [x1, x2, x3, x4, x5, x6, x7]) 209 | .apply(p1) 210 | .apply(p2) 211 | .apply(p3) 212 | .apply(p4) 213 | .apply(p5) 214 | .apply(p6) 215 | .apply(p7); 216 | } 217 | 218 | class ParserAccumulator8 { 219 | final Parser p1; 220 | final Parser p2; 221 | final Parser p3; 222 | final Parser p4; 223 | final Parser p5; 224 | final Parser p6; 225 | final Parser p7; 226 | final Parser p8; 227 | ParserAccumulator8( 228 | this.p1, this.p2, this.p3, this.p4, this.p5, this.p6, this.p7, this.p8); 229 | 230 | /// Parser sequencing: creates a parser accumulator 231 | ParserAccumulator9 and( 232 | Parser p) => 233 | new ParserAccumulator9(p1, p2, p3, p4, p5, p6, p7, p8, p); 234 | 235 | /// Alias for [and] 236 | ParserAccumulator9 operator +(Parser p) => and(p); 237 | 238 | /// Action application 239 | Parser map( 240 | R f(T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6, T7 x7, T8 x8)) => 241 | success((T1 x1) => (T2 x2) => (T3 x3) => (T4 x4) => (T5 x5) => (T6 x6) => 242 | (T7 x7) => (T8 x8) => f(x1, x2, x3, x4, x5, x6, x7, x8)) 243 | .apply(p1) 244 | .apply(p2) 245 | .apply(p3) 246 | .apply(p4) 247 | .apply(p5) 248 | .apply(p6) 249 | .apply(p7) 250 | .apply(p8); 251 | 252 | /// Alias for map 253 | Parser operator ^( 254 | Object f(T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6, T7 x7, T8 x8)) => 255 | map(f); 256 | 257 | /// Creates a [:Parser:] from [this]. 258 | Parser get list => 259 | success((T1 x1) => (T2 x2) => (T3 x3) => (T4 x4) => (T5 x5) => 260 | (T6 x6) => (T7 x7) => (T8 x8) => [x1, x2, x3, x4, x5, x6, x7, x8]) 261 | .apply(p1) 262 | .apply(p2) 263 | .apply(p3) 264 | .apply(p4) 265 | .apply(p5) 266 | .apply(p6) 267 | .apply(p7) 268 | .apply(p8); 269 | } 270 | 271 | class ParserAccumulator9 { 272 | final Parser p1; 273 | final Parser p2; 274 | final Parser p3; 275 | final Parser p4; 276 | final Parser p5; 277 | final Parser p6; 278 | final Parser p7; 279 | final Parser p8; 280 | final Parser p9; 281 | ParserAccumulator9(this.p1, this.p2, this.p3, this.p4, this.p5, this.p6, 282 | this.p7, this.p8, this.p9); 283 | 284 | /// Parser sequencing: creates a parser accumulator 285 | ParserAccumulator10 and( 286 | Parser p) => 287 | new ParserAccumulator10(p1, p2, p3, p4, p5, p6, p7, p8, p9, p); 288 | 289 | /// Alias for [and] 290 | ParserAccumulator10 operator +(Parser p) => and(p); 291 | 292 | /// Action application 293 | Parser map( 294 | R f(T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6, T7 x7, T8 x8, T9 x9)) => 295 | success((T1 x1) => (T2 x2) => (T3 x3) => (T4 x4) => (T5 x5) => (T6 x6) => 296 | (T7 x7) => 297 | (T8 x8) => (T9 x9) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9)) 298 | .apply(p1) 299 | .apply(p2) 300 | .apply(p3) 301 | .apply(p4) 302 | .apply(p5) 303 | .apply(p6) 304 | .apply(p7) 305 | .apply(p8) 306 | .apply(p9); 307 | 308 | /// Alias for map 309 | Parser operator ^( 310 | Object f( 311 | T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6, T7 x7, T8 x8, T9 x9)) => 312 | map(f); 313 | 314 | /// Creates a [:Parser:] from [this]. 315 | Parser get list => success((T1 x1) => (T2 x2) => (T3 x3) => (T4 x4) => 316 | (T5 x5) => (T6 x6) => (T7 x7) => 317 | (T8 x8) => (T9 x9) => [x1, x2, x3, x4, x5, x6, x7, x8, x9]) 318 | .apply(p1) 319 | .apply(p2) 320 | .apply(p3) 321 | .apply(p4) 322 | .apply(p5) 323 | .apply(p6) 324 | .apply(p7) 325 | .apply(p8) 326 | .apply(p9); 327 | } 328 | 329 | class ParserAccumulator10 { 330 | final Parser p1; 331 | final Parser p2; 332 | final Parser p3; 333 | final Parser p4; 334 | final Parser p5; 335 | final Parser p6; 336 | final Parser p7; 337 | final Parser p8; 338 | final Parser p9; 339 | final Parser p10; 340 | ParserAccumulator10(this.p1, this.p2, this.p3, this.p4, this.p5, this.p6, 341 | this.p7, this.p8, this.p9, this.p10); 342 | 343 | /// Action application 344 | Parser map( 345 | R f(T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6, T7 x7, T8 x8, T9 x9, 346 | T10 x10)) => 347 | success((T1 x1) => (T2 x2) => (T3 x3) => (T4 x4) => (T5 x5) => (T6 x6) => 348 | (T7 x7) => (T8 x8) => (T9 x9) => 349 | (T10 x10) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)) 350 | .apply(p1) 351 | .apply(p2) 352 | .apply(p3) 353 | .apply(p4) 354 | .apply(p5) 355 | .apply(p6) 356 | .apply(p7) 357 | .apply(p8) 358 | .apply(p9) 359 | .apply(p10); 360 | 361 | /// Alias for map 362 | Parser operator ^( 363 | Object f(T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6, T7 x7, T8 x8, 364 | T9 x9, T10 x10)) => 365 | map(f); 366 | 367 | /// Creates a [:Parser:] from [this]. 368 | Parser get list => success((T1 x1) => (T2 x2) => (T3 x3) => (T4 x4) => 369 | (T5 x5) => (T6 x6) => (T7 x7) => (T8 x8) => 370 | (T9 x9) => (T10 x10) => [x1, x2, x3, x4, x5, x6, x7, x8, x9, x10]) 371 | .apply(p1) 372 | .apply(p2) 373 | .apply(p3) 374 | .apply(p4) 375 | .apply(p5) 376 | .apply(p6) 377 | .apply(p7) 378 | .apply(p8) 379 | .apply(p9) 380 | .apply(p10); 381 | } 382 | -------------------------------------------------------------------------------- /lib/parsers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 2 | // is governed by a BSD-style license that can be found in the LICENSE file. 3 | 4 | // Authors: 5 | // Paul Brauner (polux@google.com) 6 | // Maxim Dikun (me@dikmax.name) 7 | 8 | library parsers; 9 | 10 | import 'package:persistent/persistent.dart'; 11 | 12 | part 'src/accumulators.dart'; 13 | 14 | class Undefined { 15 | const Undefined(); 16 | } // simulates the old ? operator 17 | 18 | class Position { 19 | final int line; 20 | final int character; 21 | final int offset; 22 | final int tabStop; 23 | 24 | const Position(this.offset, this.line, this.character, {this.tabStop: 1}); 25 | 26 | Position addChar(String c) { 27 | assert(c.length == 1); 28 | if (c == '\n') { 29 | return new Position(offset + 1, line + 1, 1, tabStop: tabStop); 30 | } 31 | if (c == '\t') { 32 | int used = (character - 1) % tabStop; 33 | return new Position(offset + 1, line, character + (tabStop - used), 34 | tabStop: tabStop); 35 | } 36 | return new Position(offset + 1, line, character + 1, tabStop: tabStop); 37 | } 38 | 39 | Position copy({int offset, int line, int character, int tabStop}) => 40 | new Position( 41 | offset == null ? this.offset : offset, 42 | line == null ? this.line : line, 43 | character == null ? this.character : character, 44 | tabStop: tabStop == null ? this.tabStop : tabStop); 45 | 46 | bool operator <(Position p) => offset < p.offset; 47 | 48 | bool operator >(Position p) => offset > p.offset; 49 | 50 | String toString() => '(line $line, char $character, offset $offset)'; 51 | } 52 | 53 | /** 54 | * The value computed by a parser along with the position at which it was 55 | * parsed. 56 | */ 57 | class PointedValue { 58 | final A value; 59 | final Position position; 60 | 61 | PointedValue(this.value, this.position); 62 | 63 | String toString() => '$value @ $position'; 64 | } 65 | 66 | abstract class Expectations { 67 | const Expectations(); 68 | 69 | factory Expectations.empty(Position position) => 70 | new EmptyExpectation(position); 71 | factory Expectations.single(String str, Position position) => 72 | new SingleExpectation(str, position); 73 | 74 | CombinedExpectation best(Expectations other) => 75 | new CombinedExpectation(this, other); 76 | 77 | Position get position; 78 | Set get expected; 79 | } 80 | 81 | class EmptyExpectation extends Expectations { 82 | final Position _position; 83 | 84 | const EmptyExpectation(this._position); 85 | 86 | Position get position => _position; 87 | Set get expected => new Set(); 88 | } 89 | 90 | class SingleExpectation extends Expectations { 91 | final String _expected; 92 | final Position _position; 93 | 94 | SingleExpectation(this._expected, this._position); 95 | 96 | Position get position => _position; 97 | Set get expected => new Set.from([_expected]); 98 | } 99 | 100 | class CombinedExpectation extends Expectations { 101 | final Expectations first; 102 | final Expectations second; 103 | 104 | CombinedExpectation(this.first, this.second); 105 | 106 | Position get position { 107 | if (first.position < second.position) return second.position; 108 | return first.position; 109 | } 110 | 111 | Set get expected => first.expected..addAll(second.expected); 112 | } 113 | 114 | class ParseResult { 115 | final bool isSuccess; 116 | final bool isCommitted; 117 | 118 | /// [:null:] if [:!isSuccess:] 119 | final A value; 120 | final String text; 121 | final Position position; 122 | final Expectations expectations; 123 | 124 | ParseResult(this.text, this.expectations, this.position, this.isSuccess, 125 | this.isCommitted, this.value); 126 | 127 | factory ParseResult.success(A value, String text, Position position, 128 | [Expectations expectations, bool committed = false]) { 129 | final Expectations exps = (expectations != null) 130 | ? expectations 131 | : new Expectations.empty(position); 132 | return new ParseResult(text, exps, position, true, committed, value); 133 | } 134 | 135 | factory ParseResult.failure(String text, Position position, 136 | [Expectations expectations, bool committed = false]) { 137 | final Expectations exps = (expectations != null) 138 | ? expectations 139 | : new Expectations.empty(position); 140 | return new ParseResult(text, exps, position, false, committed, null); 141 | } 142 | 143 | ParseResult map(B f(A value)) { 144 | return copy(value: f(value)); 145 | } 146 | 147 | ParseResult copy( 148 | {String text, 149 | Expectations expectations, 150 | Position position, 151 | bool isSuccess, 152 | bool isCommitted, 153 | Object value: const Undefined()}) { 154 | return new ParseResult( 155 | (text != null) ? text : this.text, 156 | (expectations != null) ? expectations : this.expectations, 157 | (position != null) ? position : this.position, 158 | (isSuccess != null) ? isSuccess : this.isSuccess, 159 | (isCommitted != null) ? isCommitted : this.isCommitted, 160 | (value != const Undefined()) ? value : this.value); 161 | } 162 | 163 | String get errorMessage { 164 | final pos = expectations.position; 165 | final maxSeenChar = 166 | (pos.offset < text.length) ? "'${text[pos.offset]}'" : 'eof'; 167 | final prelude = 'line ${pos.line}, character ${pos.character}:'; 168 | final expected = expectations.expected; 169 | if (expected.isEmpty) { 170 | return '$prelude unexpected $maxSeenChar.'; 171 | } else { 172 | final or = _humanOr(expected.toList()); 173 | return "$prelude expected $or, got $maxSeenChar."; 174 | } 175 | } 176 | 177 | String get _rest => text.substring(position.offset); 178 | 179 | get _shortRest => _rest.length < 10 ? _rest : '${_rest.substring(0, 10)}...'; 180 | 181 | toString() { 182 | final c = isCommitted ? '*' : ''; 183 | return isSuccess 184 | ? 'success$c: {value: $value, rest: "$_shortRest"}' 185 | : 'failure$c: {message: $errorMessage, rest: "$_shortRest"}'; 186 | } 187 | 188 | static String _humanOr(List es) { 189 | assert(es.length > 0); 190 | if (es.length == 1) { 191 | return es[0]; 192 | } else { 193 | StringBuffer result = new StringBuffer(); 194 | for (int i = 0; i < es.length - 2; i++) { 195 | result.write('${es[i]}, '); 196 | } 197 | result.write('${es[es.length - 2]} or ${es[es.length - 1]}'); 198 | return result.toString(); 199 | } 200 | } 201 | } 202 | 203 | typedef ParseResult _ParseFunction(String s, Position pos); 204 | 205 | class Parser { 206 | final _ParseFunction _run; 207 | 208 | Parser(ParseResult f(String s, Position pos)) : this._run = f; 209 | 210 | ParseResult run(String s, [Position pos = const Position(0, 1, 1)]) => 211 | _run(s, pos); 212 | 213 | A parse(String s, {int tabStop: 1}) { 214 | ParseResult result = run(s, new Position(0, 1, 1, tabStop: tabStop)); 215 | if (result.isSuccess) 216 | return result.value; 217 | else 218 | throw result.errorMessage; 219 | } 220 | 221 | Parser then(Parser g(A value)) { 222 | return new Parser((text, pos) { 223 | ParseResult res = _run(text, pos); 224 | if (res.isSuccess) { 225 | final res2 = g(res.value)._run(text, res.position); 226 | return res2.copy( 227 | expectations: res.expectations.best(res2.expectations), 228 | isCommitted: res.isCommitted || res2.isCommitted); 229 | } else { 230 | return res; 231 | } 232 | }); 233 | } 234 | 235 | /// Alias for [then]. 236 | Parser operator >>(Parser g(A x)) => then(g); 237 | 238 | Parser expecting(String expected) { 239 | return new Parser((s, pos) { 240 | final res = _run(s, pos); 241 | return res.copy(expectations: new Expectations.single(expected, pos)); 242 | }); 243 | } 244 | 245 | /// Alias for [expecting]. 246 | Parser operator %(String expected) => this.expecting(expected); 247 | 248 | Parser get committed { 249 | return new Parser((s, pos) { 250 | final res = _run(s, pos); 251 | return res.copy(isCommitted: true); 252 | }); 253 | } 254 | 255 | // Assumes that [this] parses a function (i.e., A = B -> C) and applies it 256 | // to the result of [p]. 257 | Parser apply(Parser p) => 258 | then((f) => p.then((x) => success((f as Function)(x)))); 259 | 260 | /// Alias for [apply]. 261 | Parser operator *(Parser p) => apply(p); 262 | 263 | /// Parses [this] then [p] and returns the result of [p]. 264 | Parser thenKeep(Parser p) => then((_) => p); 265 | 266 | /// Alias for [thenKeep]. 267 | Parser operator >(Parser p) => thenKeep(p); 268 | 269 | /// Parses [this] then [p] and returns the result of [this]. 270 | Parser thenDrop(Parser p) => 271 | this.then((x) => p.thenKeep(success(x))); 272 | 273 | /// Alias for [thenDrop]; 274 | Parser operator <(Parser p) => thenDrop(p); 275 | 276 | /// Maps [f] over the result of [this]. 277 | Parser map(B f(A x)) => success(f) * this; 278 | 279 | /// Alias for [map]. 280 | Parser operator ^(Object f(A x)) => map(f); 281 | 282 | /// Parser sequencing: creates a parser accumulator. 283 | ParserAccumulator2 and(Parser p) => 284 | new ParserAccumulator2(this, p); 285 | 286 | /// Alias for [and]. 287 | ParserAccumulator2 operator +(Parser p) => new ParserAccumulator2(this, p); 288 | 289 | /// Alternative. 290 | Parser or(Parser p) { 291 | return new Parser((s, pos) { 292 | ParseResult res = _run(s, pos); 293 | if (res.isSuccess || res.isCommitted) { 294 | return res; 295 | } else { 296 | ParseResult res2 = p._run(s, pos); 297 | return res2.copy( 298 | expectations: res.expectations.best(res2.expectations)); 299 | } 300 | }); 301 | } 302 | 303 | /// Alias for [or]. 304 | Parser operator |(Parser p) => or(p); 305 | 306 | /** 307 | * Parses without consuming any input. 308 | * 309 | * Used for defining followedBy, which is probably what you're looking for. 310 | */ 311 | Parser get lookAhead { 312 | return new Parser((s, pos) { 313 | ParseResult res = _run(s, pos); 314 | return res.isSuccess ? new ParseResult.success(res.value, s, pos) : res; 315 | }); 316 | } 317 | 318 | /** 319 | * Succeeds if and only if [this] succeeds and [p] succeeds on what remains to 320 | * parse without consuming it. 321 | * 322 | * string("let").followedBy(space) 323 | */ 324 | Parser followedBy(Parser p) => thenDrop(p.lookAhead); 325 | 326 | /** 327 | * Fails if and only if [this] succeeds on what's ahead. 328 | * 329 | * Used for defining notFollowedBy, which is probably what you're looking for. 330 | */ 331 | Parser get notAhead { 332 | return new Parser((s, pos) { 333 | ParseResult res = _run(s, pos); 334 | return res.isSuccess 335 | ? new ParseResult.failure(s, pos) 336 | : new ParseResult.success(null, s, pos); 337 | }); 338 | } 339 | 340 | /** 341 | * Succeeds if and only if [this] succeeds and [p] fails on what remains to 342 | * parse. 343 | * 344 | * string("let").notFollowedBy(alphanum) 345 | */ 346 | Parser notFollowedBy(Parser p) => thenDrop(p.notAhead); 347 | 348 | /** 349 | * Parses [this] 0 or more times until [end] succeeds. 350 | * 351 | * Returns the list of values returned by [this]. It is useful for parsing 352 | * comments. 353 | * 354 | * string('/*') > anyChar.manyUntil(string('*/')) 355 | * 356 | * The input parsed by [end] is consumed. Use [:end.lookAhead:] if you don't 357 | * want this. 358 | */ 359 | Parser> manyUntil(Parser end) { 360 | // Imperative version to avoid stack overflows. 361 | return new Parser((s, pos) { 362 | List res = []; 363 | Position index = pos; 364 | var exps = new Expectations.empty(pos); 365 | bool committed = false; 366 | while (true) { 367 | final endRes = end._run(s, index); 368 | exps = exps.best(endRes.expectations); 369 | if (endRes.isSuccess) { 370 | return endRes.copy( 371 | value: res, expectations: exps, isCommitted: committed); 372 | } else if (!endRes.isCommitted) { 373 | final xRes = this._run(s, index); 374 | exps = exps.best(xRes.expectations); 375 | committed = committed || xRes.isCommitted; 376 | if (xRes.isSuccess) { 377 | res.add(xRes.value); 378 | index = xRes.position; 379 | } else { 380 | return xRes.copy(expectations: exps, isCommitted: committed); 381 | } 382 | } else { 383 | return endRes.copy(expectations: exps, isCommitted: committed); 384 | } 385 | } 386 | }); 387 | } 388 | 389 | /** 390 | * Parses [this] 0 or more times until [end] succeeds and discards the result. 391 | * 392 | * Equivalent to [this.manyUntil(end) > success(null)] but faster. The input 393 | * parsed by [end] is consumed. Use [:end.lookAhead:] if you don't want this. 394 | */ 395 | Parser skipManyUntil(Parser end) { 396 | // Imperative version to avoid stack overflows. 397 | return new Parser((s, pos) { 398 | Position index = pos; 399 | var exps = new Expectations.empty(pos); 400 | var commit = false; 401 | while (true) { 402 | final endRes = end._run(s, index); 403 | exps = exps.best(endRes.expectations); 404 | commit = commit || endRes.isCommitted; 405 | if (endRes.isSuccess) { 406 | return endRes.copy( 407 | value: null, expectations: exps, isCommitted: commit); 408 | } else if (!endRes.isCommitted) { 409 | final xRes = this._run(s, index); 410 | exps = exps.best(xRes.expectations); 411 | commit = commit || xRes.isCommitted; 412 | if (xRes.isSuccess) { 413 | index = xRes.position; 414 | } else { 415 | return xRes.copy(expectations: exps, isCommitted: commit); 416 | } 417 | } else { 418 | return endRes.copy(expectations: exps); 419 | } 420 | } 421 | }); 422 | } 423 | 424 | // Derived combinators, defined here for infix notation 425 | 426 | Parser orElse(A value) => or(success(value)); 427 | 428 | Parser> get maybe => 429 | this.map((x) => new Option.some(x)).orElse(new Option.none()); 430 | 431 | // Imperative version to avoid stack overflows. 432 | Parser> _many(List acc()) { 433 | return new Parser((s, pos) { 434 | final res = acc(); 435 | var exps = new Expectations.empty(pos); 436 | Position index = pos; 437 | bool committed = false; 438 | while (true) { 439 | ParseResult o = this._run(s, index); 440 | exps = exps.best(o.expectations); 441 | committed = committed || o.isCommitted; 442 | if (o.isSuccess) { 443 | res.add(o.value); 444 | index = o.position; 445 | } else if (o.isCommitted) { 446 | return o.copy(expectations: exps); 447 | } else { 448 | return new ParseResult.success(res, s, index, exps, committed); 449 | } 450 | } 451 | }); 452 | } 453 | 454 | Parser> get many => _many(() => []); 455 | 456 | Parser> get many1 => then((A x) => _many(() => [x])); 457 | 458 | /** 459 | * Parses [this] zero or more time, skipping its result. 460 | * 461 | * Equivalent to [this.many > success(null)] but more efficient. 462 | */ 463 | Parser get skipMany { 464 | // Imperative version to avoid stack overflows. 465 | return new Parser((s, pos) { 466 | Position index = pos; 467 | var exps = new Expectations.empty(pos); 468 | bool committed = false; 469 | while (true) { 470 | ParseResult o = this._run(s, index); 471 | exps = exps.best(o.expectations); 472 | committed = committed || o.isCommitted; 473 | if (o.isSuccess) { 474 | index = o.position; 475 | } else if (o.isCommitted) { 476 | return o.copy(expectations: exps); 477 | } else { 478 | return new ParseResult.success(null, s, index, exps, committed); 479 | } 480 | } 481 | }); 482 | } 483 | 484 | /** 485 | * Parses [this] one or more time, skipping its result. 486 | * 487 | * Equivalent to [this.many1 > success(null)] but more efficient. 488 | */ 489 | Parser get skipMany1 => thenKeep(this.skipMany); 490 | 491 | Parser> sepBy(Parser sep) => sepBy1(sep).orElse([]); 492 | 493 | Parser> sepBy1(Parser sep) => 494 | then((x) => (sep.thenKeep(this))._many(() => [x])); 495 | 496 | Parser> endBy(Parser sep) => thenDrop(sep).many; 497 | 498 | Parser> endBy1(Parser sep) => thenDrop(sep).many1; 499 | 500 | /** 501 | * Parses zero or more occurences of [this] separated and optionally ended 502 | * by [sep]. 503 | */ 504 | Parser> sepEndBy(Parser sep) => sepEndBy1(sep).orElse([]); 505 | 506 | /** 507 | * Parses one or more occurences of [this] separated and optionally ended 508 | * by [sep]. 509 | */ 510 | Parser> sepEndBy1(Parser sep) => 511 | sepBy1(sep).thenDrop(sep.maybe); 512 | 513 | Parser chainl(Parser sep, A defaultValue) => 514 | chainl1(sep).or(success(defaultValue)); 515 | 516 | Parser chainl1(Parser sep) { 517 | Parser rest(A acc) { 518 | return new Parser((s, pos) { 519 | Position index = pos; 520 | var exps = new Expectations.empty(pos); 521 | var commit = false; 522 | while (true) { 523 | combine(Function f) => (A x) => f(acc, x); 524 | final res = success(combine).apply(sep).apply(this)._run(s, index); 525 | exps = exps.best(res.expectations); 526 | commit = commit || res.isCommitted; 527 | if (res.isSuccess) { 528 | acc = res.value; 529 | index = res.position; 530 | } else if (res.isCommitted) { 531 | return res.copy(expectations: exps); 532 | } else { 533 | return new ParseResult.success(acc, s, index, exps, commit); 534 | } 535 | } 536 | }); 537 | } 538 | 539 | return then(rest); 540 | } 541 | 542 | /// Warning: may lead to stack overflows. 543 | Parser chainr(Parser sep, A defaultValue) => 544 | chainr1(sep).or(success(defaultValue)); 545 | 546 | /// Warning: may lead to stack overflows. 547 | Parser chainr1(Parser sep) { 548 | Parser rest(A x) => success((Function f) => (A y) => f(x, y)) 549 | .apply(sep) 550 | .apply(chainr1(sep)) 551 | .or(success(x)); 552 | return then(rest); 553 | } 554 | 555 | Parser between(Parser left, Parser right) => 556 | left.thenKeep(this.thenDrop(right)); 557 | 558 | /// Returns the substring consumed by [this]. 559 | Parser get record { 560 | return new Parser((s, pos) { 561 | final result = run(s, pos); 562 | if (result.isSuccess) { 563 | return result.copy( 564 | value: s.substring(pos.offset, result.position.offset)); 565 | } else { 566 | return result.map((_) => null); 567 | } 568 | }); 569 | } 570 | 571 | /** 572 | * Returns the value parsed by [this] along with the position at which it 573 | * has been parsed. 574 | */ 575 | Parser> get withPosition { 576 | return new Parser((s, pos) { 577 | return this.map((v) => new PointedValue(v, pos))._run(s, pos); 578 | }); 579 | } 580 | } 581 | 582 | // Primitive parsers 583 | 584 | final Parser fail = new Parser((s, pos) => new ParseResult.failure(s, pos)); 585 | 586 | Parser success(A value) => 587 | new Parser((s, pos) => new ParseResult.success(value, s, pos)); 588 | 589 | final Parser eof = new Parser((s, pos) => pos.offset >= s.length 590 | ? new ParseResult.success(null, s, pos) 591 | : new ParseResult.failure(s, pos, new Expectations.single("eof", pos))); 592 | 593 | Parser pred(bool p(String char)) { 594 | return new Parser((s, pos) { 595 | if (pos.offset >= s.length) 596 | return new ParseResult.failure(s, pos); 597 | else { 598 | String c = s[pos.offset]; 599 | return p(c) 600 | ? new ParseResult.success(c, s, pos.addChar(c)) 601 | : new ParseResult.failure(s, pos); 602 | } 603 | }); 604 | } 605 | 606 | Parser char(String chr) => pred((String c) => c == chr) % "'$chr'"; 607 | 608 | Parser string(String str) { 609 | // Primitive version for efficiency 610 | return new Parser((s, pos) { 611 | final int offset = pos.offset; 612 | final int max = offset + str.length; 613 | 614 | int newline = pos.line; 615 | int newchar = pos.character; 616 | // This replicates Position#addChar for efficiency purposes. 617 | void update(c) { 618 | final isNewLine = c == '\n'; 619 | newline = newline + (isNewLine ? 1 : 0); 620 | newchar = isNewLine ? 1 : newchar + 1; 621 | } 622 | 623 | bool match = s.length >= max; 624 | for (int i = 0; i < str.length && match; i++) { 625 | final c = s[offset + i]; 626 | match = match && c == str[i]; 627 | update(c); 628 | } 629 | if (match) { 630 | return new ParseResult.success( 631 | str, s, pos.copy(offset: max, line: newline, character: newchar)); 632 | } else { 633 | return new ParseResult.failure( 634 | s, pos, new Expectations.single("'$str'", pos)); 635 | } 636 | }); 637 | } 638 | 639 | Parser rec(Parser f()) => new Parser((s, pos) => f()._run(s, pos)); 640 | 641 | final Parser position = 642 | new Parser((s, pos) => new ParseResult.success(pos, s, pos)); 643 | 644 | // Derived combinators 645 | 646 | Parser choice(List> ps) { 647 | // Imperative version for efficiency 648 | return new Parser((s, pos) { 649 | var exps = new Expectations.empty(pos); 650 | for (final p in ps) { 651 | final res = p._run(s, pos); 652 | exps = exps.best(res.expectations); 653 | if (res.isSuccess) { 654 | return res.copy(expectations: exps); 655 | } else if (res.isCommitted) { 656 | return res; 657 | } 658 | } 659 | return new ParseResult.failure(s, pos, exps); 660 | }); 661 | } 662 | 663 | class _SkipInBetween { 664 | final Parser left; 665 | final Parser right; 666 | final bool nested; 667 | 668 | _SkipInBetween(this.left, this.right, this.nested); 669 | 670 | Parser parser() => nested ? _insideMulti() : _insideSingle(); 671 | 672 | Parser _inside() => rec(parser).between(left, right); 673 | Parser get _leftOrRightAhead => (left | right).lookAhead; 674 | Parser _insideMulti() => anyChar.skipManyUntil(_leftOrRightAhead) > _nest(); 675 | Parser _nest() => (rec(_inside) > rec(_insideMulti)).maybe; 676 | Parser _insideSingle() => anyChar.skipManyUntil(right.lookAhead); 677 | } 678 | 679 | Parser skipEverythingBetween(Parser left, Parser right, 680 | {bool nested: false}) { 681 | final inBetween = new _SkipInBetween(left, right, nested).parser(); 682 | return inBetween.between(left, right) > success(null); 683 | } 684 | 685 | Parser everythingBetween(Parser left, Parser right, 686 | {bool nested: false}) { 687 | final inBetween = new _SkipInBetween(left, right, nested).parser(); 688 | return inBetween.record.between(left, right); 689 | } 690 | 691 | // Derived character parsers 692 | 693 | final Parser anyChar = pred((c) => true) % 'any character'; 694 | 695 | Parser oneOf(String chars) => 696 | pred((c) => chars.contains(c)).expecting("one of '$chars'"); 697 | 698 | Parser noneOf(String chars) => 699 | pred((c) => !chars.contains(c)).expecting("none of '$chars'"); 700 | 701 | final _spaces = " \t\n\r\v\f"; 702 | final _lower = "abcdefghijklmnopqrstuvwxyz"; 703 | final _upper = _lower.toUpperCase(); 704 | final _alpha = "$_lower$_upper"; 705 | final _digit = "1234567890"; 706 | final _alphanum = "$_alpha$_digit"; 707 | 708 | final Parser tab = char('\t') % 'tab'; 709 | 710 | final Parser newline = char('\n') % 'newline'; 711 | 712 | final Parser space = oneOf(_spaces) % 'space'; 713 | 714 | final Parser spaces = (space.many > success(null)) % 'spaces'; 715 | 716 | final Parser upper = oneOf(_upper) % 'uppercase letter'; 717 | 718 | final Parser lower = oneOf(_lower) % 'lowercase letter'; 719 | 720 | final Parser alphanum = oneOf(_alphanum); // % 'alphanumeric character' 721 | 722 | final Parser letter = oneOf(_alpha) % 'letter'; 723 | 724 | final Parser digit = oneOf(_digit) % 'digit'; 725 | 726 | class ReservedNames { 727 | Map> _map; 728 | ReservedNames._(this._map); 729 | Parser operator [](String key) { 730 | final res = _map[key]; 731 | if (res == null) 732 | throw "$key is not a reserved name"; 733 | else 734 | return res; 735 | } 736 | } 737 | 738 | /// Programming language specific combinators 739 | class LanguageParsers { 740 | String _commentStart; 741 | String _commentEnd; 742 | String _commentLine; 743 | bool _nestedComments; 744 | Parser _identStart; 745 | Parser _identLetter; 746 | Set _reservedNames; 747 | 748 | ReservedNames _reserved; 749 | 750 | LanguageParsers( 751 | {String commentStart: '/*', 752 | String commentEnd: '*/', 753 | String commentLine: '//', 754 | bool nestedComments: false, 755 | Parser identStart: null, // letter | char('_') 756 | Parser identLetter: null, // alphanum | char('_') 757 | List reservedNames: const []}) { 758 | final identStartDefault = letter | char('_'); 759 | final identLetterDefault = alphanum | char('_'); 760 | 761 | _commentStart = commentStart; 762 | _commentEnd = commentEnd; 763 | _commentLine = commentLine; 764 | _nestedComments = nestedComments; 765 | _identStart = (identStart == null) ? identStartDefault : identStart; 766 | _identLetter = (identLetter == null) ? identLetterDefault : identLetter; 767 | _reservedNames = new Set.from(reservedNames); 768 | } 769 | 770 | Parser get semi => symbol(';') % 'semicolon'; 771 | Parser get comma => symbol(',') % 'comma'; 772 | Parser get colon => symbol(':') % 'colon'; 773 | Parser get dot => symbol('.') % 'dot'; 774 | 775 | Parser get _ident => 776 | success((String c) => (List cs) => "$c${cs.join()}") 777 | .apply(_identStart) 778 | .apply(_identLetter.many); 779 | 780 | Parser get identifier => 781 | lexeme(_ident.then( 782 | (name) => _reservedNames.contains(name) ? fail : success(name))) % 783 | 'identifier'; 784 | 785 | ReservedNames get reserved { 786 | if (_reserved == null) { 787 | final map = new Map>(); 788 | for (final name in _reservedNames) { 789 | map[name] = lexeme(string(name).notFollowedBy(_identLetter)); 790 | } 791 | _reserved = new ReservedNames._(map); 792 | } 793 | return _reserved; 794 | } 795 | 796 | final Parser _escapeCode = (char('a') > success('\a')) | 797 | (char('b') > success('\b')) | 798 | (char('f') > success('\f')) | 799 | (char('n') > success('\n')) | 800 | (char('r') > success('\r')) | 801 | (char('t') > success('\t')) | 802 | (char('v') > success('\v')) | 803 | (char('\\') > success('\\')) | 804 | (char('"') > success('"')) | 805 | (char("'") > success("'")); 806 | 807 | Parser get _charChar => 808 | (char('\\') > _escapeCode) | pred((c) => c != "'"); 809 | 810 | Parser get charLiteral => 811 | lexeme(_charChar.between(char("'"), char("'"))) % 'character literal'; 812 | 813 | Parser get _stringChar => 814 | (char('\\') > _escapeCode) | pred((c) => c != '"'); 815 | 816 | Parser get stringLiteral => 817 | lexeme(_stringChar.many.between(char('"'), char('"'))) 818 | .map((cs) => cs.join()) % 819 | 'string literal'; 820 | 821 | final Parser _hexDigit = oneOf("0123456789abcdefABCDEF"); 822 | 823 | final Parser _octalDigit = oneOf("01234567"); 824 | 825 | Parser get _maybeSign => (char('-') | char('+')).orElse(''); 826 | 827 | Parser _concat(Parser> parsers) => 828 | parsers.map((list) => list.join()); 829 | 830 | Parser _concatSum(accum) => _concat(accum.list); 831 | 832 | Parser get _decimal => _concat(digit.many1); 833 | 834 | Parser get _hexaDecimal => 835 | _concatSum(oneOf("xX") + _concat(_hexDigit.many1)); 836 | 837 | Parser get _octal => 838 | _concatSum(oneOf("oO") + _concat(_octalDigit.many1)); 839 | 840 | Parser get _zeroNumber => 841 | _concat((char('0') + (_hexaDecimal | _octal | _decimal).orElse('')).list); 842 | 843 | Parser get _nat => _zeroNumber | _decimal; 844 | 845 | Parser get _int => _concatSum(lexeme(_maybeSign) + _nat); 846 | 847 | Parser get _exponent => 848 | _concatSum(oneOf('eE') + _maybeSign + _concat(digit.many1)); 849 | 850 | Parser get _fraction => _concatSum(char('.') + _concat(digit.many1)); 851 | 852 | Parser get _fractExponent => 853 | _concatSum(_fraction + _exponent.orElse('')) | _exponent; 854 | 855 | Parser get _float => _concatSum(decimal + _fractExponent); 856 | 857 | final RegExp _octalPrefix = new RegExp('0[Oo]'); 858 | 859 | int _parseInt(String str) { 860 | if (_octalPrefix.hasMatch(str)) { 861 | return int.parse(str.replaceFirst(_octalPrefix, ''), radix: 8); 862 | } 863 | return int.parse(str); 864 | } 865 | 866 | Parser get natural => lexeme(_nat).map(_parseInt) % 'natural number'; 867 | 868 | Parser get intLiteral => lexeme(_int).map(_parseInt) % 'integer'; 869 | 870 | Parser get floatLiteral => lexeme(_float).map(double.parse) % 'float'; 871 | 872 | Parser get decimal => lexeme(_decimal).map(int.parse) % 'decimal number'; 873 | 874 | Parser get hexaDecimal => 875 | lexeme(_hexaDecimal).map(int.parse) % 'hexadecimal number'; 876 | 877 | Parser get octal => lexeme(_octal).map(_parseInt) % 'octal number'; 878 | 879 | /** 880 | * [lexeme] parser for [symb] symbol. 881 | */ 882 | Parser symbol(String symb) => lexeme(string(symb)); 883 | 884 | /** 885 | * Parser combinator which skips whitespaces from the right side. 886 | */ 887 | Parser lexeme(Parser p) => p < whiteSpace; 888 | 889 | Parser get _start => string(_commentStart); 890 | Parser get _end => string(_commentEnd); 891 | 892 | Parser get _multiLineComment => 893 | skipEverythingBetween(_start, _end, nested: _nestedComments); 894 | 895 | Parser get _oneLineComment => 896 | string(_commentLine) > (pred((c) => c != '\n').skipMany > success(null)); 897 | 898 | Parser get whiteSpace => _whiteSpace % 'whitespace/comment'; 899 | 900 | Parser get _whiteSpace { 901 | if (_commentLine.isEmpty && _commentStart.isEmpty) { 902 | return space.skipMany; 903 | } else if (_commentLine.isEmpty) { 904 | return (space | _multiLineComment).skipMany; 905 | } else if (_commentStart.isEmpty) { 906 | return (space | _oneLineComment).skipMany; 907 | } else { 908 | return (space | _oneLineComment | _multiLineComment).skipMany; 909 | } 910 | } 911 | 912 | Parser parens(Parser p) => p.between(symbol('('), symbol(')')); 913 | 914 | Parser braces(Parser p) => p.between(symbol('{'), symbol('}')); 915 | 916 | Parser angles(Parser p) => p.between(symbol('<'), symbol('>')); 917 | 918 | Parser brackets(Parser p) => p.between(symbol('['), symbol(']')); 919 | } 920 | -------------------------------------------------------------------------------- /test/parsers_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Google Inc. All rights reserved. Use of this source code 2 | // is governed by a BSD-style license that can be found in the LICENSE file. 3 | 4 | // Authors: 5 | // Paul Brauner (polux@google.com) 6 | // Adam Singer (financecoding@gmail.com) 7 | // Maxim Dikun (me@dikmax.name) 8 | 9 | library parsers_test; 10 | 11 | import 'package:parsers/parsers.dart'; 12 | import 'package:persistent/persistent.dart'; 13 | import 'package:unittest/unittest.dart'; 14 | 15 | part 'src/parsers_model.dart'; 16 | 17 | _rest(parseResult) => parseResult.text.substring(parseResult.position.offset); 18 | 19 | class FailureMatcher extends Matcher { 20 | String rest; 21 | 22 | FailureMatcher(this.rest); 23 | 24 | bool matches(parseResult, Map matchState) { 25 | return parseResult is ParseResult 26 | && !parseResult.isSuccess 27 | && _rest(parseResult) == rest; 28 | } 29 | 30 | Description describe(Description description) => 31 | description.add('a parse failure with rest "$rest"'); 32 | } 33 | 34 | class SuccessMatcher extends Matcher { 35 | final Object res; 36 | final String rest; 37 | 38 | SuccessMatcher(this.res, this.rest); 39 | 40 | bool matches(parseResult, Map matchState) { 41 | return parseResult is ParseResult 42 | && parseResult.isSuccess 43 | && equals(parseResult.value).matches(res, null) 44 | && parseResult.text.substring(parseResult.position.offset) == rest; 45 | } 46 | 47 | Description describe(Description description) => 48 | description.add('a parse success with value $res and rest "$rest"'); 49 | } 50 | 51 | isFailure(rest) => new FailureMatcher(rest); 52 | 53 | isSuccess(res, rest) => new SuccessMatcher(res, rest); 54 | 55 | checkFloat(res, f, rest) { 56 | expect(res.isSuccess, isTrue); 57 | expect((res.value - f).abs() < 0.00001, isTrue); 58 | expect(res.rest, equals(rest)); 59 | } 60 | 61 | checkList(res, list, rest) { 62 | expect(res.isSuccess, isTrue); 63 | expect(res.value, orderedEquals(list)); 64 | expect(res.value.rest, equals(rest)); 65 | } 66 | 67 | main() { 68 | test('position 1', () => 69 | expect(new Position(1, 1, 1, tabStop: 4).addChar("\t").character, equals(5))); 70 | test('position 2', () => 71 | expect(new Position(1, 1, 2, tabStop: 4).addChar("\t").character, equals(5))); 72 | test('position 3', () => 73 | expect(new Position(1, 1, 4, tabStop: 4).addChar("\t").character, equals(5))); 74 | test('position 4', () => 75 | expect(new Position(1, 1, 5, tabStop: 4).addChar("\t").character, equals(9))); 76 | test('position 5', () => 77 | expect(new Position(1, 1, 3).addChar("\t").character, equals(4))); 78 | 79 | test('char 1', () => 80 | expect(char('a').run('abc'), isSuccess('a', 'bc'))); 81 | 82 | test('char 2', () => 83 | expect(char('a').run('a'), isSuccess('a', ''))); 84 | 85 | test('char 3', () => 86 | expect(char('a').run('bac'), isFailure('bac'))); 87 | 88 | test('char 4', () => 89 | expect(char('a').run('b'), isFailure('b'))); 90 | 91 | test('char 5', () => 92 | expect(char('a').run(''), isFailure(''))); 93 | 94 | test('string 1', () => 95 | expect(string('').run('abc'), isSuccess('', 'abc'))); 96 | 97 | test('string 2', () => 98 | expect(string('foo').run('fooabc'), isSuccess('foo', 'abc'))); 99 | 100 | test('string 3', () => 101 | expect(string('foo').run('barabc'), isFailure('barabc'))); 102 | 103 | test('string 4', () => 104 | expect(string('foo').run('fo'), isFailure('fo'))); 105 | 106 | test('string 5', () => 107 | expect(string('foo').run('foo'), isSuccess('foo', ''))); 108 | 109 | test('> 1', () => 110 | expect((char('a') > char('b')).run('abc'), isSuccess('b','c'))); 111 | 112 | test('> 2', () => 113 | expect((char('a') > char('b')).run('bbc'), isFailure('bbc'))); 114 | 115 | test('> 3', () => 116 | expect((char('a') > char('b')).run('aac'), isFailure('ac'))); 117 | 118 | final let = string("let").notFollowedBy(alphanum); 119 | 120 | test('notFollowedBy 1', () => 121 | expect(let.run('let aa'), isSuccess('let', ' aa'))); 122 | 123 | test('notFollowedBy 2', () => 124 | expect(let.run('letaa'), isFailure('aa'))); 125 | 126 | many1Prop(f) => expect(f(char('a')).run(''), isSuccess([], '')); 127 | 128 | test('many 1 model', () => many1Prop(manyModel)); 129 | test('many 1 impl', () => many1Prop(manyModel)); 130 | 131 | many2Prop(f) => expect(f(char('a')).run('aab'), isSuccess(['a', 'a'], 'b')); 132 | 133 | test('many 2 model', () => many2Prop(manyModel)); 134 | test('many 2 impl', () => many2Prop(manyModel)); 135 | 136 | many3Prop(f) => expect(f(char('a')).run('bab'), isSuccess([], 'bab')); 137 | 138 | test('many 3 model', () => many3Prop(manyModel)); 139 | test('many 3 impl', () => many3Prop(manyModel)); 140 | 141 | skipMany1Prop(f) => expect(f(char('a')).run(''), isSuccess(null, '')); 142 | 143 | test('skipMany 1 model', () => skipMany1Prop(skipManyModel)); 144 | test('skipMany 1 impl', () => skipMany1Prop(skipManyModel)); 145 | 146 | skipMany2Prop(f) => expect(f(char('a')).run('aab'), isSuccess(null, 'b')); 147 | 148 | test('skipMany 2 model', () => skipMany2Prop(skipManyModel)); 149 | test('skipMany 2 impl', () => skipMany2Prop(skipManyModel)); 150 | 151 | skipMany3Prop(f) => expect(f(char('a')).run('bab'), isSuccess(null, 'bab')); 152 | 153 | test('skipMany 3 model', () => skipMany3Prop(skipManyModel)); 154 | test('skipMany 3 impl', () => skipMany3Prop(skipManyModel)); 155 | 156 | test('maybe 1', () => 157 | expect(char('a').maybe.run('a'), isSuccess(new Option.some('a'),''))); 158 | 159 | test('maybe 2', () => 160 | expect(char('a').maybe.run('b'), isSuccess(new Option.none(),'b'))); 161 | 162 | test('sepBy 1', () => 163 | expect(char('a').sepBy(char(';')).run('a;a;a'), 164 | isSuccess(['a', 'a', 'a'], ''))); 165 | 166 | test('sepBy 2', () => 167 | expect(char('a').sepBy(char(';')).run('a;a;a;'), 168 | isSuccess(['a', 'a', 'a'], ';'))); 169 | 170 | test('sepBy 3', () => 171 | expect(char('a').sepBy(char(';')).run(''), 172 | isSuccess([], ''))); 173 | 174 | test('sepBy 4', () => 175 | expect(char('a').sepBy(char(';')).run(';'), 176 | isSuccess([], ';'))); 177 | 178 | test('sepBy 5', () => 179 | expect(char('a').sepBy(char(';')).run(''), 180 | isSuccess([], ''))); 181 | 182 | test('sepBy1 1', () => 183 | expect(char('a').sepBy1(char(';')).run('a;a'), 184 | isSuccess(['a','a'], ''))); 185 | 186 | test('sepBy1 2', () => 187 | expect(char('a').sepBy1(char(';')).run('a;a;'), 188 | isSuccess(['a','a'], ';'))); 189 | 190 | test('sepBy1 3', () => 191 | expect(char('a').sepBy1(char(';')).run(''), 192 | isFailure(''))); 193 | 194 | test('sepBy1 4', () => 195 | expect(char('a').sepEndBy1(char(';')).run(';'), 196 | isFailure(';'))); 197 | 198 | test('sepEndBy 1', () => 199 | expect(char('a').sepEndBy(char(';')).run('a;a;a'), 200 | isSuccess(['a', 'a', 'a'], ''))); 201 | 202 | test('sepEndBy 2', () => 203 | expect(char('a').sepEndBy(char(';')).run('a;a;a;'), 204 | isSuccess(['a', 'a', 'a'], ''))); 205 | 206 | test('sepEndBy 3', () => 207 | expect(char('a').sepEndBy(char(';')).run(''), 208 | isSuccess([], ''))); 209 | 210 | test('sepEndBy 4', () => 211 | expect(char('a').sepEndBy(char(';')).run(';'), 212 | isSuccess([], ';'))); 213 | 214 | test('sepEndBy1 1', () => 215 | expect(char('a').sepEndBy1(char(';')).run('a;a'), 216 | isSuccess(['a','a'], ''))); 217 | 218 | test('sepEndBy1 2', () => 219 | expect(char('a').sepEndBy1(char(';')).run('a;a;'), 220 | isSuccess(['a','a'], ''))); 221 | 222 | test('sepEndBy1 3', () => 223 | expect(char('a').sepEndBy1(char(';')).run(''), isFailure(''))); 224 | 225 | test('sepEndBy1 4', () => 226 | expect(char('a').sepEndBy1(char(';')).run(';'), isFailure(';'))); 227 | 228 | test('letter', () => 229 | expect(letter.run('a'), isSuccess('a', ''))); 230 | 231 | manyUntilProp0(f) => 232 | expect((string('/*') > f(anyChar, string('*/'))).run('/* abcdef */'), 233 | isSuccess(' abcdef '.split(''), '')); 234 | 235 | test('manyUntil 0 model', () => manyUntilProp0(manyUntilModel)); 236 | test('manyUntil 0 impl', () => manyUntilProp0(manyUntilImpl)); 237 | 238 | manyUntilProp1(f) => 239 | expect(f(anyChar, string('*/')).run(' a b c d */ e'), 240 | isSuccess(' a b c d '.split(''), ' e')); 241 | 242 | test('manyUntil 1 model', () => manyUntilProp1(manyUntilModel)); 243 | test('manyUntil 1 impl', () => manyUntilProp1(manyUntilImpl)); 244 | 245 | manyUntilProp2(f) => 246 | expect(f(anyChar, string('*/')).run(' a b c d e'), 247 | isFailure('')); 248 | 249 | test('manyUntil 2 model', () => manyUntilProp2(manyUntilModel)); 250 | test('manyUntil 2 impl', () => manyUntilProp2(manyUntilImpl)); 251 | 252 | manyUntilProp3(f) => 253 | expect(f(anyChar, string('*/').lookAhead).run(' a b c d */ e'), 254 | isSuccess(' a b c d '.split(''), '*/ e')); 255 | 256 | test('manyUntil 3 model', () => manyUntilProp3(manyUntilModel)); 257 | test('manyUntil 3 impl', () => manyUntilProp3(manyUntilImpl)); 258 | 259 | skipManyUntilProp1(f) => 260 | expect(f(anyChar, string('*/')).run(' a b c d */ e'), 261 | isSuccess(null, ' e')); 262 | 263 | test('skipManyUntil 1 model', () => skipManyUntilProp1(skipManyUntilModel)); 264 | test('skipManyUntil 1 impl', () => skipManyUntilProp1(skipManyUntilImpl)); 265 | 266 | skipManyUntilProp2(f) => 267 | expect(f(anyChar, string('*/')).run(' a b c d e'), 268 | isFailure('')); 269 | 270 | test('skipManyUntil 2 model', () => skipManyUntilProp2(skipManyUntilModel)); 271 | test('skipManyUntil 2 impl', () => skipManyUntilProp2(skipManyUntilImpl)); 272 | 273 | skipManyUntilProp3(f) => 274 | expect(f(anyChar, string('*/').lookAhead).run(' a b c d */ e'), 275 | isSuccess(null, '*/ e')); 276 | 277 | test('skipManyUntil 3 model', () => skipManyUntilProp3(skipManyUntilModel)); 278 | test('skipManyUntil 3 impl', () => skipManyUntilProp3(skipManyUntilImpl)); 279 | 280 | final lang = new LanguageParsers( 281 | nestedComments: true, 282 | reservedNames: ['for', 'in']); 283 | 284 | test('semi 1', () => 285 | expect(lang.semi.run(';rest'), isSuccess(';', 'rest'))); 286 | 287 | test('semi 2', () => 288 | expect(lang.semi.run('a'), isFailure('a'))); 289 | 290 | test('comma 1', () => 291 | expect(lang.comma.run(',rest'), isSuccess(',', 'rest'))); 292 | 293 | test('comma 2', () => 294 | expect(lang.comma.run('a'), isFailure('a'))); 295 | 296 | test('colon 1', () => 297 | expect(lang.colon.run(':rest'), isSuccess(':', 'rest'))); 298 | 299 | test('colon 2', () => 300 | expect(lang.colon.run('a'), isFailure('a'))); 301 | 302 | test('dot 1', () => 303 | expect(lang.dot.run('.rest'), isSuccess('.', 'rest'))); 304 | 305 | test('dot 2', () => 306 | expect(lang.dot.run('a'), isFailure('a'))); 307 | 308 | test('identifier 1', () => 309 | expect(lang.identifier.run('BarFoo toto'), 310 | isSuccess('BarFoo', 'toto'))); 311 | 312 | test('identifier 2', () => 313 | expect(lang.identifier.run('B6ar_Foo toto'), 314 | isSuccess('B6ar_Foo', 'toto'))); 315 | 316 | test('identifier 3', () => 317 | expect(lang.identifier.run('B6ar_Foo toto'), 318 | isSuccess('B6ar_Foo', 'toto'))); 319 | 320 | test('identifier 4', () => 321 | expect(lang.identifier.run('7B6ar_Foo toto'), 322 | isFailure('7B6ar_Foo toto'))); 323 | 324 | test('identifier 5', () => 325 | expect(lang.identifier.run('_7B6ar_Foo toto'), 326 | isSuccess('_7B6ar_Foo', 'toto'))); 327 | 328 | test('identifier 6', () => 329 | expect(lang.identifier.sepBy(lang.comma).run('abc, def, hij'), 330 | isSuccess(['abc', 'def', 'hij'], ''))); 331 | 332 | test('multi-line comment 1.1', () => 333 | expect((lang.identifier > lang.identifier).run('a /* abc */ b'), 334 | isSuccess('b', ''))); 335 | 336 | test('multi-line comment 1.2', () => 337 | expect((lang.identifier > lang.identifier).run('a /* x /* abc */ y */ b'), 338 | isSuccess('b', ''))); 339 | 340 | test('multi-line comment 1.3', () => 341 | expect((lang.identifier > lang.identifier).run('a /* x /* abc */ y b'), 342 | isFailure('/* x /* abc */ y b'))); 343 | 344 | test('multi-line comment 1.4', () => 345 | expect((lang.identifier > lang.identifier).run('a /*/**/*/ y b'), 346 | isSuccess('y', 'b'))); 347 | 348 | test('multi-line comment 1.5', () => 349 | expect((lang.identifier > lang.identifier).run('a /*/**/ y b'), 350 | isFailure('/*/**/ y b'))); 351 | 352 | test('single-line comment 1.1', () => 353 | expect((lang.identifier > lang.identifier).run('a // foo \n b'), 354 | isSuccess('b', ''))); 355 | 356 | final noNest = new LanguageParsers(nestedComments: false); 357 | 358 | test('multi-line comment 2.1', () => 359 | expect((noNest.identifier > lang.identifier).run('a /* abc */ b'), 360 | isSuccess('b', ''))); 361 | 362 | test('multi-line comment 2.2', () => 363 | expect((noNest.identifier > lang.identifier).run( 364 | 'a /* x /* abc */ y */ b'), 365 | isSuccess('y', '*/ b'))); 366 | 367 | test('multi-line comment 2.3', () => 368 | expect((noNest.identifier > lang.identifier).run('a /* x /* abc */ y b'), 369 | isSuccess('y', 'b'))); 370 | 371 | test('multi-line comment 2.4', () => 372 | expect((noNest.identifier > lang.identifier).run('a /*/**/*/ y b'), 373 | isFailure('*/ y b'))); 374 | 375 | test('multi-line comment 2.5', () => 376 | expect((noNest.identifier > lang.identifier).run('a /*/**/ y b'), 377 | isSuccess('y', 'b'))); 378 | 379 | test('single-line comment 2.1', () => 380 | expect((lang.identifier > lang.identifier).run('a // foo \n b'), 381 | isSuccess('b', ''))); 382 | 383 | test('reserved 1', () => 384 | expect(lang.reserved['for'].run('for a'), isSuccess('for', 'a'))); 385 | 386 | test('reserved 2', () => 387 | expect(lang.reserved['for'].run('fora'), isFailure('a'))); 388 | 389 | test('reserved 3', () => 390 | expect(() => lang.reserved['foo'].run('fora'), throws)); 391 | 392 | test('char 1', () => 393 | expect(lang.charLiteral.run(r"'a'"), isSuccess('a', ''))); 394 | 395 | test('char 2', () => 396 | expect(lang.charLiteral.run(r"'aa'"), isFailure("a'"))); 397 | 398 | test('char 3', () => 399 | expect(lang.charLiteral.run(r"''"), isFailure("'"))); 400 | 401 | test('char 4', () => 402 | expect(lang.charLiteral.run(r"'\t'"), isSuccess('\t', ''))); 403 | 404 | test('char 5', () => 405 | expect(lang.charLiteral.run(r"'\\'"), isSuccess(r'\', ''))); 406 | 407 | test('string 1', () => 408 | expect(lang.stringLiteral.run(r'"aaa"'), isSuccess('aaa', ''))); 409 | 410 | test('string 2', () => 411 | expect(lang.stringLiteral.run(r'"a\ta"'), isSuccess('a\ta', ''))); 412 | 413 | test('string 3', () => 414 | expect(lang.stringLiteral.run(r'"a\\a"'), isSuccess(r'a\a', ''))); 415 | 416 | test('string 4', () => 417 | expect(lang.stringLiteral.run(r'"a"a"'), isSuccess(r'a', 'a"'))); 418 | 419 | test('natural 1', () => 420 | expect(lang.natural.run('42'), isSuccess(42, ''))); 421 | 422 | test('natural 2', () => 423 | expect(lang.natural.run('0O42'), isSuccess(34, ''))); 424 | 425 | test('natural 3', () => 426 | expect(lang.natural.run('0o42'), isSuccess(34, ''))); 427 | 428 | test('natural 4', () => 429 | expect(lang.natural.run('0X42'), isSuccess(66, ''))); 430 | 431 | test('natural 5', () => 432 | expect(lang.natural.run('0xFf'), isSuccess(255, ''))); 433 | 434 | test('natural 6', () => 435 | expect(lang.natural.run('-0x42'), isFailure('-0x42'))); 436 | 437 | test('decimal 1', () => 438 | expect(lang.decimal.run('42'), isSuccess(42, ''))); 439 | 440 | test('decimal 2', () => 441 | expect(lang.decimal.run('-0x42'), isFailure('-0x42'))); 442 | 443 | test('int 1', () => 444 | expect(lang.intLiteral.run('-0x42'), isSuccess(-66, ''))); 445 | 446 | test('int 2', () => 447 | expect(lang.intLiteral.run('- 0x42'), isSuccess(-66, ''))); 448 | 449 | test('int 3', () => 450 | expect(lang.intLiteral.run('1'), isSuccess(1, ''))); 451 | 452 | test('int 4', () => 453 | expect(lang.intLiteral.run(' 1'), isSuccess(1, ''))); 454 | 455 | test('int 5', () => 456 | expect(lang.intLiteral.run('6492 '), isSuccess(6492, ''))); 457 | 458 | test('float 1', () => 459 | expect(lang.floatLiteral.run('3.14'), isSuccess(3.14, ''))); 460 | 461 | test('float 2', () => 462 | expect(lang.floatLiteral.run('3.14e5'), isSuccess(314000.0, ''))); 463 | 464 | test('float 3', () => 465 | expect(lang.floatLiteral.run('3.14e-5'), isSuccess(3.14e-5, ''))); 466 | 467 | test('parens 1', () => 468 | expect(lang.parens(string('abc')).run('(abc)rest'), 469 | isSuccess('abc', 'rest'))); 470 | 471 | test('parens 2', () => 472 | expect(lang.parens(string('abc')).run('abc)'), isFailure('abc)'))); 473 | 474 | test('parens 3', () => 475 | expect(lang.parens(string('abc')).run('(abc'), isFailure(''))); 476 | 477 | test('braces 1', () => 478 | expect(lang.braces(string('abc')).run('{abc}rest'), 479 | isSuccess('abc', 'rest'))); 480 | 481 | test('braces 2', () => 482 | expect(lang.braces(string('abc')).run('abc}'), isFailure('abc}'))); 483 | 484 | test('braces 3', () => 485 | expect(lang.braces(string('abc')).run('{abc'), isFailure(''))); 486 | 487 | test('angles 1', () => 488 | expect(lang.angles(string('abc')).run('rest'), 489 | isSuccess('abc', 'rest'))); 490 | 491 | test('angles 2', () => 492 | expect(lang.angles(string('abc')).run('abc>'), isFailure('abc>'))); 493 | 494 | test('angles 3', () => 495 | expect(lang.angles(string('abc')).run(' 498 | expect(lang.brackets(string('abc')).run('[abc]rest'), 499 | isSuccess('abc', 'rest'))); 500 | 501 | test('brackets 2', () => 502 | expect(lang.brackets(string('abc')).run('abc]'), isFailure('abc]'))); 503 | 504 | test('brackets 3', () => 505 | expect(lang.brackets(string('abc')).run('[abc'), isFailure(''))); 506 | 507 | test('chainl 1', () => 508 | expect(lang.natural.chainl(success((x, y) => x + y), 42).run('1 2 3'), 509 | isSuccess(6, ''))); 510 | 511 | test('chainl 2', () => 512 | expect(lang.natural.chainl(success((x, y) => x + y), 42).run('a 2 3'), 513 | isSuccess(42, 'a 2 3'))); 514 | 515 | final addop = (lang.symbol('+') > success((x, y) => x + y)) 516 | | (lang.symbol('-') > success((x, y) => x - y)); 517 | 518 | test('chainl 3', () => 519 | expect(lang.natural.chainl(addop, 42).run('3 - 1 - 2'), 520 | isSuccess(0, ''))); 521 | 522 | test('chainl 4', () => 523 | expect(lang.natural.chainl(addop, 42).run('a - 1 - 2'), 524 | isSuccess(42, 'a - 1 - 2'))); 525 | 526 | chainl1Prop1(f) => 527 | expect(f(lang.natural, success((x, y) => x + y)).run('1 2 3'), 528 | isSuccess(6, '')); 529 | 530 | test('chainl1 1 model', () => chainl1Prop1(chainl1Model)); 531 | test('chainl1 1 impl', () => chainl1Prop1(chainl1Impl)); 532 | 533 | chainl1Prop2(f) => 534 | expect(f(lang.natural, success((x, y) => x + y)).run('a 2 3'), 535 | isFailure('a 2 3')); 536 | 537 | test('chainl1 2 model', () => chainl1Prop2(chainl1Model)); 538 | test('chainl1 2 impl', () => chainl1Prop2(chainl1Impl)); 539 | 540 | chainl1Prop3(f) => 541 | expect(f(lang.natural, addop).run('3 - 1 - 2'), 542 | isSuccess(0, '')); 543 | 544 | test('chainl1 3 model', () => chainl1Prop3(chainl1Model)); 545 | test('chainl1 3 impl', () => chainl1Prop3(chainl1Impl)); 546 | 547 | chainl1Prop4(f) => 548 | expect(f(lang.natural, addop).run('a - 1 - 2'), 549 | isFailure('a - 1 - 2')); 550 | 551 | test('chainl1 4 model', () => chainl1Prop4(chainl1Model)); 552 | test('chainl1 4 impl', () => chainl1Prop4(chainl1Impl)); 553 | 554 | test('chainr 1', () => 555 | expect(lang.natural.chainr(success((x, y) => x + y), 42).run('1 2 3'), 556 | isSuccess(6, ''))); 557 | 558 | test('chainr 2', () => 559 | expect(lang.natural.chainr(success((x, y) => x + y), 42).run('a 2 3'), 560 | isSuccess(42, 'a 2 3'))); 561 | 562 | test('chainr 3', () => 563 | expect(lang.natural.chainr(addop, 42).run('3 - 1 - 2'), 564 | isSuccess(4, ''))); 565 | 566 | test('chainr 4', () => 567 | expect(lang.intLiteral.chainr(addop, 42).run('a - 1 - 2'), 568 | isSuccess(42, 'a - 1 - 2'))); 569 | 570 | test('chainr1 1', () => 571 | expect(lang.intLiteral.chainr1(success((x, y) => x + y)).run('1 2 3'), 572 | isSuccess(6, ''))); 573 | 574 | test('chainr1 2', () => 575 | expect(lang.intLiteral.chainr1(success((x, y) => x + y)).run('a 2 3'), 576 | isFailure('a 2 3'))); 577 | 578 | test('chainr1 3', () => 579 | expect(lang.intLiteral.chainr1(addop).run('3 - 1 - 2'), 580 | isSuccess(4, ''))); 581 | 582 | test('chainr1 4', () => 583 | expect(lang.intLiteral.chainr1(addop).run('a - 1 - 2'), 584 | isFailure('a - 1 - 2'))); 585 | 586 | test('choice 1', () => 587 | expect(choice([char('a'), char('b'), char('c')]).run('b'), 588 | isSuccess('b', ''))); 589 | 590 | test('choice 2', () => 591 | expect(choice([char('a'), char('b'), char('c')]).run('d'), 592 | isFailure('d'))); 593 | 594 | test('skipEverythingBetween 1', () => 595 | expect(skipEverythingBetween(string('ab'), string('ac')) 596 | .run('ab aaa ab aaa ac aaa ac foo'), 597 | isSuccess(null, ' aaa ac foo'))); 598 | 599 | test('skipEverythingBetween 2', () => 600 | expect(skipEverythingBetween(string('ab'), string('ac'), nested: true) 601 | .run('ab aaa ab aaa ac aaa ac foo'), 602 | isSuccess(null, ' foo'))); 603 | 604 | test('skipEverythingBetween 3', () => 605 | expect(skipEverythingBetween(string('ab'), string('ac')).run("abaaaaa"), 606 | isFailure(''))); 607 | 608 | test('skipEverythingBetween 4', () => 609 | expect(skipEverythingBetween(string('ab'), string('ac'), nested: true) 610 | .run("abaaaaa"), 611 | isFailure(''))); 612 | 613 | test('everythingBetween 1', () => 614 | expect(everythingBetween(string('ab'), string('ac')) 615 | .run('ab aaa ab aaa ac aaa ac foo'), 616 | isSuccess(' aaa ab aaa ', ' aaa ac foo'))); 617 | 618 | test('everythingBetween 2', () => 619 | expect(everythingBetween(string('ab'), string('ac'), nested: true) 620 | .run('ab aaa ab aaa ac aaa ac foo'), 621 | isSuccess(' aaa ab aaa ac aaa ', ' foo'))); 622 | 623 | test('everythingBetween 3', () => 624 | expect(everythingBetween(string('ab'), string('ac')).run("abaaaaa"), 625 | isFailure(''))); 626 | 627 | test('everythingBetween 4', () => 628 | expect(everythingBetween(string('ab'), string('ac'), nested: true) 629 | .run("abaaaaa"), 630 | isFailure(''))); 631 | 632 | test('record 1', () => 633 | expect(char('a').many.record.run('aaaabb'), 634 | isSuccess('aaaa', 'bb'))); 635 | 636 | test('record 2', () => 637 | expect(string('aa').record.run('abaabb'), 638 | isFailure('abaabb'))); 639 | 640 | test('record 2', () => 641 | expect(char('a').record.run(''), 642 | isFailure(''))); 643 | 644 | test('commit 1', () => 645 | expect((char('a').committed | char('b')).run('bc'), 646 | isFailure('bc'))); 647 | 648 | test('commit 2', () => 649 | expect((char('a').committed | char('b')).run('ac'), 650 | isSuccess('a', 'c'))); 651 | 652 | test('commit 3', () => 653 | expect((char('a') > char('b').committed | char('a')).run('acc'), 654 | isFailure('cc'))); 655 | 656 | test('commit 4', () => 657 | expect((char('a') > char('b').committed | char('a')).run('abc'), 658 | isSuccess('b', 'c'))); 659 | 660 | test('commit 5', () => 661 | expect(char('a').committed.many.run('ccc'), 662 | isFailure('ccc'))); 663 | 664 | test('commit 6', () => 665 | expect(char('a').committed.many.run('aac'), 666 | isFailure('c'))); 667 | 668 | test('commit 7', () => 669 | expect(char('a').committed.many.run('aaa'), 670 | isFailure(''))); 671 | 672 | test('commit 8', () => 673 | expect(char('a').committed.many.run(''), 674 | isFailure(''))); 675 | 676 | test('commit 9', () => 677 | expect(char('a').committed.skipMany.run('ccc'), 678 | isFailure('ccc'))); 679 | 680 | test('commit 10', () => 681 | expect(char('a').committed.skipMany.run('aac'), 682 | isFailure('c'))); 683 | 684 | test('commit 11', () => 685 | expect(char('a').committed.skipMany.run('aaa'), 686 | isFailure(''))); 687 | 688 | test('commit 12', () => 689 | expect(char('a').committed.skipMany.run(''), 690 | isFailure(''))); 691 | 692 | plus(x, y) => x + y; 693 | 694 | test('commit 13', () => 695 | expect(lang.natural.committed.chainl(success(plus), 42).run('1 2 3'), 696 | isFailure(''))); 697 | 698 | commit135(f) => 699 | expect(f(lang.natural.committed, success(plus)).run('1 2 3'), 700 | isFailure('')); 701 | 702 | test('commit 13.5 model', () => commit135(chainl1Model)); 703 | test('commit 13.5 model', () => commit135(chainl1Impl)); 704 | 705 | test('commit 14', () => 706 | expect(lang.natural.committed.chainl(success(plus), 42).run('a 2 3'), 707 | isFailure('a 2 3'))); 708 | 709 | commit145(f) => 710 | expect(f(lang.natural.committed, success(plus)).run('a 2 3'), 711 | isFailure('a 2 3')); 712 | 713 | test('commit 14.5 model', () => commit145(chainl1Model)); 714 | test('commit 14.5 model', () => commit145(chainl1Impl)); 715 | 716 | test('commit 15', () => 717 | expect(lang.natural.chainl(success(plus).committed, 42).run('1 2 3'), 718 | isFailure(''))); 719 | 720 | commit155(f) => 721 | expect(f(lang.natural, success(plus).committed).run('1 2 3'), 722 | isFailure('')); 723 | 724 | test('commit 15.5 model', () => commit155(chainl1Model)); 725 | test('commit 15.5 model', () => commit155(chainl1Impl)); 726 | 727 | test('commit 16', () => 728 | expect(lang.natural.chainl(success(plus).committed, 42).run('a 2 3'), 729 | isSuccess(42, 'a 2 3'))); 730 | 731 | commit165(f) => 732 | expect(f(lang.natural, success(plus).committed).run('a 2 3'), 733 | isFailure('a 2 3')); 734 | 735 | test('commit 16.5 model', () => commit165(chainl1Model)); 736 | test('commit 16.5 model', () => commit165(chainl1Impl)); 737 | 738 | test('commit 17', () => 739 | expect(choice([char('a').committed, char('b'), char('c')]).run('az'), 740 | isSuccess('a', 'z'))); 741 | 742 | test('commit 18', () => 743 | expect(choice([char('a').committed, char('b'), char('c')]).run('bz'), 744 | isFailure('bz'))); 745 | 746 | test('commit 19', () => 747 | expect(choice([char('a'), char('b').committed, char('c')]).run('bz'), 748 | isSuccess('b', 'z'))); 749 | 750 | test('commit 20', () => 751 | expect(choice([char('a'), char('b').committed, char('c')]).run('cz'), 752 | isFailure('cz'))); 753 | 754 | commit21Prop(f) => 755 | expect(f(char('a').committed, char('z')).run('ccc'), 756 | isFailure('ccc')); 757 | 758 | test('commit 21 model', () => commit21Prop(skipManyUntilModel)); 759 | test('commit 21 impl', () => commit21Prop(skipManyUntilImpl)); 760 | 761 | commit22Prop(f) => 762 | expect(f(char('a').committed, char('z')).run('aac'), 763 | isFailure('c')); 764 | 765 | test('commit 22 model', () => commit22Prop(skipManyUntilModel)); 766 | test('commit 22 impl', () => commit22Prop(skipManyUntilImpl)); 767 | 768 | commit23Prop(f) => 769 | expect(f(char('a').committed, char('z')).run('aaa'), 770 | isFailure('')); 771 | 772 | test('commit 23 model', () => commit23Prop(skipManyUntilModel)); 773 | test('commit 23 impl', () => commit23Prop(skipManyUntilImpl)); 774 | 775 | commit24Prop(f) => 776 | expect(f(char('a').committed, char('z')).run(''), 777 | isFailure('')); 778 | 779 | test('commit 24 model', () => commit24Prop(skipManyUntilModel)); 780 | test('commit 24 impl', () => commit24Prop(skipManyUntilImpl)); 781 | 782 | commit25Prop(f) => 783 | expect(f(char('a').committed, char('z')).run('aaazw'), 784 | isSuccess(null, 'w')); 785 | 786 | test('commit 25 model', () => commit25Prop(skipManyUntilModel)); 787 | test('commit 25 impl', () => commit25Prop(skipManyUntilImpl)); 788 | 789 | commit26Prop(f) => 790 | expect(f(char('a'), char('z').committed).run('aaazw'), 791 | isFailure('aaazw')); 792 | 793 | test('commit 26 model', () => commit26Prop(skipManyUntilModel)); 794 | test('commit 26 impl', () => commit26Prop(skipManyUntilImpl)); 795 | 796 | commit27Prop(f) => 797 | expect(f(char('a'), char('z').committed).run('zw'), 798 | isSuccess(null, 'w')); 799 | 800 | test('commit 27 model', () => commit27Prop(skipManyUntilModel)); 801 | test('commit 27 impl', () => commit27Prop(skipManyUntilImpl)); 802 | 803 | commit28Prop(f) => 804 | expect(f(char('a').committed, char('z')).run('ccc'), 805 | isFailure('ccc')); 806 | 807 | test('commit 28 model', () => commit28Prop(manyUntilModel)); 808 | test('commit 28 impl', () => commit28Prop(manyUntilImpl)); 809 | 810 | commit29Prop(f) => 811 | expect(f(char('a').committed, char('z')).run('aac'), 812 | isFailure('c')); 813 | 814 | test('commit 29 model', () => commit29Prop(manyUntilModel)); 815 | test('commit 29 impl', () => commit29Prop(manyUntilImpl)); 816 | 817 | commit30Prop(f) => 818 | expect(f(char('a').committed, char('z')).run('aaa'), 819 | isFailure('')); 820 | 821 | test('commit 30 model', () => commit30Prop(manyUntilModel)); 822 | test('commit 30 impl', () => commit30Prop(manyUntilImpl)); 823 | 824 | commit31Prop(f) => 825 | expect(f(char('a').committed, char('z')).run(''), 826 | isFailure('')); 827 | 828 | test('commit 31 model', () => commit31Prop(manyUntilModel)); 829 | test('commit 31 impl', () => commit31Prop(manyUntilImpl)); 830 | 831 | 832 | commit32Prop(f) => 833 | expect(f(char('a').committed, char('z')).run('aaazw'), 834 | isSuccess(['a','a','a'], 'w')); 835 | 836 | test('commit 32 model', () => commit32Prop(manyUntilModel)); 837 | test('commit 32 impl', () => commit32Prop(manyUntilImpl)); 838 | 839 | commit33Prop(f) => 840 | expect(f(char('a'), char('z').committed).run('aaazw'), 841 | isFailure('aaazw')); 842 | 843 | test('commit 33 model', () => commit33Prop(manyUntilModel)); 844 | test('commit 33 impl', () => commit33Prop(manyUntilImpl)); 845 | 846 | commit34Prop(f) => 847 | expect(f(char('a'), char('z').committed).run('zw'), 848 | isSuccess([], 'w')); 849 | 850 | test('commit 34 model', () => commit34Prop(manyUntilModel)); 851 | test('commit 34 impl', () => commit34Prop(manyUntilImpl)); 852 | 853 | 854 | 855 | 856 | commit35Prop(f) => 857 | expect(f(char('a').committed).run('ccc'), 858 | isFailure('ccc')); 859 | 860 | test('commit 35 model', () => commit35Prop(manyModel)); 861 | test('commit 35 impl', () => commit35Prop(manyImpl)); 862 | 863 | commit36Prop(f) => 864 | expect(f(char('a').committed).run('aac'), 865 | isFailure('c')); 866 | 867 | test('commit 36 model', () => commit36Prop(manyModel)); 868 | test('commit 36 impl', () => commit36Prop(manyImpl)); 869 | 870 | commit37Prop(f) => 871 | expect(f(char('a').committed).run('aaa'), 872 | isFailure('')); 873 | 874 | test('commit 37 model', () => commit37Prop(manyModel)); 875 | test('commit 37 impl', () => commit37Prop(manyImpl)); 876 | 877 | commit38Prop(f) => 878 | expect(f(char('a').committed).run(''), 879 | isFailure('')); 880 | 881 | test('commit 38 model', () => commit38Prop(manyModel)); 882 | test('commit 38 impl', () => commit38Prop(manyImpl)); 883 | 884 | commit39Prop(f) => 885 | expect(f(char('a').committed).run('aaazw'), 886 | isFailure('zw')); 887 | 888 | test('commit 39 model', () => commit39Prop(manyModel)); 889 | test('commit 39 impl', () => commit39Prop(manyImpl)); 890 | 891 | commit40Prop(f) => 892 | expect(f(char('a').committed).run('ccc'), 893 | isFailure('ccc')); 894 | 895 | test('commit 40 model', () => commit40Prop(skipManyModel)); 896 | test('commit 40 impl', () => commit40Prop(skipManyImpl)); 897 | 898 | commit41Prop(f) => 899 | expect(f(char('a').committed).run('aac'), 900 | isFailure('c')); 901 | 902 | test('commit 41 model', () => commit41Prop(skipManyModel)); 903 | test('commit 41 impl', () => commit41Prop(skipManyImpl)); 904 | 905 | commit42Prop(f) => 906 | expect(f(char('a').committed).run('aaa'), 907 | isFailure('')); 908 | 909 | test('commit 42 model', () => commit42Prop(skipManyModel)); 910 | test('commit 42 impl', () => commit42Prop(skipManyImpl)); 911 | 912 | commit43Prop(f) => 913 | expect(f(char('a').committed).run(''), 914 | isFailure('')); 915 | 916 | test('commit 43 model', () => commit43Prop(skipManyModel)); 917 | test('commit 43 impl', () => commit43Prop(skipManyImpl)); 918 | 919 | commit44Prop(f) => 920 | expect(f(char('a').committed).run('aaazw'), 921 | isFailure('zw')); 922 | 923 | test('commit 44 model', () => commit44Prop(skipManyModel)); 924 | test('commit 44 impl', () => commit44Prop(skipManyImpl)); 925 | 926 | commit45Prop(f) { 927 | t3(x) => (y) => (z) => '$x$y$z'; 928 | final p = (success(t3) * char('a') * char('b').committed * char('c')) 929 | | (success(t3) * char('d') * char('e') * char('f')); 930 | return expect(f(p).run('abcabczz'), 931 | isSuccess(['abc', 'abc'], 'zz')); 932 | } 933 | 934 | test('commit 45 model', () => commit45Prop(manyModel)); 935 | test('commit 45 impl', () => commit45Prop(manyImpl)); 936 | 937 | commit46Prop(f) { 938 | t3(x) => (y) => (z) => '$x$y$z'; 939 | final p = (success(t3) * char('a') * char('b').committed * char('c')) 940 | | (success(t3) * char('d') * char('e') * char('f')); 941 | return expect(f(p).run('abcdefabczz'), 942 | isSuccess(['abc', 'def', 'abc'], 'zz')); 943 | } 944 | 945 | test('commit 46 model', () => commit46Prop(manyModel)); 946 | test('commit 46 impl', () => commit46Prop(manyImpl)); 947 | 948 | commit47Prop(f) { 949 | t3(x) => (y) => (z) => '$x$y$z'; 950 | final p = (success(t3) * char('a') * char('b').committed * char('c')) 951 | | (success(t3) * char('a') * char('e') * char('f')); 952 | return expect(f(p).run('abcaefzz'), 953 | isFailure('efzz')); 954 | } 955 | 956 | test('commit 47 model', () => commit47Prop(manyModel)); 957 | test('commit 47 impl', () => commit47Prop(manyImpl)); 958 | 959 | commit475Prop(f) { 960 | final p = f(char('x') > char('a').committed) > string('b') 961 | | string('xaxac'); 962 | return expect(p.run('xaxac'), isFailure('c')); 963 | } 964 | 965 | test('commit 47.5 model', () => commit475Prop(manyModel)); 966 | test('commit 47.5 impl', () => commit475Prop(manyImpl)); 967 | 968 | commit48Prop(f) { 969 | final p = (char('a') > (char('b').committed > char('c'))) 970 | | (char('d') > (char('e') > char('f'))); 971 | return expect(f(p).run('abcabczz'), isSuccess(null, 'zz')); 972 | } 973 | 974 | test('commit 48 model', () => commit48Prop(skipManyModel)); 975 | test('commit 48 impl', () => commit48Prop(skipManyImpl)); 976 | 977 | commit49Prop(f) { 978 | final p = (char('a') > (char('b').committed > char('c'))) 979 | | (char('d') > (char('e') > char('f'))); 980 | return expect(f(p).run('abcdefabczz'), isSuccess(null, 'zz')); 981 | } 982 | 983 | test('commit 49 model', () => commit49Prop(skipManyModel)); 984 | test('commit 49 impl', () => commit49Prop(skipManyImpl)); 985 | 986 | commit50Prop(f) { 987 | final p = (char('a') > (char('b').committed > char('c'))) 988 | | (char('d') > (char('e') > char('f'))); 989 | return expect(f(p).run('abcaefzz'), isFailure('efzz')); 990 | } 991 | 992 | test('commit 50 model', () => commit50Prop(skipManyModel)); 993 | test('commit 50 impl', () => commit50Prop(skipManyImpl)); 994 | 995 | commit51Prop(f) { 996 | t3(x) => (y) => (z) => '$x$y$z'; 997 | final p = (success(t3) * char('a') * char('b').committed * char('c')) 998 | | (success(t3) * char('a') * char('e') * char('f')); 999 | return expect(f(p, char('z')).run('abcabczz'), 1000 | isSuccess(['abc', 'abc'], 'z')); 1001 | } 1002 | 1003 | test('commit 51 model', () => commit51Prop(manyUntilModel)); 1004 | test('commit 51 impl', () => commit51Prop(manyUntilImpl)); 1005 | 1006 | commit515Prop(f) { 1007 | final p = (f(char('x') > char('a').committed) > string('b')) 1008 | | string('xaxac'); 1009 | return expect(p.run('xaxac'), isFailure('c')); 1010 | } 1011 | 1012 | test('commit 51.5 model', () => commit515Prop(skipManyModel)); 1013 | test('commit 51.5 impl', () => commit515Prop(skipManyImpl)); 1014 | 1015 | commit52Prop(f) { 1016 | t3(x) => (y) => (z) => '$x$y$z'; 1017 | final p = (success(t3) * char('a') * char('b').committed * char('c')) 1018 | | (success(t3) * char('d') * char('e') * char('f')); 1019 | return expect(f(p, char('z')).run('abcdefabczz'), 1020 | isSuccess(['abc', 'def', 'abc'], 'z')); 1021 | } 1022 | 1023 | test('commit 52 model', () => commit52Prop(manyUntilModel)); 1024 | test('commit 52 impl', () => commit52Prop(manyUntilModel)); 1025 | 1026 | commit53Prop(f) { 1027 | t3(x) => (y) => (z) => '$x$y$z'; 1028 | final p = (success(t3) * char('a') * char('b').committed * char('c')) 1029 | | (success(t3) * char('a') * char('e') * char('f')); 1030 | return expect(f(p, char('z')).run('abcaefzz'), isFailure('efzz')); 1031 | } 1032 | 1033 | test('commit 53 model', () => commit53Prop(manyUntilModel)); 1034 | test('commit 53 impl', () => commit53Prop(manyUntilModel)); 1035 | 1036 | commit54Prop(f) { 1037 | final p = f(char('x') > char('a').committed, char('z')) > string('b') 1038 | | string('xaxazc'); 1039 | return expect(p.run('xaxazc'), isFailure('c')); 1040 | } 1041 | 1042 | test('commit 54 model', () => commit54Prop(manyUntilModel)); 1043 | test('commit 54 impl', () => commit54Prop(manyUntilImpl)); 1044 | 1045 | commit55Prop(f) { 1046 | final p = f(char('x') > char('a').committed, char('z')) 1047 | | string('xaxazc'); 1048 | return expect(p.run('xaxaw'), isFailure('w')); 1049 | } 1050 | 1051 | test('commit 55 model', () => commit55Prop(manyUntilModel)); 1052 | test('commit 55 impl', () => commit55Prop(manyUntilImpl)); 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | commit56Prop(f) { 1059 | t3(x) => (y) => (z) => int.parse(x) + int.parse(y) + int.parse(z); 1060 | plus(x, y) => x + y; 1061 | final p = (success(t3) * char('1') * char('2').committed * char('3')) 1062 | | (success(t3) * char('4') * char('5') * char('6')); 1063 | return expect(f(p, success(plus)).run('123456123zz'), 1064 | isSuccess(27, 'zz')); 1065 | } 1066 | 1067 | test('commit 56 model', () => commit56Prop(chainl1Model)); 1068 | test('commit 56 impl', () => commit56Prop(chainl1Impl)); 1069 | 1070 | commit57Prop(f) { 1071 | t3(x) => (y) => (z) => int.parse(x) + int.parse(y) + int.parse(z); 1072 | plus(x, y) => x + y; 1073 | final p = (success(t3) * char('1') * char('2').committed * char('3')) 1074 | | (success(t3) * char('1') * char('5') * char('6')); 1075 | return expect(f(p, success(plus)).run('123156zz'), 1076 | isFailure('56zz')); 1077 | } 1078 | 1079 | test('commit 57 model', () => commit57Prop(chainl1Model)); 1080 | test('commit 57 impl', () => commit57Prop(chainl1Impl)); 1081 | 1082 | commit58Prop(f) { 1083 | plus(x, y) => '$x$y'; 1084 | final p = f(char('x') > char('a').committed, success(plus)) > string('b') 1085 | | string('xaxac'); 1086 | return expect(p.run('xaxac'), isFailure('c')); 1087 | } 1088 | 1089 | test('commit 58 model', () => commit58Prop(chainl1Model)); 1090 | test('commit 58 impl', () => commit58Prop(chainl1Impl)); 1091 | 1092 | commit59Prop(f) { 1093 | plus(x, y) => '$x$y'; 1094 | final p = f(char('x') > char('a'), success(plus).committed) > string('b') 1095 | | string('xaxac'); 1096 | return expect(p.run('xaxac'), isFailure('c')); 1097 | } 1098 | 1099 | test('commit 59 model', () => commit59Prop(chainl1Model)); 1100 | test('commit 59 impl', () => commit59Prop(chainl1Impl)); 1101 | 1102 | test('sequence map 1', () => 1103 | expect((char('a') + char('b') ^ (x,y) => '$y$x').run('abc'), 1104 | isSuccess('ba', 'c'))); 1105 | 1106 | test('sequence map 2', () => 1107 | expect((char('a') + char('b') ^ (x,y) => '$y$x').run('acb'), 1108 | isFailure('cb'))); 1109 | 1110 | test('sequence list 1', () => 1111 | expect((char('a') + char('b')).list.run('abc'), 1112 | isSuccess(['a', 'b'], 'c'))); 1113 | 1114 | test('sequence list 2', () => 1115 | expect((char('a') + char('b')).list.run('acb'), 1116 | isFailure('cb'))); 1117 | 1118 | var big = "a"; 1119 | for (int i = 0; i < 15; i++) { big = '$big$big'; } 1120 | 1121 | test('no stack overflow many', () => 1122 | expect(char('a').many.run(big).value.length, equals(32768))); 1123 | 1124 | test('no stack overflow skipMany', () => 1125 | expect(char('a').skipMany.run('${big}bb'), isSuccess(null, 'bb'))); 1126 | 1127 | test('no stack overflow manyUntil', () => 1128 | expect(anyChar.manyUntil(char('b')).run('${big}b').value.length, 1129 | equals(32768))); 1130 | 1131 | test('no stack overflow comment', () => 1132 | expect(lang.natural.run('1 /* $big */'), isSuccess(1, ''))); 1133 | } 1134 | --------------------------------------------------------------------------------