import 'dart:math'; import 'package:petitparser/petitparser.dart'; import 'package:petitparser/reflection.dart'; import 'package:test/test.dart' hide anyOf; import 'utils/matchers.dart'; typedef Evaluator = num Function(num value); Parser element() => char('(').seq(ref0(content)).seq(char(')')); Parser content() => (ref0(element) | any()).star(); final nestedParser = resolve(ref0(content)).flatten().end(); class ParensGrammar extends GrammarDefinition { @override Parser start() => char('(') & ref0(start) & char(')') | epsilon(); } class NestedGrammar1 { Parser start() => ref0(term).end(); Parser term() => ref0(nestedTerm) | ref0(singleCharacter); Parser nestedTerm() => char('(').map((value) => "'$value' (nestedTerm)") & ref0(term) & char(')').map((value) => "'$value' (nestedTerm)"); Parser singleCharacter() => char('(').map((value) => "'$value' (singleCharacter)") | char(')').map((value) => "'$value' (singleCharacter)") | char('0').map((value) => "'$value' (singleCharacter)"); } class NestedGrammar2 { Parser start() => ref0(term).end(); Parser term() => (ref0(nestedTerm) | ref0(singleCharacter)).plus(); Parser nestedTerm() => char('(').map((value) => "'$value' (nestedTerm)") & ref0(term) & char(')').map((value) => "'$value' (nestedTerm)"); Parser singleCharacter() => char('(').map((value) => "'$value' (singleCharacter)") | char(')').map((value) => "'$value' (singleCharacter)") | char('0').map((value) => "'$value' (singleCharacter)"); } class NestedGrammar3 { Parser start() => ref0(term).end(); Parser term() => (ref0(nestedTerm) | ref0(singleCharacter)).plus(); Parser nestedTerm() => char('(').map((value) => "'$value' (nestedTerm)") & ref0(term) & char(')').map((value) => "'$value' (nestedTerm)"); Parser singleCharacter() => char('(').map((value) => "'$value' (singleCharacter)") | char('0').map((value) => "'$value' (singleCharacter)"); } class ProtobufGrammar extends GrammarDefinition { @override Parser start() => ref0(file); Parser file() => ref0(definition).star() & (endOfInput().trim() | ref0(definition)); Parser definition() => ref0(message); Parser message() => string('message').trim() & ref0(identifier).trim() & char('{').trim() & char('}').trim(); Parser identifier() => (letter() & word().star()).flatten(message: 'identifier expected'); } class AssignmentGrammar extends GrammarDefinition { @override Parser start() => (ref0(assignment) | ref0(expression)).end(); Parser assignment() => ref0(variable) & ref0(indexed).star() & char('=') & ref0(expression); Parser expression() { final expr = ExpressionBuilder(); expr.primitive(ref0(number)); expr.primitive(ref0(variable)); expr.primitive(ref0(literalArray)); expr.group().postfix(ref0(indexed), (a, b) => (a, b)); expr.group().left(char('+'), (a, op, b) => (a, op, b)); return expr.build(); } Parser number() => digit(); Parser variable() => letter(); Parser literalArray() => char('[') & ref0(expression).starSeparated(char(',')) & char(']'); Parser indexed() => char('[') & ref0(expression) & char(']'); } void main() { test('flatten().trim()', () { final parser = word().plus().flatten().trim(); expect(parser, isParseSuccess('ab1', result: 'ab1')); expect(parser, isParseSuccess(' ab1 ', result: 'ab1')); expect(parser, isParseSuccess(' ab1 ', result: 'ab1')); }); test('trim().flatten()', () { final parser = word().plus().trim().flatten(); expect(parser, isParseSuccess('ab1', result: 'ab1')); expect(parser, isParseSuccess(' ab1 ', result: ' ab1 ')); expect(parser, isParseSuccess(' ab1 ', result: ' ab1 ')); }); test('parse padded and limited number', () { final parser = digit().repeat(2).flatten().callCC(( continuation, context, ) { final result = continuation(context); if (result is Success && int.parse(result.value) > 31) { return context.failure('00-31 expected'); } else { return result; } }); expect(parser, isParseSuccess('00', result: '00')); expect(parser, isParseSuccess('24', result: '24')); expect(parser, isParseSuccess('31', result: '31')); expect(parser, isParseFailure('32', message: '00-31 expected')); expect(parser, isParseFailure('3', position: 1, message: 'digit expected')); }); group('date format parser', () { final day = 'dd'.toParser().map( (token) => digit() .repeat(2) .flatten() .map((value) => MapEntry(#day, int.parse(value))), ); final month = 'mm'.toParser().map( (token) => digit() .repeat(2) .flatten() .map((value) => MapEntry(#month, int.parse(value))), ); final year = 'yyyy'.toParser().map( (token) => digit() .repeat(4) .flatten() .map((value) => MapEntry(#year, int.parse(value))), ); final spacing = whitespace().map( (token) => whitespace().star().map((value) => const MapEntry(#unused, 0)), ); final verbatim = any().map( (token) => token.toParser().map((value) => const MapEntry(#unused, 0)), ); final entries = [day, month, year, spacing, verbatim].toChoiceParser(); final format = entries.star().end().map( (parsers) => parsers.toSequenceParser().map((entries) { final arguments = Map.fromEntries(entries); return DateTime( arguments[#year] ?? DateTime.now().year, arguments[#month] ?? DateTime.january, arguments[#day] ?? 1, ); }), ); test('iso', () { final date = format.parse('yyyy-mm-dd').value; expect(date, isParseSuccess('1980-06-11', result: DateTime(1980, 6, 11))); expect(date, isParseSuccess('1982-08-24', result: DateTime(1982, 8, 24))); expect( date, isParseFailure('1984.10.31', position: 4, message: '"-" expected'), ); }); test('europe', () { final date = format.parse('dd.mm.yyyy').value; expect(date, isParseSuccess('11.06.1980', result: DateTime(1980, 6, 11))); expect(date, isParseSuccess('24.08.1982', result: DateTime(1982, 8, 24))); expect( date, isParseFailure('1984', position: 2, message: '"." expected'), ); }); test('us', () { final date = format.parse('mm/dd/yyyy').value; expect(date, isParseSuccess('06/11/1980', result: DateTime(1980, 6, 11))); expect(date, isParseSuccess('08/24/1982', result: DateTime(1982, 8, 24))); expect(date, isParseFailure('Hello', message: 'digit expected')); }); }); test('stackoverflow.com/questions/64670722', () { final delimited = any().callCC((continuation, context) { final delimiter = continuation(context).value.toParser(); final parser = [ delimiter, delimiter.neg().star().flatten(), delimiter, ].toSequenceParser().pick(1); return parser.parseOn(context); }); expect(delimited, isParseSuccess('"hello"', result: 'hello')); expect(delimited, isParseSuccess('/hello/', result: 'hello')); expect(delimited, isParseSuccess(',hello,', result: 'hello')); expect(delimited, isParseSuccess('xhellox', result: 'hello')); expect( delimited, isParseFailure('abc', position: 3, message: '"a" expected'), ); }); test('function evaluator', () { final builder = ExpressionBuilder(); builder ..primitive( digit() .plus() .seq(char('.').seq(digit().plus()).optional()) .flatten() .trim() .map((a) { final number = num.parse(a); return (num value) => number; }), ) ..primitive( char('x').trim().map( (_) => (value) => value, ), ); builder.group().wrapper(char('(').trim(), char(')').trim(), (_, a, _) => a); // negation is a prefix operator builder.group().prefix( char('-').trim(), (_, a) => (num value) => -a(value), ); // power is right-associative builder.group().right( char('^').trim(), (a, _, b) => (num value) => pow(a(value), b(value)), ); // multiplication and addition are left-associative builder.group() ..left( char('*').trim(), (a, _, b) => (num value) => a(value) * b(value), ) ..left( char('/').trim(), (a, _, b) => (num value) => a(value) / b(value), ); builder.group() ..left( char('+').trim(), (a, _, b) => (num value) => a(value) + b(value), ) ..left( char('-').trim(), (a, _, b) => (num value) => a(value) - b(value), ); final parser = builder.build().end(); final expression = parser.parse('5 * x ^ 3 - 2').value; expect(expression(-2), -42); expect(expression(-1), -7); expect(expression(0), -2); expect(expression(1), 3); expect(expression(2), 38); }); test('stackoverflow.com/q/67617000/82303', () { expect(nestedParser, isParseSuccess('()', result: '()')); expect(nestedParser, isParseSuccess('(a)', result: '(a)')); expect(nestedParser, isParseSuccess('(a()b)', result: '(a()b)')); expect(nestedParser, isParseSuccess('(a(b)c)', result: '(a(b)c)')); expect(nestedParser, isParseSuccess('(a()b(cd))', result: '(a()b(cd))')); }); group('github.com/petitparser/dart-petitparser/issues/109', () { // The digit defines how many characters are read by the data parser. Parser buildMetadataParser() => digit().flatten().map(int.parse); Parser buildDataParser(int count) => any().repeat(count).flatten(); const input = '4database'; test('split', () { final metadataParser = buildMetadataParser(); final metadataResult = metadataParser.parse(input); final dataParser = buildDataParser(metadataResult.value); final dataResult = dataParser.parseOn(metadataResult); expect(dataResult.value, 'data'); }); test('continuation', () { final parser = buildMetadataParser().callCC((continuation, context) { final metadataResult = continuation(context); final dataParser = buildDataParser(metadataResult.value); return dataParser.parseOn(metadataResult); }); expect(parser.parse(input).value, 'data'); }); }); group('stackoverflow.com/questions/68105573', () { const firstInput = '(use = "official").empty()'; const secondInput = '((5 + 5) * 5) + 5'; test('greedy', () { final parser = char('(') & any().starGreedy(char(')')).flatten() & char(')'); expect(parser.parse(firstInput).value, [ '(', 'use = "official").empty(', ')', ]); expect(parser.parse(secondInput).value, ['(', '(5 + 5) * 5', ')']); }); test('lazy', () { final parser = char('(') & any().starLazy(char(')')).flatten() & char(')'); expect(parser.parse(firstInput).value, ['(', 'use = "official"', ')']); expect(parser.parse(secondInput).value, ['(', '(5 + 5', ')']); }); test('recursive', () { final inner = undefined(); final parser = char('(') & inner.starLazy(char(')')).flatten() & char(')'); inner.set(parser | any()); expect(parser.parse(firstInput).value, ['(', 'use = "official"', ')']); expect(parser.parse(secondInput).value, ['(', '(5 + 5) * 5', ')']); }); test('recursive (better)', () { final inner = undefined(); final parser = char('(') & inner.star().flatten() & char(')'); inner.set(parser | pattern('^)')); expect(parser.parse(firstInput).value, ['(', 'use = "official"', ')']); expect(parser.parse(secondInput).value, ['(', '(5 + 5) * 5', ')']); }); }); group('github.com/petitparser/dart-petitparser/issues/112', () { final inner = digit().repeat(2); test('original', () { final parser = inner.callCC>((continuation, context) { final result = continuation(context); if (result is Success && result.value[0] != result.value[1]) { return context.failure('values do not match'); } else { return result; } }); expect(parser, isParseSuccess('11', result: ['1', '1'])); expect(parser, isParseSuccess('22', result: ['2', '2'])); expect(parser, isParseSuccess('33', result: ['3', '3'])); expect( parser, isParseFailure('1', position: 1, message: 'digit expected'), ); expect(parser, isParseFailure('12', message: 'values do not match')); expect(parser, isParseFailure('21', message: 'values do not match')); }); test('where', () { final parser = inner.where( (value) => value[0] == value[1], factory: (context, success) => context.failure('values do not match'), ); expect(parser, isParseSuccess('11', result: ['1', '1'])); expect(parser, isParseSuccess('22', result: ['2', '2'])); expect(parser, isParseSuccess('33', result: ['3', '3'])); expect( parser, isParseFailure('1', position: 1, message: 'digit expected'), ); expect(parser, isParseFailure('12', message: 'values do not match')); expect(parser, isParseFailure('21', message: 'values do not match')); }); }); test('github.com/petitparser/dart-petitparser/issues/121', () { final parser = ((letter() | char('_')) & (letter() | digit() | anyOf('_- ()')).star() & char('.').not(message: 'end of id expected')) .flatten(); expect(parser, isParseSuccess('foo', result: 'foo')); expect( parser, isParseFailure('foo.1', message: 'end of id expected', position: 3), ); }); test('github.com/petitparser/dart-petitparser/issues/126', () { final parser = ParensGrammar().build(); expect(parser, isParseSuccess('', result: null)); expect(parser, isParseSuccess('()', result: ['(', null, ')'])); expect( parser, isParseSuccess( '(())', result: [ '(', ['(', null, ')'], ')', ], ), ); expect( parser, isParseSuccess( '((()))', result: [ '(', [ '(', ['(', null, ')'], ')', ], ')', ], ), ); }); group('stackoverflow.com/questions/73260748', () { test('Case 1', () { final parser = resolve(NestedGrammar1().start()); expect( parser, isParseSuccess( '(0)', result: [ "'(' (nestedTerm)", "'0' (singleCharacter)", "')' (nestedTerm)", ], ), ); }); test('Case 2', () { final parser = resolve(NestedGrammar2().start()); expect( parser, isParseSuccess( '(0)', result: [ "'(' (singleCharacter)", "'0' (singleCharacter)", "')' (singleCharacter)", ], ), ); }); test('Case 3', () { final parser = resolve(NestedGrammar3().start()); expect( parser, isParseSuccess( '(0)', result: [ [ "'(' (nestedTerm)", ["'0' (singleCharacter)"], "')' (nestedTerm)", ], ], ), ); }); }); group('stackoverflow.com/questions/75278583', () { final primitive = (uppercase() & char('|') & digit().plus() & char('|') & uppercase()) .flatten() .trim(); final parsers = { 'poster': (() { final inner = undefined(); final paren = char('(').trim() & inner.star() & char(')').trim(); inner.set(paren | pattern('^)')); return inner.end(); })(), 'improved': (() { final outer = undefined(); final inner = undefined(); final operator = string('&&') | string('||'); outer.set(inner.plusSeparated(operator)); final paren = char('(').trim() & outer & char(')').trim(); inner.set(paren | primitive); return outer.end(); })(), 'expression': (() { final builder = ExpressionBuilder(); builder.primitive(primitive); builder.group().wrapper( char('(').trim(), char(')').trim(), (l, v, r) => [l, v, r], ); builder.group() ..left(string('&&').trim(), (a, op, b) => [a, '&&', b]) ..left(string('||').trim(), (a, op, b) => [a, '||', b]); return builder.build().end(); })(), }; final inputs = { 'single': '(S|69|L)', '&&': '(S|69|L && S|69|L)', '||': '(S|69|L || S|69|L)', 'short': '((S|69|L || S|69|L) || S|69|L)', 'long': '(((S|69|L || S|69|R || S|72|L || S|72|R) && ((S|62|L && (S|78|L ' '|| S|55|L) && (S|77|L || S|1|L)) || (S|62|R && (S|78|R || S|55|R) &&' ' (S|77|R || S|1|R)))) && (M|34|L || M|34|R) && (((M|40|L && M|39|L &' '& M|36|L) || (M|40|R && M|39|R && M|36|R)) || ((M|38|L && M|36|L && ' 'M|37|L) || (M|38|R && M|36|R && M|37|R))))', }; for (final input in inputs.entries) { for (final parser in parsers.entries) { test('${parser.key} with ${input.key}', () { expect(parser.value.accept(input.value), isTrue); }); } } }); group('stackoverflow.com/questions/75503464', () { final builder = ExpressionBuilder(); final primitive = seq5( uppercase(), char('|'), digit().plus(), char('|'), uppercase(), ).flatten(message: 'value expected').trim(); builder.primitive(primitive); builder.group().wrapper(char('(').trim(), char(')').trim(), (l, v, r) => v); builder.group() ..left(string('&&').trim(), (a, op, b) => ['&&', a, b]) ..left(string('||').trim(), (a, op, b) => ['||', a, b]); final parser = builder.build().end(); test('success', () { expect(parser, isParseSuccess('S|69|L', result: 'S|69|L')); expect(parser, isParseSuccess('(S|69|L)', result: 'S|69|L')); expect( parser, isParseSuccess('S|69|L && S|69|R', result: ['&&', 'S|69|L', 'S|69|R']), ); expect( parser, isParseSuccess('S|69|L || S|69|R', result: ['||', 'S|69|L', 'S|69|R']), ); }); test('value error', () { expect( parser, isParseFailure('S|fail|L', position: 0, message: 'value expected'), ); expect( parser, isParseFailure('(S|fail|L)', position: 0, message: 'value expected'), ); expect( parser, isParseFailure( 'S|69|L && S|fail|R', position: 7, message: 'end of input expected', ), ); expect( parser, isParseFailure( 'S|69|L || S|fail|R', position: 7, message: 'end of input expected', ), ); }); test('other error', () { expect( parser, isParseFailure('(S|69|L', position: 0, message: 'value expected'), ); expect( parser, isParseFailure( 'S|69|L &', position: 7, message: 'end of input expected', ), ); }); }); group('github.com/petitparser/dart-petitparser/issues/145', () { test('solution 1', () { final parser = seq3( char('*'), seq2( whitespace().not(), [ seq2(whitespace(), char('*')), char('*'), ].toChoiceParser().neg().plus(), ).flatten(), char('*'), ).map3((_, value, _) => value); expect(parser.parse('*valid*').value, 'valid'); expect(parser.accept('* invalid*'), isFalse); expect(parser.accept('*invalid *'), isFalse); }); test('solution 2', () { final parser = seq3( char('*'), char('*').neg().plus().flatten(), char('*'), ).map3((_, value, _) => value).where((value) => value.trim() == value); expect(parser.parse('*valid*').value, 'valid'); expect(parser.accept('* invalid*'), isFalse); expect(parser.accept('*invalid *'), isFalse); }); }); group('github.com/petitparser/dart-petitparser/issues/147', () { final surrogatePair = seq2( pattern('\uD800-\uDBFF'), pattern('\uDC00-\uDFFF'), ).flatten(); test('laughing emoji', () { const input = '\u{1f606}'; expect(input, hasLength(2)); expect(surrogatePair, isParseSuccess(input, result: '😆')); }); test('heart character', () { const input = '\u2665'; expect(input, hasLength(1)); expect(surrogatePair, isParseFailure(input)); }); }); group('github.com/petitparser/dart-petitparser/issues/155', () { Parser chars() => anyOf('abc'); Parser direct() => chars().starString().end(); Parser reference() => ref0(chars).starString().end(); test('direct', () { final parser = resolve(direct()); expect(linter(parser), isEmpty); }); test('reference', () { final parser = resolve(reference()); expect(linter(parser), [isLinterIssue(title: 'Character repeater')]); }); test('reference (optimized)', () { final parser = optimize(resolve(reference())); expect(linter(parser), isEmpty); expect(parser.isEqualTo(direct()), isTrue); }); }); test('github.com/petitparser/dart-petitparser/issues/158', () { final extended = pattern('À-ÿ'); expect(extended, isParseSuccess('ä', result: 'ä')); expect(extended, isParseSuccess('ï', result: 'ï')); }); group('github.com/petitparser/dart-petitparser/issues/162', () { final uppercase = char('A').plus().map((_) => 'success'); final anycase = char('A', ignoreCase: true).plus().map((_) => 'fallback'); test('question', () { final parser = [uppercase, anycase].toChoiceParser().end(); expect(parser, isParseSuccess('AAAA', result: 'success')); expect(parser, isParseSuccess('aaaAAaaa', result: 'fallback')); expect( parser, isParseFailure('AAaaAA', position: 2, message: 'end of input expected'), ); }); test('possible fix', () { final parser = [uppercase.end(), anycase.end()].toChoiceParser(); expect(parser, isParseSuccess('AAAA', result: 'success')); expect(parser, isParseSuccess('aaaAAaaa', result: 'fallback')); expect(parser, isParseSuccess('AAaaAA', result: 'fallback')); }); test('more general', () { final parser = [ uppercase.skip(after: anycase.not()), anycase, ].toChoiceParser().end(); expect(parser, isParseSuccess('AAAA', result: 'success')); expect(parser, isParseSuccess('aaaAAaaa', result: 'fallback')); expect(parser, isParseSuccess('AAaaAA', result: 'fallback')); }); }); group('https://stackoverflow.com/questions/78078779', () { test('How to consume only as long as another parser accepts?', () { final parser = digit().plusLazy(digit().repeat(3).not()); expect(parser, isParseSuccess('123', result: ['1'], position: 1)); expect(parser, isParseSuccess('1234', result: ['1', '2'], position: 2)); }); test('How to recognize a list of items with optional delimiters?', () { final parser = digit().plusSeparated(char(',').optional()); expect( parser, isParseSuccess( '1,2,3', result: isSeparatedList( elements: ['1', '2', '3'], separators: [',', ','], ), ), ); expect( parser, isParseSuccess( '12,3', result: isSeparatedList( elements: ['1', '2', '3'], separators: [null, ','], ), ), ); }); }); group('https://stackoverflow.com/questions/78701485', () { final wholeNumber = digit().plus().trim(); final number = wholeNumber.plus() & char('.').optional() & wholeNumber; test('original', () { final parser = char('(') & number & char(')'); expect(parser, isParseSuccess('(0.53)')); expect( parser, isParseFailure('(0.53,00)', position: 5, message: '")" expected'), ); }); test('modified', () { final parser = char('(') & number & char(',').not(message: 'remove comma') & char(')'); expect(parser, isParseSuccess('(0.53)')); expect( parser, isParseFailure('(0.53,00)', position: 5, message: 'remove comma'), ); }); }); group('github.com/petitparser/dart-petitparser/discussions/177', () { test('continuation', () { final variables = ['first', 'second']; final parser = failure().callCC( (continuation, context) => variables .map((each) => each.toParser()) .toChoiceParser() .parseOn(context), ); expect(parser, isParseSuccess('first')); expect(parser, isParseSuccess('second')); expect(parser, isParseFailure('third')); variables.add('third'); expect(parser, isParseSuccess('third')); }); }); group('github.com/petitparser/dart-petitparser/issues/80', () { final surrogatePair = seq2( pattern('\uD800-\uDBFF'), pattern('\uDC00-\uDFFF'), ); final decodedSurrogatePair = surrogatePair.map2( (hi, lo) => 0x400 * (hi.codeUnitAt(0) - 0xD800) + (lo.codeUnitAt(0) - 0xDC00) + 0x10000, ); test('en.wikipedia.org/wiki/UTF-16#Examples', () { expect( decodedSurrogatePair, isParseSuccess('\u{10437}', result: 0x10437), ); expect( decodedSurrogatePair, isParseSuccess('\u{24B62}', result: 0x24B62), ); }); test('#issuecomment-2510905396', () { final parser = decodedSurrogatePair.where( (value) => 0x20000 <= value && value <= 0x2FFFF, ); expect(parser, isParseSuccess('\u{20000}', result: 0x20000)); expect(parser, isParseSuccess('\u{2abcd}', result: 0x2abcd)); expect(parser, isParseSuccess('\u{2FFFF}', result: 0x2FFFF)); }); }); group('github.com/petitparser/dart-petitparser/issues/184', () { final parser = ProtobufGrammar().build(); test('valid', () { expect(parser.parse(' '), isSuccess()); expect(parser.parse('message foo {} '), isSuccess()); expect(parser.parse('message foo {} message bar {} '), isSuccess()); }); test('invalid', () { expect( parser.parse('message '), isFailure(message: 'identifier expected', position: 8), ); expect( parser.parse('message 123'), isFailure(message: 'identifier expected', position: 8), ); expect( parser.parse('message Empty'), isFailure(message: '"{" expected', position: 13), ); expect( parser.parse('message Empty {'), isFailure(message: '"}" expected', position: 15), ); }); }); group('github.com/petitparser/dart-petitparser/issues/191', () { test('simple', () { final zero = '0'.toParser(); final one = '1'.toParser(); final parser = [seq2(zero, one), one].toChoiceParser().star().flatten(); expect(parser, isParseSuccess('1', result: '1')); expect(parser, isParseSuccess('01', result: '01')); expect(parser, isParseSuccess('11', result: '11')); expect(parser, isParseSuccess('011', result: '011')); expect(parser, isParseSuccess('0101', result: '0101')); }); test('general', () { final zero = '0'.toParser(); final one = '1'.toParser(); final parser = [zero, one] .toChoiceParser() .callCC((continuation, context) { // Parse the original choice parser (zero or one). final result = continuation(context); // If this is a success and the result is '0', create a new parser // and follow up with it after the original choice parser. if (result case Success(value: '0')) { return '1'.toParser().parseOn(result); } // Otherwise return the result of the original choice parser. return result; }) .star() .flatten(); expect(parser, isParseSuccess('1', result: '1')); expect(parser, isParseSuccess('01', result: '01')); expect(parser, isParseSuccess('11', result: '11')); expect(parser, isParseSuccess('011', result: '011')); expect(parser, isParseSuccess('0101', result: '0101')); }); }); group('github.com/petitparser/dart-petitparser/issues/193', () { final parser = AssignmentGrammar().build(); test('number', () { expect(parser, isParseSuccess('1')); }); test('variable', () { expect(parser, isParseSuccess('a')); expect(parser, isParseSuccess('a[1]')); expect(parser, isParseSuccess('a[1+2]')); expect(parser, isParseSuccess('a[1][2]')); }); test('array', () { expect(parser, isParseSuccess('[]')); expect(parser, isParseSuccess('[1]')); expect(parser, isParseSuccess('[1,2]')); }); test('addition', () { expect(parser, isParseSuccess('1+2')); expect(parser, isParseSuccess('1+2+3')); }); test('assignment', () { expect(parser, isParseSuccess('a=1')); expect(parser, isParseSuccess('a=2+3')); expect(parser, isParseSuccess('a[1]=2')); expect(parser, isParseSuccess('a[1][2+3]=4')); expect(parser, isParseSuccess('a[1]=[2+3][4]')); }); test('examples', () { expect(parser, isParseSuccess('a[0]')); expect(parser, isParseSuccess('a[0][0]')); expect(parser, isParseSuccess('[1,2,3][0]+[[1,2],1][0][0]')); }); }); }