"use strict";
/**
* This module exports the entire interface through a single object. Refer to
* the documentation for each individual submodule for more specific docs.
*
* @module mona
*/
/**
* Parser execution api
* @module mona/api
*/
var VERSION = "0.8.1";
/**
* Executes a parser and returns the result.
*
* @param {Function} parser - The parser to execute.
* @param {String} string - String to parse.
* @param {Object} [opts] - Options object.
* @param {Boolean} [opts.throwOnError=true] - If truthy, throws a ParserError
* if the parser fails and returns
* ParserState instead of its value.
* @param {String} [opts.fileName] - filename to use for error messages.
* @memberof module:mona/api
* @instance
*
* @example
* parse(token(), "a"); // => "a"
*/
function parse(parser, string, opts) {
opts = opts || {};
opts.throwOnError = typeof opts.throwOnError === "undefined" ?
true : opts.throwOnError;
if (!opts.allowTrailing) {
parser = followedBy(parser, eof());
}
var parserState = parser(
new ParserState(undefined,
string,
0,
opts.userState,
opts.position || new SourcePosition(opts.fileName),
false));
if (parserState.failed && opts.throwOnError) {
throw parserState.error;
} else if (parserState.failed && !opts.throwOnError) {
return parserState.error;
} else if (!parserState.failed && !opts.throwOnError) {
return parserState;
} else if (opts.returnState) {
return parserState;
} else {
return parserState.value;
}
}
/**
* Executes a parser asynchronously, returning an object that can be used to
* manage the parser state. Unless the parser given tries to match eof(),
* parsing will continue until the parser's done() function is called.
*
* @param {Function} parser - The parser to execute.
* @param {AsyncParserCallback} callback - node-style 2-arg callback executed
* once per successful application of
* `parser`.
* @param {Object} [opts] - Options object.
* @param {String} [opts.fileName] - filename to use for error messages.
* @memberof module:mona/api
* @instance
*
* @example
* var handle = parseAsync(token(), function(tok) {
* console.log("Got a token: ", tok);
* });
* handle.data("foobarbaz");
*/
function parseAsync(parser, callback, opts) {
opts = copy(opts || {});
// Force the matter in case someone gets clever.
opts.throwOnError = true;
opts.returnState = true;
opts.allowTrailing = true;
var done = false,
buffer = "";
function exec() {
if (done && !buffer.length) {
return false;
}
var res;
try {
res = parse(collect(parser, {min: 1}), buffer, opts);
opts.position = res.position;
buffer = res.input.slice(res.offset);
} catch (e) {
if (!e.wasEof || done) {
callback(e);
}
return false;
}
res.value.forEach(function(val) {
callback(null, val);
});
return true;
}
function errIfDone(cb) {
return function() {
if (done) {
throw new Error("AsyncParser closed");
} else {
return cb.apply(null, arguments);
}
};
}
var handle = {
done: errIfDone(function() {
done = true;
buffer = "";
while(exec()){}
return handle;
}),
data: errIfDone(function(data) {
buffer += data;
while(exec()){}
return handle;
}),
error: errIfDone(function(error) {
done = true;
callback(error);
return handle;
})
};
return handle;
}
/**
* Represents a source location.
* @typedef {Object} SourcePosition
* @property {String} name - Optional sourcefile name.
* @property {Integer} line - Line number, starting from 1.
* @property {Integer} column - Column number in the line, starting from 1.
* @memberof module:mona/api
* @instance
*/
function SourcePosition(name, line, column) {
this.name = name;
this.line = line || 1;
this.column = column || 0;
}
/**
* Information about a parsing failure.
* @typedef {Object} ParserError
* @property {api.SourcePosition} position - Source position for the error.
* @property {Array} messages - Array containing relevant error messages.
* @property {String} type - The type of parsing error.
* @memberof module:mona/api
*/
function ParserError(pos, messages, type, wasEof) {
if (Error.captureStackTrace) {
// For pretty-printing errors on node.
Error.captureStackTrace(this, this);
}
this.position = pos;
this.messages = messages;
this.type = type;
this.wasEof = wasEof;
this.message = ("(line "+ this.position.line +
", column "+this.position.column+") "+
this.messages.join("\n"));
}
ParserError.prototype = new Error();
ParserError.prototype.constructor = ParserError;
ParserError.prototype.name = "ParserError";
/**
* Core parsers
*
* @module mona/core
*/
/**
* A function accepting parserState as input that transforms it and returns a
* new parserState.
* @callback {Function} Parser
* @param {ParserState} state - Current parser state.
* @returns {ParserState} state' - Transformed parser state.
* @memberof module:mona/core
*/
/**
* Returns a parser that always succeeds without consuming input.
*
* @param [val=undefined] - value to use as this parser's value.
* @memberof module:mona/core
* @instance
*
* @example
* parse(value("foo"), ""); // => "foo"
*/
function value(val) {
return function(parserState) {
var newState = copy(parserState);
newState.value = val;
return newState;
};
}
/**
* Returns a parser that calls `fun` on the value resulting from running
* `parser` on the current parsing state. Fails without executing `fun` if
* `parser` fails.
*
* @param {Parser} parser - The parser to execute.
* @param {Function} fun - Function called with the resulting value of
* `parser`. Must return a parser.
* @memberof module:mona/core
* @instance
*
* @example
* parse(bind(token(), function(x) { return value(x+"!"); }), "a"); // => "a!"
*/
function bind(parser, fun) {
return function(parserState) {
var newParserState = parser(parserState);
if (!(newParserState instanceof ParserState)) {
throw new Error("Parsers must return a parser state object");
}
if (newParserState.failed) {
return newParserState;
} else {
return fun(newParserState.value)(newParserState);
}
};
}
/**
* Returns a parser that always fails without consuming input. Automatically
* includes the line and column positions in the final ParserError.
*
* @param {String} msg - Message to report with the failure.
* @param {String} type - A type to apply to the ParserError.
* @memberof module:mona/core
* @instance
*/
function fail(msg, type) {
msg = msg || "parser error";
type = type || "failure";
return function(parserState) {
parserState = copy(parserState);
parserState.failed = true;
var newError = new ParserError(parserState.position, [msg],
type, type === "eof");
parserState.error = mergeErrors(parserState.error, newError);
return parserState;
};
}
/**
* Returns a parser that will label a `parser` failure by replacing its error
* messages with `msg`.
*
* @param {Parser} parser - Parser whose errors to replace.
* @param {String} msg - Error message to replace errors with.
* @memberof module:mona/core
* @instance
*
* @example
* parse(token(), ""); // => unexpected eof
* parse(label(token(), "thing"), ""); // => expected thing
*/
function label(parser, msg) {
return function(parserState) {
var newState = parser(parserState);
if (newState.failed) {
newState = copy(newState);
newState.error = new ParserError(newState.error.position,
["expected "+msg],
"expectation",
newState.error.wasEof);
}
return newState;
};
}
/**
* Returns a parser that consumes a single item from the input, or fails with an
* unexpected eof error if there is no input left.
*
* @param {Integer} [count=1] - number of tokens to consume. Must be > 0.
* @memberof module:mona/core
* @instance
*
* @example
* parse(token(), "a"); // => "a"
*/
function token(count) {
count = count || 1; // force 0 to 1, as well.
return function(parserState) {
var input = parserState.input,
offset = parserState.offset,
newOffset = offset + count,
newParserState = copy(parserState),
newPosition = copy(parserState.position);
newParserState.position = newPosition;
for (var i = offset; i < newOffset && input.length >= i; i++) {
if (input.charAt(i) === "\n") {
newPosition.column = 0;
newPosition.line += 1;
} else {
newPosition.column += 1;
}
}
newParserState.offset = newOffset;
if (input.length >= newOffset) {
newParserState.value = input.slice(offset, newOffset);
return newParserState;
} else {
return fail("unexpected eof", "eof")(newParserState);
}
};
}
/**
* Returns a parser that succeeds with a value of `true` if there is no more
* input to consume.
*
* @memberof module:mona/core
* @instance
*
* @example
* parse(eof(), ""); // => true
*/
function eof() {
return function(parserState) {
if (parserState.input.length === parserState.offset) {
return value(true)(parserState);
} else {
return fail("expected end of input", "expectation")(parserState);
}
};
}
/**
* Delays calling of a parser constructor function until parse-time. Useful for
* recursive parsers that would otherwise blow the stack at construction time.
*
* @param {Function} constructor - A function that returns a Parser.
* @param {...*} args - Arguments to apply to the constructor.
* @memberof module:mona/core
* @instance
*
* @example
* // The following would usually result in an infinite loop:
* function foo() {
* return or(x(), foo());
* }
* // But you can use delay() to remedy this...
* function foo() {
* return or(x(), delay(foo));
* }
*/
function delay(constructor) {
var args = [].slice.call(arguments, 1);
return function(parserState) {
return constructor.apply(null, args)(parserState);
};
}
/**
* Debugger parser that logs the ParserState with a tag.
*
* @param {Parser} parser - Parser to wrap.
* @param {String} tag - Tag to use when logging messages.
* @param {String} [level="log"] - 'log', 'info', 'debug', 'warn', 'error'.
* @memberof module:mona/core
* @instance
*/
function log(parser, tag, level) {
level = level || "log";
return function(parserState) {
var newParserState = parser(parserState);
console[level](tag+" :: ", parserState, " => ", newParserState);
return newParserState;
};
}
/**
* Returns a parser that transforms the resulting value of a successful
* application of its given parser. This function is a lot like `bind`, except
* it always succeeds if its parser succeeds, and is expected to return a
* transformed value, instead of another parser.
*
* @param {Function} transformer - Function called on `parser`'s value. Its
* return value will be used as the `map`
* parser's value.
* @param {Parser} parser - Parser that will yield the input value.
* @memberof module:mona/core
* @instance
*
* @example
* parse(map(parseFloat, text()), "1234.5"); // => 1234.5
*/
function map(transformer, parser) {
return bind(parser, function(result) {
return value(transformer(result));
});
}
/**
* Returns a parser that returns an object with a single key whose value is the
* result of the given parser.
*
* @param {Parser} parser - Parser whose value will be tagged.
* @param {String} tag - String to use as the object's key.
* @memberof module:mona/core
* @instance
*
* @example
* parse(tag(token(), "myToken"), "a"); // => {myToken: "a"}
*/
function tag(parser, key) {
return map(function(x) { var ret = {}; ret[key] = x; return ret; }, parser);
}
/**
* Returns a parser that runs a given parser without consuming input, while
* still returning a success or failure.
*
* @param {Parser} test - Parser to execute.
* @memberof module:mona/core
* @instance
*
* @example
* parse(and(lookAhead(token()), token()), "a"); // => "a"
*/
function lookAhead(parser) {
return function(parserState) {
var ret = parser(parserState),
newState = copy(parserState);
newState.value = ret.value;
return newState;
};
}
/**
* Returns a parser that succeeds if `predicate` returns true when called on a
* parser's result.
*
* @param {Function} predicate - Tests a parser's result.
* @param {Parser} [parser=token()] - Parser to run.
* @memberof module:mona/core
* @instance
*
* @example
* parse(is(function(x) { return x === "a"; }), "a"); // => "a"
*/
function is(predicate, parser) {
return bind(parser || token(), function(x) {
return predicate(x) ? value(x) : fail();
});
}
/**
* Returns a parser that succeeds if `predicate` returns false when called on a
* parser's result.
*
* @param {Function} predicate - Tests a parser's result.
* @param {Parser} [parser=token()] - Parser to run.
* @memberof module:mona/core
* @instance
*
* @example
* parse(isNot(function(x) { return x === "a"; }), "b"); // => "b"
*/
function isNot(predicate, parser) {
return is(function(x) { return !predicate(x); }, parser);
}
/**
* Parser combinators for higher-order interaction between parsers.
*
* @module mona/combinators
*/
/**
* Returns a parser that succeeds if all the parsers given to it succeed. The
* returned parser uses the value of the last successful parser.
*
* @param {...Parser} parsers - One or more parsers to execute.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(and(token(), token()), "ab"); // => "b"
*/
function and(firstParser) {
var moreParsers = [].slice.call(arguments, 1);
if (!firstParser) {
throw new Error("and() requires at least one parser");
}
return bind(firstParser, function(result) {
return moreParsers.length ?
and.apply(null, moreParsers) :
value(result);
});
}
/**
* Returns a parser that succeeds if one of the parsers given to it
* suceeds. Uses the value of the first successful parser.
*
* @param {...Parser} parsers - One or more parsers to execute.
* @param {String} [label] - Label to replace the full message with.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(or(string("foo"), string("bar")), "bar"); // => "bar"
*/
function or() {
var errors = [];
function orHelper() {
var parsers = [].slice.call(arguments);
return function(parserState) {
var res = parsers[0](parserState);
if (res.failed) {
errors.push(res.error);
}
if (res.failed && parsers[1]) {
return orHelper.apply(null, parsers.slice(1))(parserState);
} else if (res.failed) {
var finalState = copy(res);
finalState.error = errors.reduce(function(err1, err2) {
return mergeErrors(err1, err2);
});
return finalState;
} else {
return res;
}
};
}
var labelMsg = (typeof arguments[arguments.length-1] === "string" &&
arguments[arguments.length-1]),
args = labelMsg ?
[].slice.call(arguments, 0, arguments.length-1) : arguments,
parser = orHelper.apply(null, args);
if (labelMsg) {
return label(parser, labelMsg);
} else {
return parser;
}
}
/**
* Returns a parser that returns the result of `parser` if it succeeds,
* otherwise succeeds with a value of `undefined` without consuming input.
*
* @param {Parser} parser - Parser to try.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(maybe(token()), ""); // => undefined
*/
function maybe(parser) {
return or(parser, value());
}
/**
* Returns a parser that succeeds if `parser` fails. Does not consume.
*
* @param {Parser} parser - parser to test.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(and(not(string("a")), token()), "b"); // => "b"
*/
function not(parser) {
return function(parserState) {
return parser(parserState).failed ?
value(true)(parserState) :
fail("expected parser to fail", "expectation")(parserState);
};
}
/**
* Returns a parser that works like `and`, but fails if the first parser given
* to it succeeds. Like `and`, it returns the value of the last successful
* parser.
*
* @param {Parser} notParser - If this parser succeeds, `unless` will fail.
* @param {...Parser} moreParsers - Rest of the parses to test.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(unless(string("a"), token()), "b"); // => "b"
*/
function unless(parser) {
var moreParsers = [].slice.call(arguments, 1);
return and.apply(null, [not(parser)].concat(moreParsers));
}
/**
* Returns a parser that will execute `fun` while handling the parserState
* internally, allowing the body of `fun` to be written sequentially. The
* purpose of this parser is to simulate `do` notation and prevent the need for
* heavily-nested `bind` calls.
*
* The `fun` callback will receive a function `s` which should be called with
* each parser that will be executed, which will update the internal
* parserState. The return value of the callback must be a parser.
*
* If any of the parsers fail, sequence will exit immediately, and the entire
* sequence will fail with that parser's reason.
*
* @param {SequenceFn} fun - A sequence callback function to execute.
* @memberof module:mona/combinators
* @instance
*
* @example
* mona.sequence(function(s) {
* var x = s(mona.token());
* var y = s(mona.string('b'));
* return mona.value(x+y);
* });
*/
function sequence(fun) {
return function(parserState) {
var state = parserState, failwhale = {};
function s(parser) {
state = parser(state);
if (state.failed) {
throw failwhale;
} else {
return state.value;
}
}
try {
var ret = fun(s);
if (typeof ret !== "function") {
throw new Error("sequence function must return a parser");
}
var newState = ret(state);
if (!(newState instanceof ParserState)) {
throw new Error("sequence function must return a parser");
}
return newState;
} catch(x) {
if (x === failwhale) {
return state;
} else {
throw x;
}
}
};
}
/**
* Called by `sequence` to handle sequential syntax for parsing. Called with an
* `s()` function that must be called each time a parser should be applied. The
* `s()` function will return the unwrapped value returned by the parser. If any
* of the `s()` calls fail, this callback will exit with an appropriate failure
* message, and none of the subsequent code will execute.
*
* Note that this callback may be called multiple times during parsing, and many
* of those calls might partially fail, so side-effects should be done with
* care.
*
* A `sequence` callback *must* return a `Parser`.
*
* @callback {Function} SequenceFn
* @param {Function} s - Sequencing function. Must be wrapped around a parser.
* @returns {Parser} parser - The final parser to apply before resolving
* `sequence`.
* @memberof module:mona/combinators
*/
/**
* Returns a parser that returns the result of its first parser if it succeeds,
* but fails if any of the following parsers fail.
*
* @param {Parser} parser - The value of this parser is returned if it
* succeeds.
* @param {...Parser} moreParsers - These parsers must succeed in order for
* `followedBy` to succeed.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(followedBy(string("a"), string("b")), "ab"); // => "a"
*/
function followedBy(parser) {
var parsers = [].slice.call(arguments, 1);
return bind(parser, function(result) {
return bind(and.apply(null, parsers), function() {
return value(result);
});
});
}
/**
* Returns a parser that returns an array of results that have been successfully
* parsed by `parser`, which were separated by `separator`.
*
* @param {Parser} parser - Parser for matching and collecting results.
* @param {Parser} separator - Parser for the separator
* @param {Object} [opts]
* @param {Integer} [opts.min=0] - Minimum length of the resulting array.
* @param {Integer} [opts.max=0] - Maximum length of the resulting array.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(split(token(), space()), "a b c d"); // => ["a","b","c","d"]
*/
function split(parser, separator, opts) {
opts = opts || {};
if (!opts.min) {
return or(split(parser, separator, {min: 1, max: opts.max}),
value([]));
} else {
opts = copy(opts);
opts.min = opts.min && opts.min-1;
opts.max = opts.max && opts.max-1;
return sequence(function(s) {
var x = s(parser);
var xs = s(collect(and(separator, parser), opts));
var result = [x].concat(xs);
return value(result);
});
}
}
/**
* Returns a parser that returns an array of results that have been successfully
* parsed by `parser`, separated and ended by `separator`.
*
* @param {Parser} parser - Parser for matching and collecting results.
* @param {Parser} separator - Parser for the separator
* @param {Object} [opts]
* @param {Integer} [opts.enforceEnd=true] - If true, `separator` must be at the
* end of the parse.
* @param {Integer} [opts.min=0] - Minimum length of the resulting array.
* @param {Integer} [opts.max=Infinity] - Maximum length of the resulting array.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(splitEnd(token(), space()), "a b c "); // => ["a", "b", "c"]
*/
function splitEnd(parser, separator, opts){
opts = opts || {};
var enforceEnd = typeof opts.enforceEnd === "undefined" ?
true :
opts.enforceEnd;
if (enforceEnd) {
return collect(followedBy(parser, separator), opts);
} else {
// TODO - This is bloody terrible and should die a horrible, painful death,
// but at least the tests seem to pass. :\
return sequence(function(s) {
var min = opts.min || 0,
max = opts.max || Infinity,
last;
var results = s(splitEnd(parser, separator, {min: opts.min && min-1,
max: opts.max && max-1}));
if (opts.min > results.length || opts.max) {
last = s(followedBy(parser, maybe(separator)));
return value(results.concat([last]));
} else {
last = s(maybe(parser));
if (last) {
return value(results.concat([last]));
} else {
return value(results);
}
}
});
}
}
/**
* Returns a parser that results in an array of `min` to `max` matches of
* `parser`
*
* @param {Parser} parser - Parser to match.
* @param {Object} [opts]
* @param {Integer} [opts.min=0] - Minimum number of matches.
* @param {Integer} [opts.max=Infinity] - Maximum number of matches.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(collect(token()), "abcd"); // => ["a", "b", "c", "d"]
*/
function collect(parser, opts) {
opts = opts || {};
var min = opts.min || 0,
max = typeof opts.max === "undefined" ? Infinity : opts.max;
if (min > max) { throw new Error("min must be less than or equal to max"); }
return function(parserState) {
var prev = parserState,
s = parserState,
res = [],
i = 0;
while(s = parser(s), i < max && !s.failed) {
res.push(s.value);
i++;
prev = s;
}
if (min && (res.length < min)) {
return s;
} else {
return value(res)(prev);
}
};
}
/**
* Returns a parser that results in an array of exactly `n` results for
* `parser`.
*
* @param {Parser} parser - The parser to collect results for.
* @param {Integer} n - exact number of results to collect.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(exactly(token(), 4), "abcd"); // => ["a", "b", "c", "d"]
*/
function exactly(parser, n) {
return collect(parser, {min: n, max: n});
}
/**
* Returns a parser that results in a value between an opening and closing
* parser.
*
* @param {Parser} open - Opening parser.
* @param {Parser} close - Closing parser.
* @param {Parser} parser - Parser to return the value of.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(between(string("("), string(")"), token()), "(a)"); // => "a"
*/
function between(open, close, parser) {
return and(open, followedBy(parser, close));
}
/**
* Returns a parser that skips input until `parser` stops matching.
*
* @param {Parser} parser - Determines whether to continue skipping.
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(and(skip(string("a")), token()), "aaaab"); // => "b"
*/
function skip(parser) {
return and(collect(parser), value());
}
/**
* Returns a parser that accepts a parser if its result is within range of
* `start` and `end`.
*
* @param {*} start - lower bound of the range to accept.
* @param {*} end - higher bound of the range to accept.
* @param {Parser} [parser=token()] - parser whose results to test
* @param {Function} [predicate=function(x,y){return x<=y; }] - Tests range
* @memberof module:mona/combinators
* @instance
*
* @example
* parse(range("a", "z"), "d"); // => "d"
*/
function range(start, end, parser, predicate) {
parser = parser || token();
predicate = predicate || function(x,y) { return x <= y; };
return label(bind(parser, function(result) {
if (predicate(start, result) && predicate(result, end)) {
return value(result);
} else {
return fail();
}
}), "value between {"+start+"} and {"+end+"}");
}
/**
* String-related parsers and combinators.
*
* @module mona/strings
*/
/**
* Returns a string containing the concatenated results returned by applying
* `parser`. `parser` must be a combinator that returns an array of string parse
* results.
*
* @param {Parser} parser - Parser that results in an array of strings.
* @memberof module:mona/strings
* @instance
*
* @example
* parse(stringOf(collect(token())), "aaa"); // => "aaa"
*/
function stringOf(parser) {
return bind(parser, function(xs) {
if (xs.hasOwnProperty("length") &&
xs.join) {
return value(xs.join(""));
} else {
return fail();
}
});
}
/**
* Returns a parser that succeeds if the next token or string matches one of the
* given inputs.
*
* @param {String|Array} matches - Characters or strings to match. If this
* argument is a string, it will be treated as
* if matches.split("") were passed in.
* @param {Boolean} [caseSensitive=true] - Whether to match char case exactly.
* @memberof module:mona/strings
* @instance
*
* @example
* parse(oneOf("abcd"), "c"); // => "c"
* parse(oneOf(["foo", "bar", "baz"]), "bar"); // => "bar"
*/
function oneOf(_matches, caseSensitive) {
caseSensitive = typeof caseSensitive === "undefined" ? true : caseSensitive;
var matches = typeof _matches === "string" ? _matches.split("") : _matches;
return or.apply(null, matches.map(function(m) {
return string(m, caseSensitive);
}).concat(["one of {"+matches+"}"]));
}
/**
* Returns a parser that fails if the next token or string matches one of the
* given inputs. If the third `parser` argument is given, that parser will be
* used to collect the actual value of `noneOf`.
*
* @param {String|Array} matches - Characters or strings to match. If this
* argument is a string, it will be treated as
* if matches.split("") were passed in.
* @param {Boolean} [caseSensitive=true] - Whether to match char case exactly.
* @param {Parser} [parser=token()] - What to actually parse if none of the
* given matches succeed.
* @memberof module:mona/strings
* @instance
*
* @example
* parse(noneOf("abc"), "d"); // => "d"
* parse(noneOf(["foo", "bar", "baz"]), "frob"); // => "f"
* parse(noneOf(["foo", "bar", "baz"], true, text()), "frob"); // => "frob"
*/
function noneOf(_matches, caseSensitive, parser) {
caseSensitive = typeof caseSensitive === "undefined" ? true : caseSensitive;
var matches = typeof _matches === "string" ? _matches.split("") : _matches;
return label(and(not(or.apply(null, matches.map(function(m) {
return string(m, caseSensitive);
}))), parser || token()), "none of {"+matches+"}");
}
/**
* Returns a parser that succeeds if `str` matches the next `str.length` inputs,
* consuming the string and returning it as a value.
*
* @param {String} str - String to match against.
* @param {Boolean} [caseSensitive=true] - Whether to match char case exactly.
* @memberof module:mona/strings
* @instance
*
* @example
* parse(string("foo"), "foo"); // => "foo"
*/
function string(str, caseSensitive) {
caseSensitive = typeof caseSensitive === "undefined" ? true : caseSensitive;
str = caseSensitive ? str : str.toLowerCase();
return label(sequence(function(s) {
var x = s(is(function(x) {
x = caseSensitive ? x : x.toLowerCase();
return x === str.charAt(0);
}));
var xs = (str.length > 1)?s(string(str.slice(1), caseSensitive)):"";
return value(x+xs);
}), "string matching {"+str+"}");
}
/**
* Returns a parser that matches a single non-unicode uppercase alphabetical
* character.
*
* @memberof module:mona/strings
* @instance
*
* @example
* parse(alphaUpper(), "D"); // => "D"
*/
function alphaUpper() {
return label(range("A", "Z"), "uppercase alphabetical character");
}
/**
* Returns a parser that matches a single non-unicode lowercase alphabetical
* character.
*
* @memberof module:mona/strings
* @instance
*
* @example
* parse(alphaLower(), "d"); // => "d"
*/
function alphaLower() {
return label(range("a", "z"), "lowercase alphabetical character");
}
/**
* Returns a parser that matches a single non-unicode alphabetical character.
*
* @memberof module:mona/strings
* @instance
*
* @example
* parse(alpha(), "a"); // => "a"
* parse(alpha(), "A"); // => "A"
*/
function alpha() {
return or(alphaLower(), alphaUpper(), "alphabetical character");
}
/**
* Returns a parser that parses a single digit character token from the input.
*
* @param {Integer} [base=10] - Optional base for the digit.
* @memberof module:mona/strings
* @instance
*
* @example
* parse(digit(), "5"); // => "5"
*/
function digit(base) {
base = base || 10;
return label(is(function(x) { return !isNaN(parseInt(x, base)); }),
"digit");
}
/**
* Returns a parser that matches an alphanumeric character.
*
* @param {Integer} [base=10] - Optional base for numeric parsing.
* @memberof module:mona/strings
* @instance
*
* @example
* parse(alphanum(), "1"); // => "1"
* parse(alphanum(), "a"); // => "a"
* parse(alphanum(), "A"); // => "A"
*/
function alphanum(base) {
return label(or(alpha(), digit(base)), "alphanum");
}
/**
* Returns a parser that matches one whitespace character.
*
* @memberof module:mona/strings
* @instance
*
* @example
* parse(space(), "\r"); // => "\r"
*/
function space() {
return label(oneOf(" \t\n\r"), "space");
}
/**
* Returns a parser that matches one or more whitespace characters. Returns a
* single space character as its result, regardless of which whitespace
* characters were matched.
*
* @memberof module:mona/strings
* @instance
*
* @example
* parse(spaces(), " \r\n\t \r \n"); // => " "
*/
function spaces() {
return label(and(space(), skip(space()), value(" ")), "spaces");
}
/**
* Returns a parser that collects between `min` and `max` tokens matching
* `parser`. The result is returned as a single string. This parser is
* essentially collect() for strings.
*
* @param {Parser} [parser=token()] - Parser to use to collect the results.
* @param {Object} [opts]
* @param {Integer} [opts.min=0] - Minimum number of matches.
* @param {Integer} [opts.max=Infinity] - Maximum number of matches.
* @memberof module:mona/strings
* @instance
*
* @example
* parse(text(), "abcde"); // => "abcde"
* parse(text(noneOf("a")), "bcde"); // => "bcde"
*/
function text(parser, opts) {
parser = parser || token();
opts = opts || {};
return stringOf(collect(parser, opts));
}
/**
* Returns a parser that trims any whitespace surrounding `parser`.
*
* @param {Parser} parser - Parser to match after cleaning up whitespace.
* @memberof module:mona/strings
* @instance
*
* @example
* parse(trim(token()), " \r\n a \t"); // => "a"
*/
function trim(parser) {
return between(maybe(spaces()),
maybe(spaces()),
parser);
}
/**
* Returns a parser that trims any leading whitespace before `parser`.
*
* @param {Parser} parser - Parser to match after cleaning up whitespace.
* @memberof module:mona/strings
* @instance
*
* @example
* parse(trimLeft(token()), " \r\n a"); // => "a"
*/
function trimLeft(parser) {
return and(maybe(spaces()), parser);
}
/**
* Returns a parser that trims any trailing whitespace before `parser`.
*
* @param {Parser} parser - Parser to match after cleaning up whitespace.
* @memberof module:mona/strings
* @instance
*
* @example
* parse(trimRight(token()), "a \r\n"); // => "a"
*/
function trimRight(parser) {
return followedBy(parser, maybe(spaces()));
}
/**
* Number-related parsers and combinators
*
* @module mona/numbers
*/
/**
* Returns a parser that matches a natural number. That is, a number without a
* positive/negative sign or decimal places, and returns a positive integer.
*
* @param {Integer} [base=10] - Base to use when parsing the number.
* @memberof module:mona/numbers
* @instance
*
* @example
* parse(natural(), "1234"); // => 1234
*/
function natural(base) {
base = base || 10;
return map(function(str) { return parseInt(str, base); },
text(digit(base), {min: 1}));
}
/**
* Returns a parser that matches an integer, with an optional + or - sign.
*
* @param {Integer} [base=10] - Base to use when parsing the integer.
* @memberof module:mona/numbers
* @instance
*
* @example
* parse(integer(), "-1234"); // => -1234
*/
function integer(base) {
base = base || 10;
return sequence(function(s) {
var sign = s(maybe(or(string("+"),
string("-")))),
num = s(natural(base));
return value(num * (sign === "-" ? -1 : 1));
});
}
/**
* Returns a parser that will parse floating point numbers.
*
* @memberof module:mona/numbers
* @instance
*
* @example
* parse(real(), "-1234e-10"); // => -1.234e-7
*/
function real() {
return sequence(function(s) {
var leftSide = s(integer());
var rightSide = s(or(and(string("."),
integer()),
value(0)));
while (rightSide > 1) {
rightSide = rightSide / 10;
}
rightSide = leftSide >= 0 ? rightSide : (rightSide*-1);
var e = s(or(and(string("e", false),
integer()),
value(0)));
return value((leftSide + rightSide)*(Math.pow(10, e)));
});
}
/**
* Returns a parser that will parse english cardinal numbers into their
* numerical counterparts.
*
* @memberof module:mona/numbers
* @instance
*
* @example
* parse(cardinal(), "two thousand"); // => 2000
*/
function cardinal() {
return or(numeralUpToVeryBig(),
"cardinal");
}
/**
* Returns a parser that will parse english ordinal numbers into their numerical
* counterparts.
*
* @memberof module:mona/numbers
* @instance
*
* @example
* parse(ordinal(), "one-hundred thousand and fifth"); // 100005
*/
function ordinal() {
return or(numeralUpToVeryBig(true),
"ordinal");
}
/**
* Returns a parser that will parse shorthand english ordinal numbers into their
* numerical counterparts.
*
* @param {Boolean} [strict=true] - Whether to accept only appropriate suffixes
* for each number. (if false, `2th` parses to
* `2`)
* @memberof module:mona/numbers
* @instance
*
* @example
* parse(shortOrdinal(), "5th"); // 5
*/
function shortOrdinal(strict) {
strict = typeof strict === "undefined" ? true : strict;
if (strict) {
return sequence(function(s) {
var num = s(integer());
switch ((""+num).substr(-1)) {
case "1":
s(string("st"));
break;
case "2":
s(oneOf(["nd", "d"]));
break;
case "3":
s(oneOf(["rd", "d"]));
break;
default:
s(string("th"));
break;
}
return value(num);
});
} else {
return followedBy(integer(), oneOf(["th", "st", "nd", "rd"]));
}
}
/*
* English numbers support
*/
function numeralUpToVeryBig(ordinalMode) {
return or(sequence(function(s) {
var numOfBigs = s(numeralUpToThreeNines());
s(numeralSeparator());
var bigUnit = s(oneOf(CARDINALS["evenBigger sorted"], false));
var bigUnitIndex = CARDINALS.evenBigger.indexOf(bigUnit.toLowerCase());
var bigUnitMultiplier = Math.pow(10, (bigUnitIndex+1)*3);
var lesserUnit = s(is(function(x) {
return x < bigUnitMultiplier;
}, or(and(or(and(string(","), spaces()),
numeralSeparator()),
numeralUpToVeryBig(ordinalMode)),
and(numeralSeparator(), string("and"), numeralSeparator(),
numeralUpToThreeNines(ordinalMode)),
value(null))));
if (lesserUnit === null && ordinalMode) {
s(string("th"));
lesserUnit = 0;
}
return value((numOfBigs * bigUnitMultiplier) + lesserUnit);
}), numeralUpToThreeNines(ordinalMode));
}
function numeralUpToThreeNines(ordinalMode) {
return or(numeralHundreds(numeralUpToNinetyNine(ordinalMode),
1, ordinalMode),
numeralUpToNinetyNine(ordinalMode));
}
function numeralSeparator() {
return or(spaces(), string("-"));
}
function numeralHundreds(nextParser, multiplier, ordinalMode) {
return sequence(function(s) {
var numOfHundreds = s(numeralOneThroughNine());
s(numeralSeparator());
s(string("hundred"));
var smallNum = s(or(
and(numeralSeparator(),
multiplier > 1 ?
value() :
maybe(and(string("and"), numeralSeparator())),
nextParser),
value(null)));
if (smallNum === null && ordinalMode) {
s(string("th"));
smallNum = 0;
}
return value(((numOfHundreds * 100) + smallNum) * multiplier);
});
}
function numeralUpToNinetyNine(ordinalMode) {
return or(sequence(function(s) {
var ten = s(oneOf(CARDINALS["tens sorted"], false));
var tenIndex = CARDINALS.tens.indexOf(ten.toLowerCase());
var small = s(or(and(numeralSeparator(),
numeralOneThroughNine(ordinalMode)),
value(0)));
return value(((tenIndex + 2) * 10) + small);
}), !ordinalMode?fail():sequence(function(s) {
var ten = s(oneOf(ORDINALS["tens sorted"], false));
var tenIndex = ORDINALS.tens.indexOf(ten.toLowerCase());
return value((tenIndex + 2) * 10);
}), numeralUpToNineteen(ordinalMode));
}
function numeralOneThroughNine(ordinalMode) {
var source = ordinalMode ? ORDINALS : CARDINALS;
return map(function(x) {
return source["1-9"].indexOf(x.toLowerCase()) + 1;
}, oneOf(source["1-9 sorted"], false));
}
function numeralUpToNineteen(ordinalMode) {
var source = ordinalMode ? ORDINALS : CARDINALS;
return map(function(x) {
return source["0-19"].indexOf(x.toLowerCase());
}, oneOf(source["0-19 sorted"], false));
}
var CARDINALS = {
"1-9": ["one", "two", "three", "four", "five", "six",
"seven", "eight", "nine"],
"0-19": ["zero", "one", "two", "three", "four", "five", "six",
"seven", "eight", "nine", "ten", "eleven", "twelve",
"thirteen", "fourteen", "fifteen", "sixteen", "seventeen",
"eighteen", "nineteen"],
tens: ["twenty", "thirty", "forty", "fifty", "sixty",
"seventy", "eighty", "ninety"],
evenBigger: ["thousand", "million", "billion", "trillion",
"quadrillion", "quintillion", "sextillion", "septillion",
"octillion", "nonillion", "decillion", "undecillion",
"duodecillion", "tredecillion"] // At this point, wikipedia ran
// out of numbers until the
// googol and googelplex
};
var ORDINALS = {
"1-9": ["first", "second", "third", "fourth", "fifth", "sixth",
"seventh", "eighth", "ninth"],
"0-19": ["zeroeth", "first", "second", "third", "fourth", "fifth",
"sixth", "seventh", "eighth", "ninth", "tenth", "eleventh",
"twelfth", "thirteenth", "fourteenth", "fifteenth",
"sixteenth", "seventeenth", "eighteenth", "nineteenth"],
tens: ["twentieth", "thirtieth", "fortieth", "fiftieth",
"sixtieth", "seventieth", "eightieth", "ninetieth"]
};
// We need a sorted version because we need the longest strings to show up
// first.
function _sortByLength(a, b) {
return b.length - a.length;
}
for (var group in CARDINALS) {
if (CARDINALS.hasOwnProperty(group)) {
CARDINALS[group + " sorted"] = CARDINALS[group].slice();
CARDINALS[group + " sorted"].sort(_sortByLength);
}
}
for (group in ORDINALS) {
if (ORDINALS.hasOwnProperty(group)) {
ORDINALS[group + " sorted"] = ORDINALS[group].slice();
ORDINALS[group + " sorted"].sort(_sortByLength);
}
}
module.exports = {
// API
version: VERSION,
parse: parse,
parseAsync: parseAsync,
// Base parsers
value: value,
bind: bind,
fail: fail,
label: label,
token: token,
eof: eof,
log: log,
delay: delay,
map: map,
tag: tag,
lookAhead: lookAhead,
is: is,
isNot: isNot,
// Combinators
and: and,
or: or,
maybe: maybe,
not: not,
unless: unless,
sequence: sequence,
followedBy: followedBy,
split: split,
splitEnd: splitEnd,
collect: collect,
exactly: exactly,
between: between,
skip: skip,
range: range,
// String-related parsers
stringOf: stringOf,
oneOf: oneOf,
noneOf: noneOf,
string: string,
alphaLower: alphaLower,
alphaUpper: alphaUpper,
alpha: alpha,
digit: digit,
alphanum: alphanum,
space: space,
spaces: spaces,
text: text,
trim: trim,
trimLeft: trimLeft,
trimRight: trimRight,
// Numbers
natural: natural,
integer: integer,
"float": real, // For compatibility
real: real,
cardinal: cardinal,
ordinal: ordinal,
shortOrdinal: shortOrdinal
};
/*
* Internals
*/
function copy(obj) {
var newObj = Object.create(Object.getPrototypeOf(obj));
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
function mergeErrors(err1, err2) {
if (!err1 || (!err1.messages.length && err2.messages.length)) {
return err2;
} else if (!err2 || (!err2.messages.length && err1.messages.length)) {
return err1;
} else {
switch (comparePositions(err1.position, err2.position)) {
case "gt":
return err1;
case "lt":
return err2;
case "eq":
var newMessages =
(err1.messages.concat(err2.messages)).reduce(function(acc, x) {
return (~acc.indexOf(x)) ? acc : acc.concat([x]);
}, []);
return new ParserError(err2.position,
newMessages,
err2.type,
err2.wasEof || err1.wasEof);
default:
throw new Error("This should never happen");
}
}
}
function comparePositions(pos1, pos2) {
if (pos1.line < pos2.line) {
return "lt";
} else if (pos1.line > pos2.line) {
return "gt";
} else if (pos1.column < pos2.column) {
return "lt";
} else if (pos1.column > pos2.column) {
return "gt";
} else {
return "eq";
}
}
function ParserState(value, input, offset, userState,
position, hasConsumed, error, failed) {
this.value = value;
this.input = input;
this.offset = offset;
this.position = position;
this.userState = userState;
this.failed = failed;
this.error = error;
}