import 'package:petitparser/petitparser.dart'; import 'package:test/test.dart' hide anyOf; import 'utils/assertions.dart'; import 'utils/matchers.dart'; void main() { group('cast', () { expectParserInvariants(any().cast()); test('default', () { final parser = digit().map(int.parse).cast(); expect(parser, isParseSuccess('1', result: 1)); expect(parser, isParseFailure('a', message: 'digit expected')); }); }); group('castList', () { expectParserInvariants(any().star().castList()); test('default', () { final parser = digit().map(int.parse).repeat(3).castList(); expect(parser, isParseSuccess('123', result: [1, 2, 3])); expect( parser, isParseFailure('abc', position: 0, message: 'digit expected'), ); }); }); group('continuation', () { expectParserInvariants( any().callCC((continuation, context) => continuation(context)), ); test('delegation', () { final parser = digit().callCC( (continuation, context) => continuation(context), ); expect(parser, isParseSuccess('1', result: '1')); expect(parser, isParseFailure('a', message: 'digit expected')); }); test('diversion', () { final parser = digit().callCC( (continuation, context) => letter().parseOn(context), ); expect(parser, isParseSuccess('a', result: 'a')); expect(parser, isParseFailure('1', message: 'letter expected')); }); test('resume', () { final continuations = >[]; final contexts = []; final parser = digit().callCC((continuation, context) { continuations.add(continuation); contexts.add(context); // we have to return something for now return context.failure('Abort'); }); // execute the parser twice to collect the continuations expect(parser.parse('1') is Success, isFalse); expect(parser.parse('a') is Success, isFalse); // later we can execute the captured continuations expect(continuations[0](contexts[0]) is Success, isTrue); expect(continuations[1](contexts[1]) is Success, isFalse); // of course the continuations can be resumed multiple times expect(continuations[0](contexts[0]) is Success, isTrue); expect(continuations[1](contexts[1]) is Success, isFalse); }); test('success', () { final parser = digit().callCC( (continuation, context) => context.success('success'), ); expect(parser, isParseSuccess('1', result: 'success', position: 0)); expect(parser, isParseSuccess('a', result: 'success', position: 0)); }); test('failure', () { final parser = digit().callCC( (continuation, context) => context.failure('failure'), ); expect(parser, isParseFailure('1', message: 'failure')); expect(parser, isParseFailure('a', message: 'failure')); }); }); group('flatten', () { expectParserInvariants(any().flatten()); test('default', () { final parser = digit().repeat(2, unbounded).flatten(); expect(parser, isParseFailure('', message: 'digit expected')); expect(parser, isParseFailure('a', message: 'digit expected')); expect( parser, isParseFailure('1', position: 1, message: 'digit expected'), ); expect( parser, isParseFailure('1a', position: 1, message: 'digit expected'), ); expect(parser, isParseSuccess('12', result: '12')); expect(parser, isParseSuccess('123', result: '123')); expect(parser, isParseSuccess('1234', result: '1234')); }); test('with message', () { final parser = digit() .repeat(2, unbounded) .flatten(message: 'gimme a number'); expect(parser, isParseFailure('', message: 'gimme a number')); expect(parser, isParseFailure('a', message: 'gimme a number')); expect(parser, isParseFailure('1', message: 'gimme a number')); expect(parser, isParseFailure('1a', message: 'gimme a number')); expect(parser, isParseSuccess('12', result: '12')); expect(parser, isParseSuccess('123', result: '123')); expect(parser, isParseSuccess('1234', result: '1234')); }); test('nested', () { final parser = digit() .star() .flatten() .plusSeparated(char(',')) .flatten(); expect(parser, isParseSuccess('1', result: '1')); expect(parser, isParseSuccess('1,12', result: '1,12')); expect(parser, isParseSuccess('1,12,123', result: '1,12,123')); }); }); group('map', () { expectParserInvariants(any().map((a) => a)); test('default', () { final parser = digit().map( (each) => each.codeUnitAt(0) - '0'.codeUnitAt(0), ); expect(parser, isParseSuccess('1', result: 1)); expect(parser, isParseSuccess('4', result: 4)); expect(parser, isParseSuccess('9', result: 9)); expect(parser, isParseFailure('', message: 'digit expected')); expect(parser, isParseFailure('a', message: 'digit expected')); }); test('without side-effects', () { final effects = []; final parser = digit().map(effects.add, hasSideEffects: false); expect(parser.fastParseOn('1', 0), 1); expect(effects, isEmpty); }); test('with side-effects', () { final effects = []; final parser = digit().map(effects.add, hasSideEffects: true); expect(parser.fastParseOn('1', 0), 1); expect(effects, ['1']); }); }); group('permute', () { expectParserInvariants(any().star().permute([-1, 1])); test('from start', () { final parser = digit().seq(letter()).permute([1, 0]); expect(parser, isParseSuccess('1a', result: ['a', '1'])); expect(parser, isParseSuccess('2b', result: ['b', '2'])); expect(parser, isParseFailure('', message: 'digit expected')); expect( parser, isParseFailure('1', position: 1, message: 'letter expected'), ); expect( parser, isParseFailure('12', position: 1, message: 'letter expected'), ); }); test('from end', () { final parser = digit().seq(letter()).permute([-1, 0]); expect(parser, isParseSuccess('1a', result: ['a', '1'])); expect(parser, isParseSuccess('2b', result: ['b', '2'])); expect(parser, isParseFailure('', message: 'digit expected')); expect( parser, isParseFailure('1', position: 1, message: 'letter expected'), ); expect( parser, isParseFailure('12', position: 1, message: 'letter expected'), ); }); test('repeated', () { final parser = digit().seq(letter()).permute([1, 1]); expect(parser, isParseSuccess('1a', result: ['a', 'a'])); expect(parser, isParseSuccess('2b', result: ['b', 'b'])); expect(parser, isParseFailure('', message: 'digit expected')); expect( parser, isParseFailure('1', position: 1, message: 'letter expected'), ); expect( parser, isParseFailure('12', position: 1, message: 'letter expected'), ); }); }); group('pick', () { expectParserInvariants(any().star().pick(-1)); test('from start', () { final parser = digit().seq(letter()).pick(1); expect(parser, isParseSuccess('1a', result: 'a')); expect(parser, isParseSuccess('2b', result: 'b')); expect(parser, isParseFailure('', message: 'digit expected')); expect( parser, isParseFailure('1', position: 1, message: 'letter expected'), ); expect( parser, isParseFailure('12', position: 1, message: 'letter expected'), ); }); test('from end', () { final parser = digit().seq(letter()).pick(-1); expect(parser, isParseSuccess('1a', result: 'a')); expect(parser, isParseSuccess('2b', result: 'b')); expect(parser, isParseFailure('', message: 'digit expected')); expect( parser, isParseFailure('1', position: 1, message: 'letter expected'), ); expect( parser, isParseFailure('12', position: 1, message: 'letter expected'), ); }); }); group('token', () { expectParserInvariants(any().token()); test('default', () { final parser = digit().plus().token(); expect(parser, isParseFailure('', message: 'digit expected')); expect(parser, isParseFailure('a', message: 'digit expected')); final token = parser.parse('123').value; expect(token.value, ['1', '2', '3']); expect(token.buffer, '123'); expect(token.start, 0); expect(token.stop, 3); expect(token.input, '123'); expect(token.length, 3); expect(token.line, 1); expect(token.column, 1); expect(token.toString(), isNot(startsWith('Instance of'))); expect( token.toString(), stringContainsInOrder(['Token', '[1:1]: [1, 2, 3]']), ); }); const buffer = '1\r12\r\n123\n1234'; final parser = any().map((value) => value.codeUnitAt(0)).token().star(); final result = parser.parse(buffer).value; test('value', () { final expected = [49, 13, 49, 50, 13, 10, 49, 50, 51, 10, 49, 50, 51, 52]; expect(result.map((token) => token.value), expected); }); test('buffer', () { final expected = List.filled(buffer.length, buffer); expect(result.map((token) => token.buffer), expected); }); test('start', () { final expected = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; expect(result.map((token) => token.start), expected); }); test('stop', () { final expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; expect(result.map((token) => token.stop), expected); }); test('length', () { final expected = List.filled(buffer.length, 1); expect(result.map((token) => token.length), expected); }); test('line', () { final expected = [1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4]; expect(result.map((token) => token.line), expected); }); test('column', () { final expected = [1, 2, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]; expect(result.map((token) => token.column), expected); }); test('input', () { final expected = [ '1', '\r', '1', '2', '\r', '\n', '1', '2', '3', '\n', '1', '2', '3', '4', ]; expect(result.map((token) => token.input), expected); }); group('join', () { test('normal', () { final joined = Token.join(result); expect( joined, isA>>() .having((token) => token.value, 'value', [ 49, 13, 49, 50, 13, 10, 49, 50, 51, 10, 49, 50, 51, 52, ]) .having((token) => token.buffer, 'buffer', buffer) .having((token) => token.start, 'start', 0) .having((token) => token.stop, 'stop', buffer.length), ); }); test('reverse order', () { final joined = Token.join(result.reversed); expect( joined, isA>>() .having((token) => token.value, 'value', [ 52, 51, 50, 49, 10, 51, 50, 49, 10, 13, 50, 49, 13, 49, ]) .having((token) => token.buffer, 'buffer', buffer) .having((token) => token.start, 'start', 0) .having((token) => token.stop, 'stop', buffer.length), ); }); test('empty', () { expect(() => Token.join([]), throwsArgumentError); }); test('different buffer', () { const token = [Token(12, '12', 0, 2), Token(32, '32', 0, 2)]; expect(() => Token.join(token), throwsArgumentError); }); }); test('unique', () { expect({...result}.length, result.length); }); test('equals', () { for (var i = 0; i < result.length; i++) { for (var j = 0; j < result.length; j++) { final condition = i == j ? isTrue : isFalse; expect(result[i] == result[j], condition); expect(result[i].hashCode == result[j].hashCode, condition); } } }); }); group('trim', () { expectParserInvariants(any().trim(char('a'), char('b'))); test('default', () { final parser = char('a').trim(); expect(parser, isParseSuccess('a', result: 'a')); expect(parser, isParseSuccess(' a', result: 'a')); expect(parser, isParseSuccess('a ', result: 'a')); expect(parser, isParseSuccess(' a ', result: 'a')); expect(parser, isParseSuccess(' a', result: 'a')); expect(parser, isParseSuccess('a ', result: 'a')); expect(parser, isParseSuccess(' a ', result: 'a')); expect(parser, isParseFailure('', message: '"a" expected')); expect(parser, isParseFailure('b', message: '"a" expected')); expect( parser, isParseFailure(' b', position: 1, message: '"a" expected'), ); expect( parser, isParseFailure(' b', position: 2, message: '"a" expected'), ); }); test('custom both', () { final parser = char('a').trim(char('*')); expect(parser, isParseSuccess('a', result: 'a')); expect(parser, isParseSuccess('*a', result: 'a')); expect(parser, isParseSuccess('a*', result: 'a')); expect(parser, isParseSuccess('*a*', result: 'a')); expect(parser, isParseSuccess('**a', result: 'a')); expect(parser, isParseSuccess('a**', result: 'a')); expect(parser, isParseSuccess('**a**', result: 'a')); expect(parser, isParseFailure('', message: '"a" expected')); expect(parser, isParseFailure('b', message: '"a" expected')); expect( parser, isParseFailure('*b', position: 1, message: '"a" expected'), ); expect( parser, isParseFailure('**b', position: 2, message: '"a" expected'), ); }); test('custom left and right', () { final parser = char('a').trim(char('*'), char('#')); expect(parser, isParseSuccess('a', result: 'a')); expect(parser, isParseSuccess('*a', result: 'a')); expect(parser, isParseSuccess('a#', result: 'a')); expect(parser, isParseSuccess('*a#', result: 'a')); expect(parser, isParseSuccess('**a', result: 'a')); expect(parser, isParseSuccess('a##', result: 'a')); expect(parser, isParseSuccess('**a##', result: 'a')); expect(parser, isParseFailure('', message: '"a" expected')); expect(parser, isParseFailure('b', message: '"a" expected')); expect( parser, isParseFailure('*b', position: 1, message: '"a" expected'), ); expect( parser, isParseFailure('**b', position: 2, message: '"a" expected'), ); expect( parser, isParseFailure('#a', position: 0, message: '"a" expected'), ); expect(parser, isParseSuccess('a*', result: 'a', position: 1)); }); }); group('where', () { expectParserInvariants(any().where((value) => true)); test('default', () { final parser = any().where((value) => value == '*'); expect(parser, isParseSuccess('*', result: '*')); expect(parser, isParseFailure('', message: 'input expected')); expect(parser, isParseFailure('!', message: 'unexpected "!"')); }); test('with message', () { final parser = any().where( (value) => value == '*', message: 'star expected', ); expect(parser, isParseSuccess('*', result: '*')); expect(parser, isParseFailure('', message: 'input expected')); expect(parser, isParseFailure('!', message: 'star expected')); }); test('with factory', () { final parser = digit() .plus() .flatten() .map(int.parse) .where( (value) => value % 7 == 0, factory: (context, success) => context.failure('${success.value} is not divisible by 7'), ); expect(parser, isParseSuccess('7', result: 7)); expect(parser, isParseSuccess('14', result: 14)); expect(parser, isParseSuccess('861', result: 861)); expect(parser, isParseFailure('', message: 'digit expected')); expect( parser, isParseFailure('865', message: '865 is not divisible by 7'), ); }); }); }