| suite('parser', function() { | |
| var string = Parser.string; | |
| var regex = Parser.regex; | |
| var letter = Parser.letter; | |
| var digit = Parser.digit; | |
| var any = Parser.any; | |
| var optWhitespace = Parser.optWhitespace; | |
| var eof = Parser.eof; | |
| var succeed = Parser.succeed; | |
| var all = Parser.all; | |
| test('Parser.string', function() { | |
| var parser = string('x'); | |
| assert.equal(parser.parse('x'), 'x'); | |
| assert.throws(function() { parser.parse('y') }) | |
| }); | |
| test('Parser.regex', function() { | |
| var parser = regex(/^[0-9]/); | |
| assert.equal(parser.parse('1'), '1'); | |
| assert.equal(parser.parse('4'), '4'); | |
| assert.throws(function() { parser.parse('x'); }); | |
| assert.throws(function() { regex(/./) }, 'must be anchored'); | |
| }); | |
| suite('then', function() { | |
| test('with a parser, uses the last return value', function() { | |
| var parser = string('x').then(string('y')); | |
| assert.equal(parser.parse('xy'), 'y'); | |
| assert.throws(function() { parser.parse('y'); }); | |
| assert.throws(function() { parser.parse('xz'); }); | |
| }); | |
| test('asserts that a parser is returned', function() { | |
| var parser1 = letter.then(function() { return 'not a parser' }); | |
| assert.throws(function() { parser1.parse('x'); }); | |
| var parser2 = letter.then('x'); | |
| assert.throws(function() { letter.parse('xx'); }); | |
| }); | |
| test('with a function that returns a parser, continues with that parser', function() { | |
| var piped; | |
| var parser = string('x').then(function(x) { | |
| piped = x; | |
| return string('y'); | |
| }); | |
| assert.equal(parser.parse('xy'), 'y'); | |
| assert.equal(piped, 'x'); | |
| assert.throws(function() { parser.parse('x'); }); | |
| }); | |
| }); | |
| suite('map', function() { | |
| test('with a function, pipes the value in and uses that return value', function() { | |
| var piped; | |
| var parser = string('x').map(function(x) { | |
| piped = x; | |
| return 'y'; | |
| }); | |
| assert.equal(parser.parse('x'), 'y') | |
| assert.equal(piped, 'x'); | |
| }); | |
| }); | |
| suite('result', function() { | |
| test('returns a constant result', function() { | |
| var myResult = 1; | |
| var oneParser = string('x').result(1); | |
| assert.equal(oneParser.parse('x'), 1); | |
| var myFn = function() {}; | |
| var fnParser = string('x').result(myFn); | |
| assert.equal(fnParser.parse('x'), myFn); | |
| }); | |
| }); | |
| suite('skip', function() { | |
| test('uses the previous return value', function() { | |
| var parser = string('x').skip(string('y')); | |
| assert.equal(parser.parse('xy'), 'x'); | |
| assert.throws(function() { parser.parse('x'); }); | |
| }); | |
| }); | |
| suite('or', function() { | |
| test('two parsers', function() { | |
| var parser = string('x').or(string('y')); | |
| assert.equal(parser.parse('x'), 'x'); | |
| assert.equal(parser.parse('y'), 'y'); | |
| assert.throws(function() { parser.parse('z') }); | |
| }); | |
| test('with then', function() { | |
| var parser = string('\\') | |
| .then(function() { | |
| return string('y') | |
| }).or(string('z')); | |
| assert.equal(parser.parse('\\y'), 'y'); | |
| assert.equal(parser.parse('z'), 'z'); | |
| assert.throws(function() { parser.parse('\\z') }); | |
| }); | |
| }); | |
| function assertEqualArray(arr1, arr2) { | |
| assert.equal(arr1.join(), arr2.join()); | |
| } | |
| suite('many', function() { | |
| test('simple case', function() { | |
| var letters = letter.many(); | |
| assertEqualArray(letters.parse('x'), ['x']); | |
| assertEqualArray(letters.parse('xyz'), ['x','y','z']); | |
| assertEqualArray(letters.parse(''), []); | |
| assert.throws(function() { letters.parse('1'); }); | |
| assert.throws(function() { letters.parse('xyz1'); }); | |
| }); | |
| test('followed by then', function() { | |
| var parser = string('x').many().then(string('y')); | |
| assert.equal(parser.parse('y'), 'y'); | |
| assert.equal(parser.parse('xy'), 'y'); | |
| assert.equal(parser.parse('xxxxxy'), 'y'); | |
| }); | |
| }); | |
| suite('times', function() { | |
| test('zero case', function() { | |
| var zeroLetters = letter.times(0); | |
| assertEqualArray(zeroLetters.parse(''), []); | |
| assert.throws(function() { zeroLetters.parse('x'); }); | |
| }); | |
| test('nonzero case', function() { | |
| var threeLetters = letter.times(3); | |
| assertEqualArray(threeLetters.parse('xyz'), ['x', 'y', 'z']); | |
| assert.throws(function() { threeLetters.parse('xy'); }); | |
| assert.throws(function() { threeLetters.parse('xyzw'); }); | |
| var thenDigit = threeLetters.then(digit); | |
| assert.equal(thenDigit.parse('xyz1'), '1'); | |
| assert.throws(function() { thenDigit.parse('xy1'); }); | |
| assert.throws(function() { thenDigit.parse('xyz'); }); | |
| assert.throws(function() { thenDigit.parse('xyzw'); }); | |
| }); | |
| test('with a min and max', function() { | |
| var someLetters = letter.times(2, 4); | |
| assertEqualArray(someLetters.parse('xy'), ['x', 'y']); | |
| assertEqualArray(someLetters.parse('xyz'), ['x', 'y', 'z']); | |
| assertEqualArray(someLetters.parse('xyzw'), ['x', 'y', 'z', 'w']); | |
| assert.throws(function() { someLetters.parse('xyzwv'); }); | |
| assert.throws(function() { someLetters.parse('x'); }); | |
| var thenDigit = someLetters.then(digit); | |
| assert.equal(thenDigit.parse('xy1'), '1'); | |
| assert.equal(thenDigit.parse('xyz1'), '1'); | |
| assert.equal(thenDigit.parse('xyzw1'), '1'); | |
| assert.throws(function() { thenDigit.parse('xy'); }); | |
| assert.throws(function() { thenDigit.parse('xyzw'); }); | |
| assert.throws(function() { thenDigit.parse('xyzwv1'); }); | |
| assert.throws(function() { thenDigit.parse('x1'); }); | |
| }); | |
| test('atLeast', function() { | |
| var atLeastTwo = letter.atLeast(2); | |
| assertEqualArray(atLeastTwo.parse('xy'), ['x', 'y']); | |
| assertEqualArray(atLeastTwo.parse('xyzw'), ['x', 'y', 'z', 'w']); | |
| assert.throws(function() { atLeastTwo.parse('x'); }); | |
| }); | |
| }); | |
| suite('fail', function() { | |
| var fail = Parser.fail; | |
| var succeed = Parser.succeed; | |
| test('use Parser.fail to fail dynamically', function() { | |
| var parser = any.then(function(ch) { | |
| return fail('character '+ch+' not allowed'); | |
| }).or(string('x')); | |
| assert.throws(function() { parser.parse('y'); }); | |
| assert.equal(parser.parse('x'), 'x'); | |
| }); | |
| test('use Parser.succeed or Parser.fail to branch conditionally', function() { | |
| var allowedOperator; | |
| var parser = | |
| string('x') | |
| .then(string('+').or(string('*'))) | |
| .then(function(operator) { | |
| if (operator === allowedOperator) return succeed(operator); | |
| else return fail('expected '+allowedOperator); | |
| }) | |
| .skip(string('y')) | |
| ; | |
| allowedOperator = '+'; | |
| assert.equal(parser.parse('x+y'), '+'); | |
| assert.throws(function() { parser.parse('x*y'); }); | |
| allowedOperator = '*'; | |
| assert.equal(parser.parse('x*y'), '*'); | |
| assert.throws(function() { parser.parse('x+y'); }); | |
| }); | |
| }); | |
| test('eof', function() { | |
| var parser = optWhitespace.skip(eof).or(all.result('default')); | |
| assert.equal(parser.parse(' '), ' ') | |
| assert.equal(parser.parse('x'), 'default'); | |
| }); | |
| }); |