Comparing version
{ | ||
"name": "parsimmon", | ||
"version": "0.0.6", | ||
"version": "0.1.0", | ||
"description": "A monadic LL(infinity) parser combinator library", | ||
@@ -12,3 +12,4 @@ "keywords": ["parsing", "parse", "parser combinators"], | ||
"devDependencies": { | ||
"mocha": "*", | ||
"mocha": "1.8.x", | ||
"chai": "1.5.x", | ||
"uglify-js": "2.x" | ||
@@ -15,0 +16,0 @@ }, |
@@ -105,2 +105,3 @@ [](http://travis-ci.org/jayferd/parsimmon) | ||
- `Parsimmon.eof` expects the end of the stream. | ||
- `Parsimmon.index` is a parser that yields the current index of the parse. | ||
@@ -135,3 +136,13 @@ ### Parser methods | ||
expects `parser` at least `n` times. Yields an array of the results. | ||
- `parser.mark()` yields an object with `start`, `value`, and `end` keys, where | ||
`value` is the original value yielded by the parser, and `start` and `end` are | ||
the indices in the stream that contain the parsed text. | ||
### Fantasyland | ||
[fantasyland]: https://github.com/fantasyland/fantasy-land "Fantasyland" | ||
[fantasyland-logo]: https://github.com/fantasyland/fantasy-land/raw/master/logo.png | ||
![][fantasyland-logo] | ||
Parsimmon is also compatible with [fantasyland][]. It is a Semigroup, an Applicative Functor and a Monad. |
@@ -12,3 +12,39 @@ var Parsimmon = {}; | ||
function parseError(stream, i, expected) { | ||
function makeSuccess(index, value) { | ||
return { | ||
status: true, | ||
index: index, | ||
value: value, | ||
furthest: -1, | ||
expected: '' | ||
}; | ||
} | ||
function makeFailure(index, expected) { | ||
return { | ||
status: false, | ||
index: -1, | ||
value: null, | ||
furthest: index, | ||
expected: expected | ||
}; | ||
} | ||
function furthestBacktrackFor(result, last) { | ||
if (!last) return result; | ||
if (result.furthest >= last.furthest) return result; | ||
return { | ||
status: result.status, | ||
index: result.index, | ||
value: result.value, | ||
furthest: last.furthest, | ||
expected: last.expected | ||
} | ||
} | ||
function parseError(stream, result) { | ||
var expected = result.expected; | ||
var i = result.furthest; | ||
if (i === stream.length) { | ||
@@ -29,23 +65,7 @@ var message = 'expected ' + expected + ', got the end of the string'; | ||
_.parse = function(stream) { | ||
return this.skip(eof)._(stream, 0, success, parseError); | ||
var result = this.skip(eof)._(stream, 0); | ||
function success(stream, i, result) { return result; } | ||
return result.status ? result.value : parseError(stream, result); | ||
}; | ||
function furthestFailure(onFailure, myI, myExpected) { | ||
return function(stream, yourI, yourExpected) { | ||
if (myI > yourI) return onFailure(stream, myI, myExpected); | ||
else return onFailure.apply(this, arguments); | ||
}; | ||
} | ||
function furthestFailureSuccess(onSuccess, myFurthestFailureI, myFurthestExpected) { | ||
return function(stream, i, result, yourFurthestFailureI, yourFurthestExpected) { | ||
if (myFurthestFailureI > yourFurthestFailureI) { | ||
return onSuccess(stream, i, result, myFurthestFailureI, myFurthestExpected); | ||
} | ||
else return onSuccess.apply(this, arguments); | ||
}; | ||
} | ||
// -*- primitive combinators -*- // | ||
@@ -55,10 +75,6 @@ _.or = function(alternative) { | ||
return Parser(function(stream, i, onSuccess, onFailure) { | ||
return self._(stream, i, onSuccess, failure); | ||
return Parser(function(stream, i) { | ||
var result = self._(stream, i); | ||
function failure(stream, newI, expected) { | ||
var altSuccess = furthestFailureSuccess(onSuccess, newI, expected); | ||
var altFailure = furthestFailure(onFailure, newI, expected); | ||
return alternative._(stream, i, altSuccess, altFailure); | ||
} | ||
return result.status ? result : furthestBacktrackFor(alternative._(stream, i), result); | ||
}); | ||
@@ -70,11 +86,12 @@ }; | ||
return Parser(function(stream, i, onSuccess, onFailure) { | ||
return self._(stream, i, success, onFailure); | ||
return Parser(function(stream, i) { | ||
var result = self._(stream, i); | ||
function success(stream, newI, result, furthestFailureI, furthestExpected) { | ||
var nextParser = (next instanceof Parser ? next : next(result)); | ||
var nextSuccess = furthestFailureSuccess(onSuccess, furthestFailureI, furthestExpected); | ||
var nextFailure = furthestFailure(onFailure, furthestFailureI, furthestExpected); | ||
return nextParser._(stream, newI, nextSuccess, nextFailure); | ||
if (result.status) { | ||
var nextParser = (next instanceof Parser ? next : next(result.value)); | ||
return furthestBacktrackFor(nextParser._(stream, result.index), result); | ||
} | ||
else { | ||
return result; | ||
} | ||
}); | ||
@@ -100,22 +117,18 @@ }; | ||
return Parser(function(stream, i, onSuccess, onFailure) { | ||
var xs = []; | ||
while (self._(stream, i, success, failure)); | ||
var furthestFailureI, furthestExpected; | ||
return onSuccess(stream, i, xs, furthestFailureI, furthestExpected); | ||
return Parser(function(stream, i) { | ||
var accum = []; | ||
var result; | ||
var prevResult; | ||
function success(stream, newI, x, successFurthestFailureI, successFurthestExpected) { | ||
i = newI; | ||
xs.push(x); | ||
furthestFailureI = successFurthestFailureI; | ||
furthestExpected = successFurthestExpected; | ||
return true; | ||
} | ||
for (;;) { | ||
result = self._(stream, i); | ||
prevResult = furthestBacktrackFor(result, prevResult); | ||
function failure(stream, newI, expected) { | ||
if (!(furthestFailureI > newI)) { | ||
furthestFailureI = newI; | ||
furthestExpected = expected; | ||
if (result.status) { | ||
i = result.index; | ||
accum.push(result.value); | ||
} | ||
return false; | ||
else { | ||
return furthestBacktrackFor(makeSuccess(i, accum), prevResult); | ||
} | ||
} | ||
@@ -149,33 +162,33 @@ }); | ||
return Parser(function(stream, i, onSuccess, onFailure) { | ||
var xs = []; | ||
var result = true; | ||
var furthestFailureI, furthestExpected; | ||
return Parser(function(stream, i) { | ||
var accum = []; | ||
var start = i; | ||
var result; | ||
var prevResult; | ||
for (var times = 0; times < min; times += 1) { | ||
result = self._(stream, i, success, failure); | ||
if (!result) return onFailure(stream, furthestFailureI, furthestExpected); | ||
result = self._(stream, i); | ||
prevResult = furthestBacktrackFor(result, prevResult); | ||
if (result.status) { | ||
i = result.index; | ||
accum.push(result.value); | ||
} | ||
else { | ||
return prevResult; | ||
} | ||
} | ||
for (; times < max && result; times += 1) { | ||
result = self._(stream, i, success, failure); | ||
result = self._(stream, i); | ||
prevResult = furthestBacktrackFor(result, prevResult); | ||
if (result.status) { | ||
i = result.index; | ||
accum.push(result.value); | ||
} | ||
else { | ||
break; | ||
} | ||
} | ||
return onSuccess(stream, i, xs, furthestFailureI, furthestExpected); | ||
function success(stream, newI, x, successFurthestFailureI, successFurthestExpected) { | ||
i = newI; | ||
xs.push(x); | ||
furthestFailureI = successFurthestFailureI; | ||
furthestExpected = successFurthestExpected; | ||
return true; | ||
} | ||
function failure(stream, newI, expected) { | ||
if (!(furthestFailureI > newI)) { | ||
furthestFailureI = newI; | ||
furthestExpected = expected; | ||
} | ||
return false; | ||
} | ||
return furthestBacktrackFor(makeSuccess(i, accum), prevResult); | ||
}); | ||
@@ -204,2 +217,14 @@ }; | ||
// TODO: this would be better implemented with `seq` | ||
_.mark = function() { | ||
var self = this; | ||
return index.then(function(start) { | ||
return self.then(function(value) { | ||
return index.map(function(end) { | ||
return { start: start, value: value, end: end }; | ||
}); | ||
}); | ||
}); | ||
}; | ||
// -*- primitive parsers -*- // | ||
@@ -210,10 +235,10 @@ var string = Parsimmon.string = function(str) { | ||
return Parser(function(stream, i, onSuccess, onFailure) { | ||
return Parser(function(stream, i) { | ||
var head = stream.slice(i, i+len); | ||
if (head === str) { | ||
return onSuccess(stream, i+len, head, -1); | ||
return makeSuccess(i+len, head); | ||
} | ||
else { | ||
return onFailure(stream, i, expected); | ||
return makeFailure(i, expected); | ||
} | ||
@@ -228,3 +253,3 @@ }); | ||
return Parser(function(stream, i, onSuccess, onFailure) { | ||
return Parser(function(stream, i) { | ||
var match = re.exec(stream.slice(i)); | ||
@@ -234,6 +259,6 @@ | ||
var result = match[0]; | ||
return onSuccess(stream, i+result.length, result, -1); | ||
return makeSuccess(i+result.length, result); | ||
} | ||
else { | ||
return onFailure(stream, i, expected); | ||
return makeFailure(i, re); | ||
} | ||
@@ -243,5 +268,5 @@ }); | ||
var succeed = Parsimmon.succeed = function(result) { | ||
return Parser(function(stream, i, onSuccess) { | ||
return onSuccess(stream, i, result); | ||
var succeed = Parsimmon.succeed = function(value) { | ||
return Parser(function(stream, i) { | ||
return makeSuccess(i, value); | ||
}); | ||
@@ -251,5 +276,3 @@ }; | ||
var fail = Parsimmon.fail = function(expected) { | ||
return Parser(function(stream, i, _, onFailure) { | ||
return onFailure(stream, i, expected); | ||
}); | ||
return Parser(function(stream, i) { return makeFailure(i, expected); }); | ||
}; | ||
@@ -264,17 +287,41 @@ | ||
var any = Parsimmon.any = Parser(function(stream, i, onSuccess, onFailure) { | ||
if (i >= stream.length) return onFailure(stream, i, 'any character'); | ||
var any = Parsimmon.any = Parser(function(stream, i) { | ||
if (i >= stream.length) return makeFailure(i, 'any character'); | ||
return onSuccess(stream, i+1, stream.charAt(i), -1); | ||
return makeSuccess(i+1, stream.charAt(i)); | ||
}); | ||
var all = Parsimmon.all = Parser(function(stream, i, onSuccess, onFailure) { | ||
return onSuccess(stream, stream.length, stream.slice(i), -1); | ||
var all = Parsimmon.all = Parser(function(stream, i) { | ||
return makeSuccess(stream.length, stream.slice(i)); | ||
}); | ||
var eof = Parsimmon.eof = Parser(function(stream, i, onSuccess, onFailure) { | ||
if (i < stream.length) return onFailure(stream, i, 'EOF'); | ||
var eof = Parsimmon.eof = Parser(function(stream, i) { | ||
if (i < stream.length) return makeFailure(i, 'EOF'); | ||
return onSuccess(stream, i, '', -1); | ||
return makeSuccess(i, null); | ||
}); | ||
var index = Parsimmon.index = Parser(function(stream, i) { | ||
return makeSuccess(i, i); | ||
}); | ||
//- fantasyland compat | ||
//- Semigroup | ||
_.concat = _.then; | ||
//- Applicative | ||
_.of = Parser.of = Parsimmon.of = succeed | ||
// TODO: this could be better implemented with `seq` | ||
_.ap = function(other) { | ||
return this.then(function(fn) { | ||
return other.then(function(val) { | ||
return fn(val); | ||
}); | ||
}); | ||
}; | ||
//- Monad | ||
_.chain = _.then; | ||
}); |
@@ -1,10 +0,1 @@ | ||
var assert = require('assert') | ||
, Parsimmon = require('./../index') | ||
, mocha = require('mocha') | ||
; | ||
function partialEquals(x) { | ||
return function(y) { return x === y; } | ||
} | ||
suite('parser', function() { | ||
@@ -20,2 +11,3 @@ var string = Parsimmon.string; | ||
var all = Parsimmon.all; | ||
var index = Parsimmon.index; | ||
@@ -26,3 +18,3 @@ test('Parsimmon.string', function() { | ||
assert.throws(function() { parser.parse('y') }, | ||
partialEquals("Parse Error: expected 'x' at character 0, got 'y'\n parsing: 'y'")); | ||
"Parse Error: expected 'x' at character 0, got 'y'\n parsing: 'y'"); | ||
}); | ||
@@ -36,3 +28,3 @@ | ||
assert.throws(function() { parser.parse('x'); }, | ||
partialEquals("Parse Error: expected /^[0-9]/ at character 0, got 'x'\n parsing: 'x'")); | ||
"Parse Error: expected /^[0-9]/ at character 0, got 'x'\n parsing: 'x'"); | ||
assert.throws(function() { regex(/./) }, 'must be anchored'); | ||
@@ -46,5 +38,5 @@ }); | ||
assert.throws(function() { parser.parse('y'); }, | ||
partialEquals("Parse Error: expected 'x' at character 0, got 'y'\n parsing: 'y'")); | ||
"Parse Error: expected 'x' at character 0, got 'y'\n parsing: 'y'"); | ||
assert.throws(function() { parser.parse('xz'); }, | ||
partialEquals("Parse Error: expected 'y' at character 1, got '...z'\n parsing: 'xz'")); | ||
"Parse Error: expected 'y' at character 1, got '...z'\n parsing: 'xz'"); | ||
}); | ||
@@ -215,3 +207,3 @@ | ||
assert.throws(function() { parser.parse('y'); }, | ||
partialEquals("Parse Error: expected a character besides y, got the end of the string\n parsing: 'y'")); | ||
"Parse Error: expected a character besides y, got the end of the string\n parsing: 'y'"); | ||
assert.equal(parser.parse('x'), 'x'); | ||
@@ -236,3 +228,3 @@ }); | ||
assert.throws(function() { parser.parse('x*y'); }, | ||
partialEquals("Parse Error: expected + at character 2, got '...y'\n parsing: 'x*y'")); | ||
"Parse Error: expected + at character 2, got '...y'\n parsing: 'x*y'"); | ||
@@ -242,3 +234,3 @@ allowedOperator = '*'; | ||
assert.throws(function() { parser.parse('x+y'); }, | ||
partialEquals("Parse Error: expected * at character 2, got '...y'\n parsing: 'x+y'")); | ||
"Parse Error: expected * at character 2, got '...y'\n parsing: 'x+y'"); | ||
}); | ||
@@ -254,2 +246,16 @@ }); | ||
test('index', function() { | ||
var parser = regex(/^x*/).then(index); | ||
assert.equal(parser.parse(''), 0); | ||
assert.equal(parser.parse('xx'), 2); | ||
assert.equal(parser.parse('xxxx'), 4); | ||
}); | ||
test('mark', function() { | ||
var ys = regex(/^y*/).mark() | ||
var parser = optWhitespace.then(ys).skip(optWhitespace); | ||
assert.deepEqual(parser.parse(''), { start: 0, value: '', end: 0 }); | ||
assert.deepEqual(parser.parse(' yy '), { start: 1, value: 'yy', end: 3 }); | ||
}); | ||
suite('smart error messages', function() { | ||
@@ -264,3 +270,3 @@ // this is mainly about .or(), .many(), and .times(), but not about | ||
assert.throws(function() { parser.parse('abc'); }, | ||
partialEquals("Parse Error: expected 'def', got the end of the string\n parsing: 'abc'")); | ||
"Parse Error: expected 'def', got the end of the string\n parsing: 'abc'"); | ||
}); | ||
@@ -272,3 +278,3 @@ | ||
assert.throws(function() { parser.parse('abc'); }, | ||
partialEquals("Parse Error: expected 'd', got the end of the string\n parsing: 'abc'")); | ||
"Parse Error: expected 'd', got the end of the string\n parsing: 'abc'"); | ||
}); | ||
@@ -281,3 +287,3 @@ | ||
assert.throws(function() { parser.parse('abcdef'); }, | ||
partialEquals("Parse Error: expected 'g', got the end of the string\n parsing: 'abcdef'")); | ||
"Parse Error: expected 'g', got the end of the string\n parsing: 'abcdef'"); | ||
}); | ||
@@ -292,8 +298,9 @@ }); | ||
assert.deepEqual(list.parse('(a b) (c ((() d)))'), [['a', 'b'], ['c', [[[], 'd']]]]); | ||
// assert.deepEqual(list.parse('(a b) (c ((() d)))'), [['a', 'b'], ['c', [[[], 'd']]]]); | ||
assert.throws(function() { list.parse('(a b ()) c)'); }, | ||
partialEquals("Parse Error: expected EOF at character 10, got '...)'\n parsing: '(a b ()) c)'")); | ||
"Parse Error: expected EOF at character 10, got '...)'\n parsing: '(a b ()) c)'"); | ||
assert.throws(function() { list.parse('(a (b)) (() c'); }, | ||
partialEquals("Parse Error: expected ')', got the end of the string\n parsing: '(a (b)) (() c'")); | ||
"Parse Error: expected ')', got the end of the string\n parsing: '(a (b)) (() c'"); | ||
}); | ||
@@ -307,3 +314,3 @@ | ||
assert.throws(function() { parser.parse('aaabcde'); }, | ||
partialEquals("Parse Error: expected 'def' at character 5, got '...de'\n parsing: 'aaabcde'")); | ||
"Parse Error: expected 'def' at character 5, got '...de'\n parsing: 'aaabcde'"); | ||
}); | ||
@@ -317,6 +324,6 @@ }); | ||
assert.throws(function() { parser.parse('aabcde'); }, | ||
partialEquals("Parse Error: expected 'def' at character 4, got '...de'\n parsing: 'aabcde'")); | ||
"Parse Error: expected 'def' at character 4, got '...de'\n parsing: 'aabcde'"); | ||
assert.throws(function() { parser.parse('aaaaabcde'); }, | ||
partialEquals("Parse Error: expected 'def' at character 7, got '...de'\n parsing: 'aaaaabcde'")); | ||
"Parse Error: expected 'def' at character 7, got '...de'\n parsing: 'aaaaabcde'"); | ||
}); | ||
@@ -323,0 +330,0 @@ }); |
Sorry, the diff of this file is not supported yet
974092
3481.88%13
30%528
10%147
8.09%3
50%