// ignore_for_file: unnecessary_lambdas import 'package:petitparser/petitparser.dart'; import 'package:test/test.dart'; import 'utils/matchers.dart'; class ListGrammarDefinition extends GrammarDefinition { @override Parser start() => ref0(list).end(); Parser list() => ref0(element) & char(',') & ref0(list) | ref0(element); Parser element() => digit().plus().flatten(); } class ListParserDefinition extends ListGrammarDefinition { @override Parser element() => super.element().map((value) => int.parse(value as String)); } class TokenizedListGrammarDefinition extends GrammarDefinition { @override Parser start() => ref0(list).end(); Parser list() => ref0(element) & ref1(token, char(',')) & ref0(list) | ref0(element); Parser element() => ref1(token, digit().plus()); Parser token(Parser parser) => parser.flatten().trim(); } class TypedReferencesGrammarDefinition extends GrammarDefinition { @override Parser> start() => ref0(f0); Parser> f0() => ref1(f1, 1); Parser> f1(int a1) => ref2(f2, a1, 2); Parser> f2(int a1, int a2) => ref3(f3, a1, a2, 3); Parser> f3(int a1, int a2, int a3) => ref4(f4, a1, a2, a3, 4); Parser> f4(int a1, int a2, int a3, int a4) => ref5(f5, a1, a2, a3, a4, 5); Parser> f5(int a1, int a2, int a3, int a4, int a5) => [ a1.toString().toParser(), a2.toString().toParser(), a3.toString().toParser(), a4.toString().toParser(), a5.toString().toParser(), ].toSequenceParser(); } class UntypedReferencesGrammarDefinition extends GrammarDefinition { @override Parser start() => ref0(f0); Parser f0() => ref1(f1, 1); Parser f1(int a1) => ref2(f2, a1, 2); Parser f2(int a1, int a2) => ref3(f3, a1, a2, 3); Parser f3(int a1, int a2, int a3) => ref4(f4, a1, a2, a3, 4); Parser f4(int a1, int a2, int a3, int a4) => ref5(f5, a1, a2, a3, a4, 5); Parser f5(int a1, int a2, int a3, int a4, int a5) => [ a1.toString().toParser(), a2.toString().toParser(), a3.toString().toParser(), a4.toString().toParser(), a5.toString().toParser(), ].toSequenceParser(); } class DeprecatedUntypedReferencesGrammarDefinition extends GrammarDefinition { @override Parser start() => ref(f0); Parser f0() => ref(f1, 1); Parser f1(int a1) => ref(f2, a1, 2); Parser f2(int a1, int a2) => ref(f3, a1, a2, 3); Parser f3(int a1, int a2, int a3) => ref(f4, a1, a2, a3, 4); Parser f4(int a1, int a2, int a3, int a4) => ref(f5, a1, a2, a3, a4, 5); Parser f5(int a1, int a2, int a3, int a4, int a5) => [ a1.toString().toParser(), a2.toString().toParser(), a3.toString().toParser(), a4.toString().toParser(), a5.toString().toParser(), ].toSequenceParser(); } class BuggedGrammarDefinition extends GrammarDefinition { @override Parser start() => epsilon(); Parser directRecursion1() => ref0(directRecursion1); Parser indirectRecursion1() => ref0(indirectRecursion2); Parser indirectRecursion2() => ref0(indirectRecursion3); Parser indirectRecursion3() => ref0(indirectRecursion1); Parser delegation1() => ref0(delegation2); Parser delegation2() => ref0(delegation3); Parser delegation3() => epsilon(); } class LambdaGrammarDefinition extends GrammarDefinition { @override Parser start() => ref0(expression).end(); Parser expression() => ref0(variable) | ref0(abstraction) | ref0(application); Parser variable() => (letter() & word().star()).flatten().trim(); Parser abstraction() => token('\\') & ref0(variable) & token('.') & ref0(expression); Parser application() => token('(') & ref0(expression) & ref0(expression) & token(')'); Parser token(String value) => char(value).trim(); } class ExpressionGrammarDefinition extends GrammarDefinition { @override Parser start() => ref0(terms).end(); Parser terms() => ref0(addition) | ref0(factors); Parser addition() => ref0(factors).plusSeparated(token(char('+') | char('-'))); Parser factors() => ref0(multiplication) | ref0(power); Parser multiplication() => ref0(power).plusSeparated(token(char('*') | char('/'))); Parser power() => ref0(primary).plusSeparated(char('^').trim()); Parser primary() => ref0(number) | ref0(parentheses); Parser number() => token( char('-').optional() & digit().plus() & (char('.') & digit().plus()).optional(), ); Parser parentheses() => token('(') & ref0(terms) & token(')'); Parser token(Object value) { if (value is String) { return char(value).trim(); } else if (value is Parser) { return value.flatten().trim(); } throw ArgumentError.value(value, 'unable to parse'); } } void main() { group('reference & resolve', () { Parser numberToken() => (char('-').optional() & digit().plus() & (char('.') & digit().plus()).optional()) .flatten() .trim(); Parser number() => ref0(numberToken).map(num.parse); Parser> numberList([String separator = ',']) => ref0( number, ).plusSeparated(separator.toParser()).map((list) => list.elements); test('reference without parameters', () { final firstReference = ref0(number); final secondReference = ref0(number); expect(firstReference, isNot(same(secondReference))); expect(firstReference == secondReference, isTrue); }); test('reference with different production', () { final firstReference = ref0(number); final secondReference = ref0(numberToken); expect(firstReference, isNot(same(secondReference))); // ignore: unrelated_type_equality_checks expect(firstReference == secondReference, isFalse); }); test('reference with same parameters', () { final firstReference = ref1(numberList, ','); final secondReference = ref1(numberList, ','); expect(firstReference, isNot(same(secondReference))); expect(firstReference == secondReference, isTrue); }); test('reference with different parameters', () { final firstReference = ref1(numberList, ','); final secondReference = ref1(numberList, ';'); expect(firstReference, isNot(same(secondReference))); expect(firstReference == secondReference, isFalse); }); test('reference unsupported methods', () { final reference = ref0(number); expect(() => reference.copy(), throwsUnsupportedError); expect(() => reference.parse('0'), throwsUnsupportedError); expect(() => reference.fastParseOn('0', 0), throwsUnsupportedError); }); test('references typed', () { Parser> f9( int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, ) => [ a1, a2, a3, a4, a5, a6, a7, a8, a9, ].map((value) => value.toString().toParser()).toSequenceParser(); Parser> f8( int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, ) => ref9(f9, a1, a2, a3, a4, a5, a6, a7, a8, 9); Parser> f7( int a1, int a2, int a3, int a4, int a5, int a6, int a7, ) => ref8(f8, a1, a2, a3, a4, a5, a6, a7, 8); Parser> f6(int a1, int a2, int a3, int a4, int a5, int a6) => ref7(f7, a1, a2, a3, a4, a5, a6, 7); Parser> f5(int a1, int a2, int a3, int a4, int a5) => ref6(f6, a1, a2, a3, a4, a5, 6); Parser> f4(int a1, int a2, int a3, int a4) => ref5(f5, a1, a2, a3, a4, 5); Parser> f3(int a1, int a2, int a3) => ref4(f4, a1, a2, a3, 4); Parser> f2(int a1, int a2) => ref3(f3, a1, a2, 3); Parser> f1(int a1) => ref2(f2, a1, 2); Parser> f0() => ref1(f1, 1); Parser> start() => ref0(f0); expect( resolve(start()), isParseSuccess('123456789', result: '123456789'.split('')), ); }); test('references untyped', () { Parser> f9( int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, ) => [ a1, a2, a3, a4, a5, a6, a7, a8, a9, ].map((value) => value.toString().toParser()).toSequenceParser(); Parser> f8( int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, ) => ref(f9, a1, a2, a3, a4, a5, a6, a7, a8, 9); Parser> f7( int a1, int a2, int a3, int a4, int a5, int a6, int a7, ) => ref(f8, a1, a2, a3, a4, a5, a6, a7, 8); Parser> f6(int a1, int a2, int a3, int a4, int a5, int a6) => ref(f7, a1, a2, a3, a4, a5, a6, 7); Parser> f5(int a1, int a2, int a3, int a4, int a5) => ref(f6, a1, a2, a3, a4, a5, 6); Parser> f4(int a1, int a2, int a3, int a4) => ref(f5, a1, a2, a3, a4, 5); Parser> f3(int a1, int a2, int a3) => ref(f4, a1, a2, a3, 4); Parser> f2(int a1, int a2) => ref(f3, a1, a2, 3); Parser> f1(int a1) => ref(f2, a1, 2); Parser> f0() => ref(f1, 1); Parser> start() => ref(f0); expect( resolve(start()), isParseSuccess('123456789', result: '123456789'.split('')), ); }); test('resolved parser', () { expect(resolve(number()), isParseSuccess('1', result: 1)); expect(resolve(numberList()), isParseSuccess('1,2', result: [1, 2])); }); test('resolved parser with arguments', () { expect(resolve(numberList()), isParseSuccess('1,2', result: [1, 2])); expect( resolve(numberList(';')), isParseSuccess('3;4;5', result: [3, 4, 5]), ); }); test('direct recursion', () { Parser create() => ref0(create); expect(() => resolve(create()), throwsStateError); }); test('reference', () { Parser> list() => [ (ref0(number) & char(',') & ref0(list)).map( (values) => [values[0] as num, ...values[2] as Iterable], ), ref0(number).map((value) => [value]), ].toChoiceParser(); final parser = resolve>(list()); expect(parser, isParseSuccess('1', result: [1])); expect(parser, isParseSuccess('1,2', result: [1, 2])); expect(parser, isParseSuccess('1,2,2', result: [1, 2, 2])); }); }); group('definition', () { final grammarDefinition = ListGrammarDefinition(); final parserDefinition = ListParserDefinition(); final tokenDefinition = TokenizedListGrammarDefinition(); final typedReferenceDefinition = TypedReferencesGrammarDefinition(); final untypedReferenceDefinition = UntypedReferencesGrammarDefinition(); final deprecatedUntypedReferenceDefinition = DeprecatedUntypedReferencesGrammarDefinition(); final buggedDefinition = BuggedGrammarDefinition(); test('reference without parameters', () { final firstReference = ref0(grammarDefinition.start); final secondReference = ref0(grammarDefinition.start); expect(firstReference, isNot(same(secondReference))); expect(firstReference == secondReference, isTrue); }); test('reference with different production', () { final firstReference = ref0(grammarDefinition.start); final secondReference = ref0(grammarDefinition.element); expect(firstReference, isNot(same(secondReference))); expect(firstReference == secondReference, isFalse); }); test('reference with same parameters', () { final firstReference = ref1(typedReferenceDefinition.f1, 42); final secondReference = ref1(typedReferenceDefinition.f1, 42); expect(firstReference, isNot(same(secondReference))); expect(firstReference == secondReference, isTrue); }); test('reference with different parameters', () { final firstReference = ref1(typedReferenceDefinition.f1, 42); final secondReference = ref1(typedReferenceDefinition.f1, 43); expect(firstReference, isNot(same(secondReference))); expect(firstReference == secondReference, isFalse); }); test('reference with multiple arguments', () { final parser = typedReferenceDefinition.build(); expect( parser, isParseSuccess('12345', result: ['1', '2', '3', '4', '5']), ); }); test('reference with multiple arguments (untyped)', () { final parser = untypedReferenceDefinition.build(); expect( parser, isParseSuccess('12345', result: ['1', '2', '3', '4', '5']), ); }); test('reference with multiple arguments (untyped, deprecated)', () { final parser = deprecatedUntypedReferenceDefinition.build(); expect( parser, isParseSuccess('12345', result: ['1', '2', '3', '4', '5']), ); }); test('reference unsupported methods', () { final reference = ref0(grammarDefinition.start); expect(() => reference.copy(), throwsUnsupportedError); expect(() => reference.parse(''), throwsUnsupportedError); }); test('grammar', () { final parser = grammarDefinition.build(); expect(parser, isParseSuccess('1,2', result: ['1', ',', '2'])); expect( parser, isParseSuccess( '1,2,3', result: [ '1', ',', ['2', ',', '3'], ], ), ); }); test('parser', () { final parser = parserDefinition.build(); expect(parser, isParseSuccess('1,2', result: [1, ',', 2])); expect( parser, isParseSuccess( '1,2,3', result: [ 1, ',', [2, ',', 3], ], ), ); }); test('token', () { final parser = tokenDefinition.build(); expect(parser, isParseSuccess('1, 2', result: ['1', ',', '2'])); expect( parser, isParseSuccess( '1, 2, 3', result: [ '1', ',', ['2', ',', '3'], ], ), ); }); test('direct recursion', () { expect( () => buggedDefinition.buildFrom(buggedDefinition.directRecursion1()), throwsStateError, ); }); test('indirect recursion', () { expect( () => buggedDefinition.buildFrom(buggedDefinition.indirectRecursion1()), throwsStateError, ); expect( () => buggedDefinition.buildFrom(buggedDefinition.indirectRecursion2()), throwsStateError, ); expect( () => buggedDefinition.buildFrom(buggedDefinition.indirectRecursion3()), throwsStateError, ); }); test('delegation', () { expect( buggedDefinition.buildFrom(buggedDefinition.delegation1()), isA>(), ); expect( buggedDefinition.buildFrom(buggedDefinition.delegation2()), isA>(), ); expect( buggedDefinition.buildFrom(buggedDefinition.delegation3()), isA>(), ); }); test('lambda example', () { final definition = LambdaGrammarDefinition(); final parser = definition.build(); expect(parser, isParseSuccess('x', result: isNotNull)); expect(parser, isParseSuccess('xy', result: isNotNull)); expect(parser, isParseSuccess('x12', result: isNotNull)); expect(parser, isParseSuccess('\\x.y', result: isNotNull)); expect(parser, isParseSuccess('\\x.\\y.z', result: isNotNull)); expect(parser, isParseSuccess('(x x)', result: isNotNull)); expect(parser, isParseSuccess('(x y)', result: isNotNull)); expect(parser, isParseSuccess('(x (y z))', result: isNotNull)); expect(parser, isParseSuccess('((x y) z)', result: isNotNull)); }); test('expression example', () { final definition = ExpressionGrammarDefinition(); final parser = definition.build(); expect(parser, isParseSuccess('1', result: isNotNull)); expect(parser, isParseSuccess('12', result: isNotNull)); expect(parser, isParseSuccess('1.23', result: isNotNull)); expect(parser, isParseSuccess('-12.3', result: isNotNull)); expect(parser, isParseSuccess('1 + 2', result: isNotNull)); expect(parser, isParseSuccess('1 + 2 + 3', result: isNotNull)); expect(parser, isParseSuccess('1 - 2', result: isNotNull)); expect(parser, isParseSuccess('1 - 2 - 3', result: isNotNull)); expect(parser, isParseSuccess('1 * 2', result: isNotNull)); expect(parser, isParseSuccess('1 * 2 * 3', result: isNotNull)); expect(parser, isParseSuccess('1 / 2', result: isNotNull)); expect(parser, isParseSuccess('1 / 2 / 3', result: isNotNull)); expect(parser, isParseSuccess('1 ^ 2', result: isNotNull)); expect(parser, isParseSuccess('1 ^ 2 ^ 3', result: isNotNull)); expect(parser, isParseSuccess('1 + (2 * 3)', result: isNotNull)); expect(parser, isParseSuccess('(1 + 2) * 3', result: isNotNull)); }); }); }