blob: db76ed5aebcb71000f7b121de85e181d35884d8b [file] [log] [blame]
var Parser = P(function(_, super_, Parser) {
// The Parser object is a wrapper for a parser function.
// Externally, you use one to parse a string by calling
// var result = SomeParser.parse('Me Me Me! Parse Me!');
// You should never call the constructor, rather you should
// construct your Parser from the base parsers and the
// parser combinator methods.
function parseError(stream, message) {
if (stream) {
stream = "'"+stream+"'";
}
else {
stream = 'EOF';
}
throw 'Parse Error: '+message+' at '+stream;
}
_.init = function(body) { this._ = body; };
_.parse = function(stream) {
return this.skip(eof)._(''+stream, success, parseError);
function success(stream, result) { return result; }
};
// -*- primitive combinators -*- //
_.or = function(alternative) {
pray('or is passed a parser', alternative instanceof Parser);
var self = this;
return Parser(function(stream, onSuccess, onFailure) {
return self._(stream, onSuccess, failure);
function failure(newStream) {
return alternative._(stream, onSuccess, onFailure);
}
});
};
_.then = function(next) {
var self = this;
return Parser(function(stream, onSuccess, onFailure) {
return self._(stream, success, onFailure);
function success(newStream, result) {
var nextParser = (next instanceof Parser ? next : next(result));
pray('a parser is returned', nextParser instanceof Parser);
return nextParser._(newStream, onSuccess, onFailure);
}
});
};
// -*- optimized iterative combinators -*- //
_.many = function() {
var self = this;
return Parser(function(stream, onSuccess, onFailure) {
var xs = [];
while (self._(stream, success, failure));
return onSuccess(stream, xs);
function success(newStream, x) {
stream = newStream;
xs.push(x);
return true;
}
function failure() {
return false;
}
});
};
_.times = function(min, max) {
if (arguments.length < 2) max = min;
var self = this;
return Parser(function(stream, onSuccess, onFailure) {
var xs = [];
var result = true;
var failure;
for (var i = 0; i < min; i += 1) {
result = self._(stream, success, firstFailure);
if (!result) return onFailure(stream, failure);
}
for (; i < max && result; i += 1) {
result = self._(stream, success, secondFailure);
}
return onSuccess(stream, xs);
function success(newStream, x) {
xs.push(x);
stream = newStream;
return true;
}
function firstFailure(newStream, msg) {
failure = msg;
stream = newStream;
return false;
}
function secondFailure(newStream, msg) {
return false;
}
});
};
// -*- higher-level combinators -*- //
_.result = function(res) { return this.then(succeed(res)); };
_.atMost = function(n) { return this.times(0, n); };
_.atLeast = function(n) {
var self = this;
return self.times(n).then(function(start) {
return self.many().map(function(end) {
return start.concat(end);
});
});
};
_.map = function(fn) {
return this.then(function(result) { return succeed(fn(result)); });
};
_.skip = function(two) {
return this.then(function(result) { return two.result(result); });
};
// -*- primitive parsers -*- //
var string = this.string = function(str) {
var len = str.length;
var expected = "expected '"+str+"'";
return Parser(function(stream, onSuccess, onFailure) {
var head = stream.slice(0, len);
if (head === str) {
return onSuccess(stream.slice(len), head);
}
else {
return onFailure(stream, expected);
}
});
};
var regex = this.regex = function(re) {
pray('regexp parser is anchored', re.toString().charAt(1) === '^');
var expected = 'expected '+re;
return Parser(function(stream, onSuccess, onFailure) {
var match = re.exec(stream);
if (match) {
var result = match[0];
return onSuccess(stream.slice(result.length), result);
}
else {
return onFailure(stream, expected);
}
});
};
var succeed = Parser.succeed = function(result) {
return Parser(function(stream, onSuccess) {
return onSuccess(stream, result);
});
};
var fail = Parser.fail = function(msg) {
return Parser(function(stream, _, onFailure) {
return onFailure(stream, msg);
});
};
var letter = Parser.letter = regex(/^[a-z]/i);
var letters = Parser.letters = regex(/^[a-z]*/i);
var digit = Parser.digit = regex(/^[0-9]/);
var digits = Parser.digits = regex(/^[0-9]*/);
var whitespace = Parser.whitespace = regex(/^\s+/);
var optWhitespace = Parser.optWhitespace = regex(/^\s*/);
var any = Parser.any = Parser(function(stream, onSuccess, onFailure) {
if (!stream) return onFailure(stream, 'expected any character');
return onSuccess(stream.slice(1), stream.charAt(0));
});
var all = Parser.all = Parser(function(stream, onSuccess, onFailure) {
return onSuccess('', stream);
});
var eof = Parser.eof = Parser(function(stream, onSuccess, onFailure) {
if (stream) return onFailure(stream, 'expected EOF');
return onSuccess(stream, stream);
});
});