"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; }