Comparing version 0.3.2 to 0.4.0
@@ -118,18 +118,22 @@ var Parsimmon = (function(undefined) { | ||
function parseError(stream, result) { | ||
var expected = result.expected; | ||
var i = result.furthest; | ||
function assertParser(p) { | ||
if (!(p instanceof Parser)) throw new Error('not a parser: '+p); | ||
} | ||
var formatError = Parsimmon.formatError = function(stream, error) { | ||
var expected = error.expected; | ||
var i = error.index; | ||
if (i === stream.length) { | ||
var message = 'expected ' + expected + ', got the end of the string'; | ||
return 'expected ' + expected + ', got the end of the string'; | ||
} | ||
else { | ||
var prefix = (i > 0 ? "'..." : "'"); | ||
var suffix = (stream.length - i > 12 ? "...'" : "'"); | ||
var message = 'expected ' + expected + ' at character ' + i + ', got ' | ||
+ prefix + stream.slice(i, i+12) + suffix; | ||
} | ||
throw new Error('Parse Error: ' + message + "\n parsing: '" + stream + "'"); | ||
} | ||
var prefix = (i > 0 ? "'..." : "'"); | ||
var suffix = (stream.length - i > 12 ? "...'" : "'"); | ||
return ( | ||
'expected ' + expected + ' at character ' + i + ', got ' + | ||
prefix + stream.slice(i, i+12) + suffix | ||
); | ||
}; | ||
_.init = function(body) { this._ = body; }; | ||
@@ -140,3 +144,10 @@ | ||
return result.status ? result.value : parseError(stream, result); | ||
return result.status ? { | ||
status: true, | ||
value: result.value | ||
} : { | ||
status: false, | ||
index: result.furthest, | ||
expected: result.expected | ||
}; | ||
}; | ||
@@ -150,12 +161,8 @@ | ||
_.then = function(next) { | ||
var self = this; | ||
if (typeof next === 'function') { | ||
throw new Error('chaining features of .then are no longer supported'); | ||
} | ||
return Parser(function(stream, i) { | ||
var result = self._(stream, i); | ||
if (!result.status) return result; | ||
var nextParser = (next instanceof Parser ? next : next(result.value)); | ||
return furthestBacktrackFor(nextParser._(stream, result.index), result); | ||
}); | ||
assertParser(next); | ||
return seq(this, next).map(function(results) { return results[1]; }); | ||
}; | ||
@@ -416,5 +423,13 @@ | ||
//- Monad | ||
_.chain = _.then; | ||
_.chain = function(f) { | ||
var self = this; | ||
return Parser(function(stream, i) { | ||
var result = self._(stream, i); | ||
if (!result.status) return result; | ||
var nextParser = f(result.value); | ||
return furthestBacktrackFor(nextParser._(stream, result.index), result); | ||
}); | ||
}; | ||
}); | ||
return Parsimmon; | ||
})() |
@@ -1,1 +0,1 @@ | ||
var Parsimmon=function(t){var r=function(t,r,n){return function e(u,a){if(a===n){a=u;u=Object}function i(){var t=this instanceof i?this:new s;t.init.apply(t,arguments);return t}function s(){}i.Bare=s;var f=s[t]=u[t];var c=s[t]=i[t]=i.p=new s;var o;c.constructor=i;i.extend=function(t){return e(i,t)};return(i.open=function(t){if(typeof t==="function"){t=t.call(i,c,f,i,u)}if(typeof t==="object"){for(o in t){if(r.call(t,o)){c[o]=t[o]}}}if(!("init"in c))c.init=u;return i})(a)}}("prototype",{}.hasOwnProperty);var n={};n.Parser=r(function(t,r,e){"use strict";function u(t,r){return{status:true,index:t,value:r,furthest:-1,expected:""}}function a(t,r){return{status:false,index:-1,value:null,furthest:t,expected:r}}function i(t,r){if(!r)return t;if(t.furthest>=r.furthest)return t;return{status:t.status,index:t.index,value:t.value,furthest:r.furthest,expected:r.expected}}function s(t,r){var n=r.expected;var e=r.furthest;if(e===t.length){var u="expected "+n+", got the end of the string"}else{var a=e>0?"'...":"'";var i=t.length-e>12?"...'":"'";var u="expected "+n+" at character "+e+", got "+a+t.slice(e,e+12)+i}throw new Error("Parse Error: "+u+"\n parsing: '"+t+"'")}t.init=function(t){this._=t};t.parse=function(t){var r=this.skip(_)._(t,0);return r.status?r.value:s(t,r)};t.or=function(t){return z(this,t)};t.then=function(t){var r=this;return e(function(n,u){var a=r._(n,u);if(!a.status)return a;var s=t instanceof e?t:t(a.value);return i(s._(n,a.index),a)})};t.many=function(){var t=this;return e(function(r,n){var e=[];var a;var s;for(;;){a=i(t._(r,n),a);if(a.status){n=a.index;e.push(a.value)}else{return i(u(n,e),a)}}})};t.times=function(t,r){if(arguments.length<2)r=t;var n=this;return e(function(e,a){var s=[];var f=a;var c;var o;for(var v=0;v<t;v+=1){c=n._(e,a);o=i(c,o);if(c.status){a=c.index;s.push(c.value)}else{return o}}for(;v<r;v+=1){c=n._(e,a);o=i(c,o);if(c.status){a=c.index;s.push(c.value)}else{break}}return i(u(a,s),o)})};t.result=function(t){return this.then(o(t))};t.atMost=function(t){return this.times(0,t)};t.atLeast=function(t){var r=this;return k(this.times(t),this.many()).map(function(t){return t[0].concat(t[1])})};t.map=function(t){var r=this;return e(function(n,e){var a=r._(n,e);if(!a.status)return a;return i(u(a.index,t(a.value)),a)})};t.skip=function(t){return k(this,t).map(function(t){return t[0]})};t.mark=function(){return k(E,this,E).map(function(t){return{start:t[0],value:t[1],end:t[2]}})};var f=n.string=function(t){var r=t.length;var n="'"+t+"'";return e(function(e,i){var s=e.slice(i,i+r);if(s===t){return u(i+r,s)}else{return a(i,n)}})};var c=n.regex=function(t){var r=RegExp("^(?:"+t.source+")",(""+t).slice((""+t).lastIndexOf("/")+1));return e(function(n,e){var i=r.exec(n.slice(e));if(i){var s=i[0];return u(e+s.length,s)}else{return a(e,t)}})};var o=n.succeed=function(t){return e(function(r,n){return u(n,t)})};var v=n.fail=function(t){return e(function(r,n){return a(n,t)})};var l=n.letter=c(/[a-z]/i);var h=n.letters=c(/[a-z]*/i);var p=n.digit=c(/[0-9]/);var d=n.digits=c(/[0-9]*/);var g=n.whitespace=c(/\s+/);var x=n.optWhitespace=c(/\s*/);var m=n.any=e(function(t,r){if(r>=t.length)return a(r,"any character");return u(r+1,t.charAt(r))});var y=n.all=e(function(t,r){return u(t.length,t.slice(r))});var _=n.eof=e(function(t,r){if(r<t.length)return a(r,"EOF");return u(r,null)});var w=n.lazy=function(t){var r=e(function(n,e){r._=t()._;return r._(n,e)});return r};var k=n.seq=function(){var t=[].slice.call(arguments);var r=t.length;return e(function(n,e){var a;var s=new Array(r);for(var f=0;f<r;f+=1){a=i(t[f]._(n,e),a);if(!a.status)return a;s[f]=a.value;e=a.index}return i(u(e,s),a)})};var z=n.alt=function(){var t=[].slice.call(arguments);var r=t.length;if(r===0)return v("zero alternates");return e(function(r,n){var e;for(var u=0;u<t.length;u+=1){e=i(t[u]._(r,n),e);if(e.status)return e}return e})};var E=n.index=e(function(t,r){return u(r,r)});t.concat=t.or;t.empty=v("empty");t.of=e.of=n.of=o;t.ap=function(t){return k(this,t).map(function(t){return t[0](t[1])})};t.chain=t.then});return n}(); | ||
var Parsimmon=function(t){var r=function(t,r,n){return function e(u,a){if(a===n){a=u;u=Object}function i(){var t=this instanceof i?this:new f;t.init.apply(t,arguments);return t}function f(){}i.Bare=f;var s=f[t]=u[t];var o=f[t]=i[t]=i.p=new f;var c;o.constructor=i;i.extend=function(t){return e(i,t)};return(i.open=function(t){if(typeof t==="function"){t=t.call(i,o,s,i,u)}if(typeof t==="object"){for(c in t){if(r.call(t,c)){o[c]=t[c]}}}if(!("init"in o))o.init=u;return i})(a)}}("prototype",{}.hasOwnProperty);var n={};n.Parser=r(function(t,r,e){"use strict";function u(t,r){return{status:true,index:t,value:r,furthest:-1,expected:""}}function a(t,r){return{status:false,index:-1,value:null,furthest:t,expected:r}}function i(t,r){if(!r)return t;if(t.furthest>=r.furthest)return t;return{status:t.status,index:t.index,value:t.value,furthest:r.furthest,expected:r.expected}}function f(t){if(!(t instanceof e))throw new Error("not a parser: "+t)}var s=n.formatError=function(t,r){var n=r.expected;var e=r.index;if(e===t.length){return"expected "+n+", got the end of the string"}var u=e>0?"'...":"'";var a=t.length-e>12?"...'":"'";return"expected "+n+" at character "+e+", got "+u+t.slice(e,e+12)+a};t.init=function(t){this._=t};t.parse=function(t){var r=this.skip(w)._(t,0);return r.status?{status:true,value:r.value}:{status:false,index:r.furthest,expected:r.expected}};t.or=function(t){return z(this,t)};t.then=function(t){if(typeof t==="function"){throw new Error("chaining features of .then are no longer supported")}f(t);return k(this,t).map(function(t){return t[1]})};t.many=function(){var t=this;return e(function(r,n){var e=[];var a;var f;for(;;){a=i(t._(r,n),a);if(a.status){n=a.index;e.push(a.value)}else{return i(u(n,e),a)}}})};t.times=function(t,r){if(arguments.length<2)r=t;var n=this;return e(function(e,a){var f=[];var s=a;var o;var c;for(var v=0;v<t;v+=1){o=n._(e,a);c=i(o,c);if(o.status){a=o.index;f.push(o.value)}else{return c}}for(;v<r;v+=1){o=n._(e,a);c=i(o,c);if(o.status){a=o.index;f.push(o.value)}else{break}}return i(u(a,f),c)})};t.result=function(t){return this.then(v(t))};t.atMost=function(t){return this.times(0,t)};t.atLeast=function(t){var r=this;return k(this.times(t),this.many()).map(function(t){return t[0].concat(t[1])})};t.map=function(t){var r=this;return e(function(n,e){var a=r._(n,e);if(!a.status)return a;return i(u(a.index,t(a.value)),a)})};t.skip=function(t){return k(this,t).map(function(t){return t[0]})};t.mark=function(){return k(O,this,O).map(function(t){return{start:t[0],value:t[1],end:t[2]}})};var o=n.string=function(t){var r=t.length;var n="'"+t+"'";return e(function(e,i){var f=e.slice(i,i+r);if(f===t){return u(i+r,f)}else{return a(i,n)}})};var c=n.regex=function(t){var r=RegExp("^(?:"+t.source+")",(""+t).slice((""+t).lastIndexOf("/")+1));return e(function(n,e){var i=r.exec(n.slice(e));if(i){var f=i[0];return u(e+f.length,f)}else{return a(e,t)}})};var v=n.succeed=function(t){return e(function(r,n){return u(n,t)})};var l=n.fail=function(t){return e(function(r,n){return a(n,t)})};var h=n.letter=c(/[a-z]/i);var p=n.letters=c(/[a-z]*/i);var d=n.digit=c(/[0-9]/);var x=n.digits=c(/[0-9]*/);var g=n.whitespace=c(/\s+/);var m=n.optWhitespace=c(/\s*/);var y=n.any=e(function(t,r){if(r>=t.length)return a(r,"any character");return u(r+1,t.charAt(r))});var _=n.all=e(function(t,r){return u(t.length,t.slice(r))});var w=n.eof=e(function(t,r){if(r<t.length)return a(r,"EOF");return u(r,null)});var E=n.lazy=function(t){var r=e(function(n,e){r._=t()._;return r._(n,e)});return r};var k=n.seq=function(){var t=[].slice.call(arguments);var r=t.length;return e(function(n,e){var a;var f=new Array(r);for(var s=0;s<r;s+=1){a=i(t[s]._(n,e),a);if(!a.status)return a;f[s]=a.value;e=a.index}return i(u(e,f),a)})};var z=n.alt=function(){var t=[].slice.call(arguments);var r=t.length;if(r===0)return l("zero alternates");return e(function(r,n){var e;for(var u=0;u<t.length;u+=1){e=i(t[u]._(r,n),e);if(e.status)return e}return e})};var O=n.index=e(function(t,r){return u(r,r)});t.concat=t.or;t.empty=l("empty");t.of=e.of=n.of=v;t.ap=function(t){return k(this,t).map(function(t){return t[0](t[1])})};t.chain=function(t){var r=this;return e(function(n,e){var u=r._(n,e);if(!u.status)return u;var a=t(u.value);return i(a._(n,u.index),u)})}});return n}(); |
@@ -46,18 +46,22 @@ var P = require('pjs').P; | ||
function parseError(stream, result) { | ||
var expected = result.expected; | ||
var i = result.furthest; | ||
function assertParser(p) { | ||
if (!(p instanceof Parser)) throw new Error('not a parser: '+p); | ||
} | ||
var formatError = Parsimmon.formatError = function(stream, error) { | ||
var expected = error.expected; | ||
var i = error.index; | ||
if (i === stream.length) { | ||
var message = 'expected ' + expected + ', got the end of the string'; | ||
return 'expected ' + expected + ', got the end of the string'; | ||
} | ||
else { | ||
var prefix = (i > 0 ? "'..." : "'"); | ||
var suffix = (stream.length - i > 12 ? "...'" : "'"); | ||
var message = 'expected ' + expected + ' at character ' + i + ', got ' | ||
+ prefix + stream.slice(i, i+12) + suffix; | ||
} | ||
throw new Error('Parse Error: ' + message + "\n parsing: '" + stream + "'"); | ||
} | ||
var prefix = (i > 0 ? "'..." : "'"); | ||
var suffix = (stream.length - i > 12 ? "...'" : "'"); | ||
return ( | ||
'expected ' + expected + ' at character ' + i + ', got ' + | ||
prefix + stream.slice(i, i+12) + suffix | ||
); | ||
}; | ||
_.init = function(body) { this._ = body; }; | ||
@@ -68,3 +72,10 @@ | ||
return result.status ? result.value : parseError(stream, result); | ||
return result.status ? { | ||
status: true, | ||
value: result.value | ||
} : { | ||
status: false, | ||
index: result.furthest, | ||
expected: result.expected | ||
}; | ||
}; | ||
@@ -78,12 +89,8 @@ | ||
_.then = function(next) { | ||
var self = this; | ||
if (typeof next === 'function') { | ||
throw new Error('chaining features of .then are no longer supported'); | ||
} | ||
return Parser(function(stream, i) { | ||
var result = self._(stream, i); | ||
if (!result.status) return result; | ||
var nextParser = (next instanceof Parser ? next : next(result.value)); | ||
return furthestBacktrackFor(nextParser._(stream, result.index), result); | ||
}); | ||
assertParser(next); | ||
return seq(this, next).map(function(results) { return results[1]; }); | ||
}; | ||
@@ -344,4 +351,12 @@ | ||
//- Monad | ||
_.chain = _.then; | ||
_.chain = function(f) { | ||
var self = this; | ||
return Parser(function(stream, i) { | ||
var result = self._(stream, i); | ||
if (!result.status) return result; | ||
var nextParser = f(result.value); | ||
return furthestBacktrackFor(nextParser._(stream, result.index), result); | ||
}); | ||
}; | ||
}); | ||
module.exports = Parsimmon; |
{ | ||
"name": "parsimmon", | ||
"version": "0.3.2", | ||
"version": "0.4.0", | ||
"description": "A monadic LL(infinity) parser combinator library", | ||
@@ -5,0 +5,0 @@ "keywords": ["parsing", "parse", "parser combinators"], |
134
README.md
@@ -17,16 +17,19 @@ [![Build Status](https://secure.travis-ci.org/jayferd/parsimmon.png)](http://travis-ci.org/jayferd/parsimmon) | ||
var optWhitespace = Parsimmon.optWhitespace; | ||
var lazy = Parsimmon.lazy; | ||
var id = regex(/[a-z_]\w*/i); | ||
var number = regex(/[0-9]+/).map(parseInt); | ||
function lexeme(p) { return p.skip(optWhitespace); } | ||
var atom = number.or(id); | ||
var lparen = lexeme(string('(')); | ||
var rparen = lexeme(string(')')); | ||
var form = string('(').skip(optWhitespace).then(function() { | ||
return expr.many().skip(string(')')); | ||
}); | ||
var expr = lazy(function() { return form.or(atom) }); | ||
var expr = form.or(atom).skip(optWhitespace); | ||
var number = lexeme(regex(/[0-9]+/).map(parseInt)); | ||
var id = lexeme(regex(/[a-z_]\w*/i)); | ||
expr.parse('3') // => 3 | ||
expr.parse('(add (mul 10 (add 3 4)) (add 7 8))') | ||
var atom = number.or(id); | ||
var form = lparen.then(expr.many()).skip(rparen); | ||
expr.parse('3').value // => 3 | ||
expr.parse('(add (mul 10 (add 3 4)) (add 7 8))').value | ||
// => ['add', ['mul', 10, ['add', 3, 4]], ['add', 7, 8]] | ||
@@ -39,5 +42,3 @@ ``` | ||
of text, and the promise of either an object yielded by that action on | ||
success or a message in case of failure. Under the hood, this is | ||
represented by a function that takes a stream and calls one of two | ||
callbacks with an error or a result. For example, `string('foo')` | ||
success or a message in case of failure. For example, `string('foo')` | ||
yields the string `'foo'` if the beginning of the stream is `'foo'`, | ||
@@ -47,41 +48,25 @@ and otherwise fails. | ||
The combinator method `.map` is used to transform the yielded value. | ||
For example, `string('foo').map(function(x) { return x + 'bar'; })` | ||
will yield `'foobar'` if the stream starts with `'foo'`. The parser | ||
`digits.map(function(x) { return parseInt(x) * 2; })` will yield | ||
the number 24 when it encounters the string '12'. The method | ||
`.result` can be used to set a constant result. | ||
For example, | ||
The two core ways to combine parsers are `.then` and `.or`. The | ||
method `.then` provides a way to decide how to continue the parse | ||
based on the result of a previous parser. For a kind of contrived | ||
example, | ||
``` js | ||
var sentence = regex(/[\w\s]+/).then(function(contents) { | ||
var ending; | ||
string('foo').map(function(x) { return x + 'bar'; }) | ||
``` | ||
if (contents.indexOf('bang') >= 0) { | ||
ending = '!'; | ||
} | ||
else { | ||
ending = '.' | ||
} | ||
will yield `'foobar'` if the stream starts with `'foo'`. The parser | ||
return string(ending).result(contents + ending); | ||
}); | ||
sentence.parse('quick brown dogs and things.') // => 'quick brown dogs and things.' | ||
sentence.parse('shebang.') // parse error: expected '!' | ||
sentence.parse('shebang!') // => 'shebang!' | ||
``` js | ||
digits.map(function(x) { return parseInt(x) * 2; }) | ||
``` | ||
For the monad-loving crowd, `.then` is the `bind` operation on | ||
the parser monad (much like Parsec). For others, this is very | ||
similar to the Promises/A spec, implemented by jQuery's deferred | ||
objects. | ||
will yield the number 24 when it encounters the string '12'. The method | ||
`.result` can be used to set a constant result. | ||
The method `.or` allows a parser to continue by trying another parser | ||
if it fails. So `string('a').or(string('b'))` will yield an `'a'` if | ||
the stream starts with an `'a'`, and a `'b'` if the stream starts with | ||
a `'b'`, and fail otherwise. | ||
Calling `.parse(str)` on a parser parses the string, and returns an | ||
object with a `status` flag, indicating whether the parse succeeded. | ||
If it succeeded, the `value` attribute will contain the yielded value. | ||
Otherwise, the `index` and `expected` attributes will contain the | ||
index of the parse error, and a message indicating what was expected. | ||
The error object can be passed along with the original source to | ||
`Parsimmon.formatError(source, error)` to obtain a human-readable | ||
error string. | ||
@@ -99,5 +84,7 @@ ## Full API | ||
that it expects to find in order, yielding an array of the results. | ||
- `Parsimmon.lazy(f)` accepts a function that returns a parser, which is evaluated the | ||
first time the parser is used. This is useful for referencing parsers that haven't yet | ||
been defined. | ||
- `Parsimmon.alt(p1, p2, ... pn)` accepts a variable number of parsers, | ||
and yields the value of the first one that succeeds, backtracking in between. | ||
- `Parsimmon.lazy(f)` accepts a function that returns a parser, which | ||
is evaluated the first time the parser is used. This is useful for | ||
referencing parsers that haven't yet been defined. | ||
- `Parsimmon.fail(message)` | ||
@@ -118,6 +105,8 @@ - `Parsimmon.letter` is equivalent to `Parsimmon.regex(/[a-z]/i)` | ||
returns a new parser which tries `parser`, and if it fails uses `otherParser`. | ||
- `parser.then(function(result) { return anotherParser; })`: | ||
- `parser.chain(function(result) { return anotherParser; })`: | ||
returns a new parser which tries `parser`, and on success calls the | ||
given function with the result of the parse, which is expected to | ||
return another parser. | ||
return another parser, which will be tried next. This allows you | ||
to dynamically decide how to continue the parse, which is impossible | ||
with the other combinators. | ||
- `parser.then(anotherParser)`: | ||
@@ -148,2 +137,49 @@ expects `anotherParser` to follow `parser`, and yields the result | ||
## Tips and patterns | ||
These apply to most parsers for traditional langauges - it's possible | ||
you may need to do something different for yours! | ||
For most parsers, the following format is helpful: | ||
1. define a `lexeme` function to skip all the stuff you don't care | ||
about (whitespace, comments, etc). You may need multiple types of lexemes. | ||
For example, | ||
``` js | ||
var ignore = whitespace.or(comment.many()); | ||
function lexeme(p) { return p.skip(ignore); } | ||
``` | ||
1. Define all your lexemes first. These should yield native javascript values. | ||
``` js | ||
var lparen = lexeme(string('(')); | ||
var rparen = lexeme(string(')')); | ||
var number = lexeme(regex(/[0-9]+/)).map(parseInt); | ||
``` | ||
1. Forward-declare one or more top-level expressions with `lazy`, | ||
referring to parsers that have not yet been defined. Generally, this | ||
takes the form of a large `.alt()` call | ||
``` js | ||
var expr = lazy(function() { return Parsimmon.alt(p1, p2, ...); }); | ||
``` | ||
1. Then build your parsers from the inside out - these should return | ||
AST nodes or other objects specific to your domain. | ||
``` js | ||
var p1 = ... | ||
var p2 = ... | ||
``` | ||
1. Finally, export your top-level parser. Remember to skip ignored | ||
stuff at the beginning. | ||
``` js | ||
return ignore.then(expr.many()); | ||
``` | ||
### Fantasyland | ||
@@ -150,0 +186,0 @@ |
@@ -45,18 +45,22 @@ var Parsimmon = {}; | ||
function parseError(stream, result) { | ||
var expected = result.expected; | ||
var i = result.furthest; | ||
function assertParser(p) { | ||
if (!(p instanceof Parser)) throw new Error('not a parser: '+p); | ||
} | ||
var formatError = Parsimmon.formatError = function(stream, error) { | ||
var expected = error.expected; | ||
var i = error.index; | ||
if (i === stream.length) { | ||
var message = 'expected ' + expected + ', got the end of the string'; | ||
return 'expected ' + expected + ', got the end of the string'; | ||
} | ||
else { | ||
var prefix = (i > 0 ? "'..." : "'"); | ||
var suffix = (stream.length - i > 12 ? "...'" : "'"); | ||
var message = 'expected ' + expected + ' at character ' + i + ', got ' | ||
+ prefix + stream.slice(i, i+12) + suffix; | ||
} | ||
throw new Error('Parse Error: ' + message + "\n parsing: '" + stream + "'"); | ||
} | ||
var prefix = (i > 0 ? "'..." : "'"); | ||
var suffix = (stream.length - i > 12 ? "...'" : "'"); | ||
return ( | ||
'expected ' + expected + ' at character ' + i + ', got ' + | ||
prefix + stream.slice(i, i+12) + suffix | ||
); | ||
}; | ||
_.init = function(body) { this._ = body; }; | ||
@@ -67,3 +71,10 @@ | ||
return result.status ? result.value : parseError(stream, result); | ||
return result.status ? { | ||
status: true, | ||
value: result.value | ||
} : { | ||
status: false, | ||
index: result.furthest, | ||
expected: result.expected | ||
}; | ||
}; | ||
@@ -77,12 +88,8 @@ | ||
_.then = function(next) { | ||
var self = this; | ||
if (typeof next === 'function') { | ||
throw new Error('chaining features of .then are no longer supported'); | ||
} | ||
return Parser(function(stream, i) { | ||
var result = self._(stream, i); | ||
if (!result.status) return result; | ||
var nextParser = (next instanceof Parser ? next : next(result.value)); | ||
return furthestBacktrackFor(nextParser._(stream, result.index), result); | ||
}); | ||
assertParser(next); | ||
return seq(this, next).map(function(results) { return results[1]; }); | ||
}; | ||
@@ -343,3 +350,11 @@ | ||
//- Monad | ||
_.chain = _.then; | ||
_.chain = function(f) { | ||
var self = this; | ||
return Parser(function(stream, i) { | ||
var result = self._(stream, i); | ||
if (!result.status) return result; | ||
var nextParser = f(result.value); | ||
return furthestBacktrackFor(nextParser._(stream, result.index), result); | ||
}); | ||
}; | ||
}); |
@@ -12,8 +12,19 @@ suite('parser', function() { | ||
var index = Parsimmon.index; | ||
var lazy = Parsimmon.lazy; | ||
test('Parsimmon.string', function() { | ||
var parser = string('x'); | ||
assert.equal(parser.parse('x'), 'x'); | ||
assert.throws(function() { parser.parse('y') }, | ||
"Parse Error: expected 'x' at character 0, got 'y'\n parsing: 'y'"); | ||
var res = parser.parse('x'); | ||
assert.ok(res.status); | ||
assert.equal(res.value, 'x'); | ||
res = parser.parse('y') | ||
assert.ok(!res.status) | ||
assert.equal("'x'", res.expected); | ||
assert.equal(0, res.index); | ||
assert.equal( | ||
"expected 'x' at character 0, got 'y'", | ||
Parsimmon.formatError('y', res) | ||
); | ||
}); | ||
@@ -24,8 +35,14 @@ | ||
assert.equal(parser.parse('1'), '1'); | ||
assert.equal(parser.parse('4'), '4'); | ||
assert.throws(function() { parser.parse('x'); }, | ||
"Parse Error: expected /[0-9]/ at character 0, got 'x'\n parsing: 'x'"); | ||
assert.throws(function() { parser.parse('x0'); }, | ||
"Parse Error: expected /[0-9]/ at character 0, got 'x0'\n parsing: 'x0'"); | ||
assert.equal(parser.parse('1').value, '1'); | ||
assert.equal(parser.parse('4').value, '4'); | ||
assert.deepEqual(parser.parse('x0'), { | ||
status: false, | ||
index: 0, | ||
expected: /[0-9]/ | ||
}); | ||
assert.deepEqual(parser.parse('0x'), { | ||
status: false, | ||
index: 1, | ||
expected: 'EOF' | ||
}); | ||
}); | ||
@@ -36,15 +53,22 @@ | ||
var parser = string('x').then(string('y')); | ||
assert.equal(parser.parse('xy'), 'y'); | ||
assert.throws(function() { parser.parse('y'); }, | ||
"Parse Error: expected 'x' at character 0, got 'y'\n parsing: 'y'"); | ||
assert.throws(function() { parser.parse('xz'); }, | ||
"Parse Error: expected 'y' at character 1, got '...z'\n parsing: 'xz'"); | ||
assert.deepEqual(parser.parse('xy'), { status: true, value: 'y' }); | ||
assert.deepEqual(parser.parse('y'), { | ||
status: false, | ||
expected: "'x'", | ||
index: 0 | ||
}) | ||
assert.deepEqual(parser.parse('xz'), { | ||
status: false, | ||
expected: "'y'", | ||
index: 1 | ||
}); | ||
}); | ||
}); | ||
suite('chain', function() { | ||
test('asserts that a parser is returned', function() { | ||
var parser1 = letter.then(function() { return 'not a parser' }); | ||
var parser1 = letter.chain(function() { return 'not a parser' }); | ||
assert.throws(function() { parser1.parse('x'); }); | ||
var parser2 = letter.then('x'); | ||
assert.throws(function() { letter.parse('xx'); }); | ||
assert.throws(function() { letter.then('x'); }); | ||
}); | ||
@@ -54,3 +78,3 @@ | ||
var piped; | ||
var parser = string('x').then(function(x) { | ||
var parser = string('x').chain(function(x) { | ||
piped = x; | ||
@@ -60,5 +84,5 @@ return string('y'); | ||
assert.equal(parser.parse('xy'), 'y'); | ||
assert.deepEqual(parser.parse('xy'), { status: true, value: 'y'}); | ||
assert.equal(piped, 'x'); | ||
assert.throws(function() { parser.parse('x'); }); | ||
assert.ok(!parser.parse('x').status); | ||
}); | ||
@@ -76,3 +100,3 @@ }); | ||
assert.equal(parser.parse('x'), 'y') | ||
assert.deepEqual(parser.parse('x'), { status: true, value: 'y' }); | ||
assert.equal(piped, 'x'); | ||
@@ -84,11 +108,4 @@ }); | ||
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); | ||
assert.deepEqual(oneParser.parse('x'), { status: true, value: 1 }); | ||
}); | ||
@@ -101,4 +118,4 @@ }); | ||
assert.equal(parser.parse('xy'), 'x'); | ||
assert.throws(function() { parser.parse('x'); }); | ||
assert.deepEqual(parser.parse('xy'), { status: true, value: 'x' }); | ||
assert.ok(!parser.parse('x').status); | ||
}); | ||
@@ -111,23 +128,19 @@ }); | ||
assert.equal(parser.parse('x'), 'x'); | ||
assert.equal(parser.parse('y'), 'y'); | ||
assert.throws(function() { parser.parse('z') }); | ||
assert.equal(parser.parse('x').value, 'x'); | ||
assert.equal(parser.parse('y').value, 'y'); | ||
assert.ok(!parser.parse('z').status); | ||
}); | ||
test('with then', function() { | ||
test('with chain', function() { | ||
var parser = string('\\') | ||
.then(function() { | ||
return string('y') | ||
.chain(function(x) { | ||
return string('y'); | ||
}).or(string('z')); | ||
assert.equal(parser.parse('\\y'), 'y'); | ||
assert.equal(parser.parse('z'), 'z'); | ||
assert.throws(function() { parser.parse('\\z') }); | ||
assert.equal(parser.parse('\\y').value, 'y'); | ||
assert.equal(parser.parse('z').value, 'z'); | ||
assert.ok(!parser.parse('\\z').status); | ||
}); | ||
}); | ||
function assertEqualArray(arr1, arr2) { | ||
assert.equal(arr1.join(), arr2.join()); | ||
} | ||
suite('many', function() { | ||
@@ -137,7 +150,7 @@ test('simple case', function() { | ||
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'); }); | ||
assert.deepEqual(letters.parse('x').value, ['x']); | ||
assert.deepEqual(letters.parse('xyz').value, ['x','y','z']); | ||
assert.deepEqual(letters.parse('').value, []); | ||
assert.ok(!letters.parse('1').status); | ||
assert.ok(!letters.parse('xyz1').status); | ||
}); | ||
@@ -148,5 +161,5 @@ | ||
assert.equal(parser.parse('y'), 'y'); | ||
assert.equal(parser.parse('xy'), 'y'); | ||
assert.equal(parser.parse('xxxxxy'), 'y'); | ||
assert.equal(parser.parse('y').value, 'y'); | ||
assert.equal(parser.parse('xy').value, 'y'); | ||
assert.equal(parser.parse('xxxxxy').value, 'y'); | ||
}); | ||
@@ -159,4 +172,4 @@ }); | ||
assertEqualArray(zeroLetters.parse(''), []); | ||
assert.throws(function() { zeroLetters.parse('x'); }); | ||
assert.deepEqual(zeroLetters.parse('').value, []); | ||
assert.ok(!zeroLetters.parse('x').status); | ||
}); | ||
@@ -167,11 +180,11 @@ | ||
assertEqualArray(threeLetters.parse('xyz'), ['x', 'y', 'z']); | ||
assert.throws(function() { threeLetters.parse('xy'); }); | ||
assert.throws(function() { threeLetters.parse('xyzw'); }); | ||
assert.deepEqual(threeLetters.parse('xyz').value, ['x', 'y', 'z']); | ||
assert.ok(!threeLetters.parse('xy').status); | ||
assert.ok(!threeLetters.parse('xyzw').status); | ||
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'); }); | ||
assert.equal(thenDigit.parse('xyz1').value, '1'); | ||
assert.ok(!thenDigit.parse('xy1').status); | ||
assert.ok(!thenDigit.parse('xyz').status); | ||
assert.ok(!thenDigit.parse('xyzw').status); | ||
}); | ||
@@ -182,16 +195,16 @@ | ||
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'); }); | ||
assert.deepEqual(someLetters.parse('xy').value, ['x', 'y']); | ||
assert.deepEqual(someLetters.parse('xyz').value, ['x', 'y', 'z']); | ||
assert.deepEqual(someLetters.parse('xyzw').value, ['x', 'y', 'z', 'w']); | ||
assert.ok(!someLetters.parse('xyzwv').status); | ||
assert.ok(!someLetters.parse('x').status); | ||
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'); }); | ||
assert.equal(thenDigit.parse('xy1').value, '1'); | ||
assert.equal(thenDigit.parse('xyz1').value, '1'); | ||
assert.equal(thenDigit.parse('xyzw1').value, '1'); | ||
assert.ok(!thenDigit.parse('xy').status); | ||
assert.ok(!thenDigit.parse('xyzw').status); | ||
assert.ok(!thenDigit.parse('xyzwv1').status); | ||
assert.ok(!thenDigit.parse('x1').status); | ||
}); | ||
@@ -201,7 +214,6 @@ | ||
var atMostTwo = letter.atMost(2); | ||
debugger | ||
assertEqualArray(atMostTwo.parse(''), []); | ||
assertEqualArray(atMostTwo.parse('a'), ['a']); | ||
assertEqualArray(atMostTwo.parse('ab'), ['a', 'b']); | ||
assert.throws(function() { atMostTwo.parse('abc'); }); | ||
assert.deepEqual(atMostTwo.parse('').value, []); | ||
assert.deepEqual(atMostTwo.parse('a').value, ['a']); | ||
assert.deepEqual(atMostTwo.parse('ab').value, ['a', 'b']); | ||
assert.ok(!atMostTwo.parse('abc').status); | ||
}); | ||
@@ -212,5 +224,5 @@ | ||
assertEqualArray(atLeastTwo.parse('xy'), ['x', 'y']); | ||
assertEqualArray(atLeastTwo.parse('xyzw'), ['x', 'y', 'z', 'w']); | ||
assert.throws(function() { atLeastTwo.parse('x'); }); | ||
assert.deepEqual(atLeastTwo.parse('xy').value, ['x', 'y']); | ||
assert.deepEqual(atLeastTwo.parse('xyzw').value, ['x', 'y', 'z', 'w']); | ||
assert.ok(!atLeastTwo.parse('x').status); | ||
}); | ||
@@ -224,9 +236,12 @@ }); | ||
test('use Parsimmon.fail to fail dynamically', function() { | ||
var parser = any.then(function(ch) { | ||
var parser = any.chain(function(ch) { | ||
return fail('a character besides ' + ch); | ||
}).or(string('x')); | ||
assert.throws(function() { parser.parse('y'); }, | ||
"Parse Error: expected a character besides y, got the end of the string\n parsing: 'y'"); | ||
assert.equal(parser.parse('x'), 'x'); | ||
assert.deepEqual(parser.parse('y'), { | ||
status: false, | ||
index: 1, | ||
expected: 'a character besides y' | ||
}); | ||
assert.equal(parser.parse('x').value, 'x'); | ||
}); | ||
@@ -240,3 +255,3 @@ | ||
.then(string('+').or(string('*'))) | ||
.then(function(operator) { | ||
.chain(function(operator) { | ||
if (operator === allowedOperator) return succeed(operator); | ||
@@ -249,10 +264,16 @@ else return fail(allowedOperator); | ||
allowedOperator = '+'; | ||
assert.equal(parser.parse('x+y'), '+'); | ||
assert.throws(function() { parser.parse('x*y'); }, | ||
"Parse Error: expected + at character 2, got '...y'\n parsing: 'x*y'"); | ||
assert.equal(parser.parse('x+y').value, '+'); | ||
assert.deepEqual(parser.parse('x*y'), { | ||
status: false, | ||
index: 2, | ||
expected: '+' | ||
}); | ||
allowedOperator = '*'; | ||
assert.equal(parser.parse('x*y'), '*'); | ||
assert.throws(function() { parser.parse('x+y'); }, | ||
"Parse Error: expected * at character 2, got '...y'\n parsing: 'x+y'"); | ||
assert.equal(parser.parse('x*y').value, '*'); | ||
assert.deepEqual(parser.parse('x+y'), { | ||
status: false, | ||
index: 2, | ||
expected: '*' | ||
}); | ||
}); | ||
@@ -264,4 +285,4 @@ }); | ||
assert.equal(parser.parse(' '), ' ') | ||
assert.equal(parser.parse('x'), 'default'); | ||
assert.equal(parser.parse(' ').value, ' ') | ||
assert.equal(parser.parse('x').value, 'default'); | ||
}); | ||
@@ -271,5 +292,5 @@ | ||
var parser = regex(/^x*/).then(index); | ||
assert.equal(parser.parse(''), 0); | ||
assert.equal(parser.parse('xx'), 2); | ||
assert.equal(parser.parse('xxxx'), 4); | ||
assert.equal(parser.parse('').value, 0); | ||
assert.equal(parser.parse('xx').value, 2); | ||
assert.equal(parser.parse('xxxx').value, 4); | ||
}); | ||
@@ -280,4 +301,4 @@ | ||
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 }); | ||
assert.deepEqual(parser.parse('').value, { start: 0, value: '', end: 0 }); | ||
assert.deepEqual(parser.parse(' yy ').value, { start: 1, value: 'yy', end: 3 }); | ||
}); | ||
@@ -293,4 +314,7 @@ | ||
assert.throws(function() { parser.parse('abc'); }, | ||
"Parse Error: expected 'def', got the end of the string\n parsing: 'abc'"); | ||
assert.deepEqual(parser.parse('abc'), { | ||
status: false, | ||
index: 3, | ||
expected: "'def'" | ||
}); | ||
}); | ||
@@ -301,4 +325,7 @@ | ||
assert.throws(function() { parser.parse('abc'); }, | ||
"Parse Error: expected 'd', got the end of the string\n parsing: 'abc'"); | ||
assert.deepEqual(parser.parse('abc'), { | ||
status: false, | ||
index: 3, | ||
expected: "'d'" | ||
}); | ||
}); | ||
@@ -310,4 +337,7 @@ | ||
assert.throws(function() { parser.parse('abcdef'); }, | ||
"Parse Error: expected 'g', got the end of the string\n parsing: 'abcdef'"); | ||
assert.deepEqual(parser.parse('abcdef'), { | ||
status: false, | ||
index: 6, | ||
expected: "'g'" | ||
}); | ||
}); | ||
@@ -318,13 +348,22 @@ }); | ||
test('prefer longest branch even in a .many()', function() { | ||
var list = lazy(function() { | ||
return optWhitespace.then(atom.or(sexpr)).skip(optWhitespace).many(); | ||
}); | ||
var atom = regex(/^[^()\s]+/); | ||
var sexpr = string('(').then(function() { return list; }).skip(string(')')); | ||
var list = optWhitespace.then(atom.or(sexpr)).skip(optWhitespace).many(); | ||
var sexpr = string('(').then(list).skip(string(')')); | ||
// assert.deepEqual(list.parse('(a b) (c ((() d)))'), [['a', 'b'], ['c', [[[], 'd']]]]); | ||
assert.deepEqual(list.parse('(a b) (c ((() d)))').value, | ||
[['a', 'b'], ['c', [[[], 'd']]]]); | ||
assert.throws(function() { list.parse('(a b ()) c)'); }, | ||
"Parse Error: expected EOF at character 10, got '...)'\n parsing: '(a b ()) c)'"); | ||
assert.deepEqual(list.parse('(a b ()) c)'), { | ||
status: false, | ||
index: 10, | ||
expected: 'EOF' | ||
}); | ||
assert.throws(function() { list.parse('(a (b)) (() c'); }, | ||
"Parse Error: expected ')', got the end of the string\n parsing: '(a (b)) (() c'"); | ||
assert.deepEqual(list.parse('(a (b)) (() c'), { | ||
status: false, | ||
index: 13, | ||
expected: "')'" | ||
}); | ||
}); | ||
@@ -335,6 +374,10 @@ | ||
assert.deepEqual(parser.parse('aaabcdefaa'), ['a', 'a', 'def', 'a', 'a']); | ||
assert.deepEqual(parser.parse('aaabcdefaa').value, | ||
['a', 'a', 'def', 'a', 'a']); | ||
assert.throws(function() { parser.parse('aaabcde'); }, | ||
"Parse Error: expected 'def' at character 5, got '...de'\n parsing: 'aaabcde'"); | ||
assert.deepEqual(parser.parse('aaabcde'), { | ||
status: false, | ||
index: 5, | ||
expected: "'def'" | ||
}); | ||
}); | ||
@@ -347,7 +390,13 @@ }); | ||
assert.throws(function() { parser.parse('aabcde'); }, | ||
"Parse Error: expected 'def' at character 4, got '...de'\n parsing: 'aabcde'"); | ||
assert.deepEqual(parser.parse('aabcde'), { | ||
status: false, | ||
index: 4, | ||
expected: "'def'" | ||
}); | ||
assert.throws(function() { parser.parse('aaaaabcde'); }, | ||
"Parse Error: expected 'def' at character 7, got '...de'\n parsing: 'aaaaabcde'"); | ||
assert.deepEqual(parser.parse('aaaaabcde'), { | ||
status: false, | ||
index: 7, | ||
expected: "'def'" | ||
}); | ||
}); | ||
@@ -354,0 +403,0 @@ }); |
1002442
1290
187