123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- var options = require("option");
- var rules = require("../lib/rules");
- var testing = require("../lib/testing");
- var TokenIterator = require("../lib/TokenIterator");
- var errors = require("../lib/errors");
- var results = require("../lib/parsing-results");
- var StringSource = require("../lib/StringSource");
- var assertIsSuccess = testing.assertIsSuccess;
- var assertIsSuccessWithValue = testing.assertIsSuccessWithValue;
- var assertIsFailure = testing.assertIsFailure;
- var assertIsFailureWithRemaining = testing.assertIsFailureWithRemaining;
- var assertIsError = testing.assertIsError;
- var Tokeniser = require("./Tokeniser");
- var Token = require("../lib/Token");
- var stringSourceRange = function(string, startIndex, endIndex) {
- return new StringSource(string).range(startIndex, endIndex);
- };
- var token = function(tokenType, value, source) {
- return new Token(tokenType, value, source);
- };
- var keyword = function(value) {
- return rules.token("keyword", value);
- };
- var identifier = function(value) {
- return rules.token("identifier", value);
- };
- exports.tokenRuleFailsIfInputIsEmpty = function(test) {
- var tokens = [];
- var rule = rules.token("keyword", "true");
- var result = rule(new TokenIterator(tokens));
- assertIsFailure(test, result, {
- remaining: [],
- errors: [errors.error({
- expected: "keyword \"true\"",
- actual: "end of tokens"
- })]
- });
- test.done();
- };
- exports.tokenRuleConsumeTokenWhenTokenIsOfCorrectType = function(test) {
- var parser = rules.token("keyword", "true");
- var result = parseString(parser, "true");
- assertIsSuccess(test, result, {
- value: "true",
- source: stringSourceRange("true", 0, 4)
- });
- test.done();
- };
- exports.parsingTokenFailsIfTokenIsOfWrongType = function(test) {
- var parser = rules.token("keyword", "true");
- var result = parseString(parser, "blah");
- assertIsFailure(test, result, {
- remaining: [
- token("identifier", "blah", stringSourceRange("blah", 0, 4)),
- token("end", null, stringSourceRange("blah", 4, 4))
- ],
- errors: [errors.error({
- expected: "keyword \"true\"",
- actual: "identifier \"blah\"",
- location: stringSourceRange("blah", 0, 4)
- })]
- });
- test.done();
- };
- exports.parsingTokenFailsIfTokenIsOfWrongValue = function(test) {
- var parser = rules.token("keyword", "true");
- var result = parseString(parser, "false");
- assertIsFailure(test, result, {
- remaining: [
- token("keyword", "false", stringSourceRange("false", 0, 5)),
- token("end", null, stringSourceRange("false", 5, 5))
- ],
- errors: [errors.error({
- expected: "keyword \"true\"",
- actual: "keyword \"false\"",
- location: stringSourceRange("false", 0, 5)
- })]
- });
- test.done();
- };
- exports.anyValueIsAcceptedIfValueOfTokenIsNotSpecified = function(test) {
- var parser = rules.token("keyword");
- var result = parseString(parser, "true");
- assertIsSuccess(test, result, {
- value: "true",
- source: stringSourceRange("true", 0, 4)
- });
- test.done();
- };
- exports.firstSuccessIsReturnedByFirstOf = function(test) {
- var trueParser = keyword("true");
- var falseParser = keyword("false");
- var evilParser = function() {
- throw new Error("Hahaha!");
- };
- var result = parseString(rules.firstOf("Boolean", trueParser, falseParser, evilParser), "false");
- assertIsSuccessWithValue(test, result, "false");
- test.done();
- };
- exports.firstOfFailsIfNoParsersMatch = function(test) {
- var trueParser = keyword("true");
- var falseParser = keyword("false");
- var result = parseString(rules.firstOf("Boolean", trueParser, falseParser), "blah");
- assertIsFailure(test, result, {
- remaining:[
- identifier("blah", stringSourceRange("blah", 0, 4)),
- token("end", null, stringSourceRange("blah", 4, 4))
- ],
- errors: [errors.error({
- expected: "Boolean",
- actual: "identifier \"blah\"",
- location: stringSourceRange("blah", 0, 4)
- })]
- });
- test.done();
- };
- exports.firstOfReturnsErrorIfSubRuleReturnsErrorEvenIfLaterRuleSucceeds = function(test) {
- var trueParser = rules.sequence(rules.sequence.cut(), keyword("true"));
- var falseParser = keyword("false");
- var result = parseString(rules.firstOf("Boolean", trueParser, falseParser), "false");
- assertIsError(test, result, {
- remaining:[
- keyword("false", stringSourceRange("false", 0, 5)),
- token("end", null, stringSourceRange("false", 5, 5))
- ],
- errors: [errors.error({
- expected: "keyword \"true\"",
- actual: "keyword \"false\"",
- location: stringSourceRange("false", 0, 5)
- })]
- });
- test.done();
- };
- exports.thenReturnsFailureIfOriginalResultIsFailure = function(test) {
- var parser = rules.then(keyword("true"), function() { return true; });
- var result = parseString(parser, "blah");
- assertIsFailure(test, result, {
- remaining:[
- identifier("blah", stringSourceRange("blah", 0, 4)),
- token("end", null, stringSourceRange("blah", 4, 4))
- ],
- errors: [errors.error({
- expected: "keyword \"true\"",
- actual: "identifier \"blah\"",
- location: stringSourceRange("blah", 0, 4)
- })]
- });
- test.done();
- };
- exports.thenMapsOverValueIfOriginalResultIsSuccess = function(test) {
- var parser = rules.then(keyword("true"), function() { return true; });
- var result = parseString(parser, "true");
- assertIsSuccessWithValue(test, result, true);
- test.done();
- };
- exports.sequenceSucceedsIfSubParsersCanBeAppliedInOrder = function(test) {
- var parser = rules.sequence(identifier("one"), identifier("two"));
- var result = parseString(parser, "one two");
- assertIsSuccess(test, result, {
- source: stringSourceRange("one two", 0, 7)
- });
- test.done();
- };
- exports.sequenceFailIfSubParserFails = function(test) {
- var parser = rules.sequence(identifier("("), identifier(")"));
- var result = parseString(parser, "(");
- assertIsFailure(test, result, {
- remaining:[token("end", null, stringSourceRange("(", 1, 1))],
- errors: [errors.error({
- expected: "identifier \")\"",
- actual: "end",
- location: stringSourceRange("(", 1, 1)
- })]
- });
- test.done();
- };
- exports.sequenceFailIfSubParserFailsAndFinalParserSucceeds = function(test) {
- var parser = rules.sequence(identifier("("), identifier(")"));
- var result = parseString(parser, ")");
- assertIsFailure(test, result, {
- remaining:[
- identifier(")", stringSourceRange(")", 0, 1)),
- token("end", null, stringSourceRange(")", 1, 1))
- ],
- errors: [errors.error({
- expected: "identifier \"(\"",
- actual: "identifier \")\"",
- location: stringSourceRange(")", 0, 1)
- })]
- });
- test.done();
- };
- exports.sequenceReturnsMapOfCapturedValues = function(test) {
- var name = rules.sequence.capture(identifier(), "name");
- var parser = rules.sequence(identifier("("), name, identifier(")"));
- var result = parseString(parser, "( bob )");
- assertIsSuccess(test, result);
- test.deepEqual(result.value().get(name), "bob");
- test.done();
- };
- exports.failureInSubRuleInSequenceBeforeCutCausesSequenceToFail = function(test) {
- var parser = rules.sequence(identifier("("), rules.sequence.cut(), identifier(), identifier(")"));
- var result = parseString(parser, "bob");
- assertIsFailure(test, result);
- test.done();
- };
- exports.failureInSubRuleInSequenceAfterCutCausesError = function(test) {
- var parser = rules.sequence(identifier("("), rules.sequence.cut(), identifier(), identifier(")"));
- var result = parseString(parser, "( true");
- assertIsError(test, result, {
- remaining: [
- token("keyword", "true", stringSourceRange("( true", 2, 6)),
- token("end", null, stringSourceRange("( true", 6, 6))
- ],
- errors: [errors.error({
- expected: "identifier",
- actual: "keyword \"true\"",
- location: stringSourceRange("( true", 2, 6)
- })]
- });
- test.done();
- };
- exports.canPullSingleValueOutOfCapturedValuesUsingExtract = function(test) {
- var name = rules.sequence.capture(identifier(), "name");
- var parser = rules.then(
- rules.sequence(identifier("("), name, identifier(")")),
- rules.sequence.extract(name)
- );
- var result = parseString(parser, "( bob )");
- assertIsSuccessWithValue(test, result, "bob");
- test.done();
- };
- exports.canPullSingleValueOutOfCapturedValuesUsingHeadOnSequenceRule = function(test) {
- var name = rules.sequence.capture(identifier(), "name");
- var parser =
- rules.sequence(identifier("("), name, identifier(")"))
- .head();
- var result = parseString(parser, "( bob )");
- assertIsSuccessWithValue(test, result, "bob");
- test.done();
- };
- exports.canApplyValuesFromSequenceToFunction = function(test) {
- var firstName = rules.sequence.capture(identifier(), "firstName");
- var secondName = rules.sequence.capture(identifier(), "secondName");
- var parser = rules.then(
- rules.sequence(
- secondName,
- identifier(","),
- firstName
- ),
- rules.sequence.applyValues(function(firstName, secondName) {
- return {first: firstName, second: secondName};
- }, firstName, secondName)
- );
- var result = parseString(parser, "Bobertson , Bob");
- assertIsSuccessWithValue(test, result, {first: "Bob", second: "Bobertson"});
- test.done();
- };
- exports.canApplyValuesAndSourceFromSequenceToFunctionUsingMapOnSequenceRule = function(test) {
- var firstName = rules.sequence.capture(identifier(), "firstName");
- var secondName = rules.sequence.capture(identifier());
- var parser = rules.sequence(
- secondName,
- identifier(","),
- firstName
- ).map(function(secondName, firstName, source) {
- return {first: firstName, second: secondName, source: source};
- });
- var result = parseString(parser, "Bobertson , Bob");
- assertIsSuccessWithValue(test, result, {
- first: "Bob",
- second: "Bobertson",
- source: stringSourceRange("Bobertson , Bob", 0, 15)
- });
- test.done();
- };
- exports.canApplyValuesWithSourceFromSequenceToFunction = function(test) {
- var firstName = rules.sequence.capture(identifier(), "firstName");
- var secondName = rules.sequence.capture(identifier(), "secondName");
- var parser = rules.then(
- rules.sequence(
- secondName,
- identifier(","),
- firstName
- ),
- rules.sequence.applyValues(function(firstName, secondName, source) {
- return {first: firstName, second: secondName, source: source};
- }, firstName, secondName, rules.sequence.source)
- );
- var result = parseString(parser, "Bobertson , Bob");
- assertIsSuccessWithValue(test, result, {
- first: "Bob",
- second: "Bobertson",
- source: stringSourceRange("Bobertson , Bob", 0, 15)
- });
- test.done();
- };
- exports.exceptionIfTryingToReadAValueThatHasntBeenCaptured = function(test) {
- var name = rules.sequence.capture(identifier(), "name");
- var parser = rules.sequence(identifier("("), identifier(")"));
- var result = parseString(parser, "( )");
- assertIsSuccess(test, result);
- try {
- result.value().get(name);
- test.ok(false, "Expected exception");
- } catch (error) {
- test.equal(error.message, "No value for capture \"name\"");
- }
- test.done();
- };
- exports.exceptionIfTryingToCaptureValueWithUsedName = function(test) {
- var firstName = rules.sequence.capture(identifier(), "name");
- var secondName = rules.sequence.capture(identifier(), "name");
- var parser = rules.sequence(secondName, identifier(","), firstName);
- try {
- parseString(parser, "Bobertson , Bob")
- test.ok(false, "Expected exception");
- } catch (error) {
- test.equal(error.message, "Cannot add second value for capture \"name\"");
- }
- test.done();
- };
- exports.optionalRuleDoesNothingIfValueDoesNotMatch = function(test) {
- var parser = rules.optional(identifier("("));
- var result = parseString(parser, "");
- assertIsSuccess(test, result);
- test.deepEqual(result.value(), options.none);
- test.done();
- };
- exports.optionalRuleConsumesInputIfPossible = function(test) {
- var parser = rules.optional(identifier("("));
- var result = parseString(parser, "(");
- assertIsSuccess(test, result);
- test.deepEqual(result.value(), options.some("("));
- test.done();
- };
- exports.optionalRulePreservesErrors = function(test) {
- var error = results.error([errors.error({
- expected: "something",
- actual: "something else"
- })]);
- var parser = rules.optional(function(input) {
- return error;
- });
- var result = parseString(parser, "");
- test.deepEqual(result, error);
- test.done();
- };
- exports.zeroOrMoreWithSeparatorParsesEmptyStringAndReturnsEmptyArray = function(test) {
- var parser = rules.zeroOrMoreWithSeparator(identifier(), identifier(","));
- var result = parseString(parser, "");
- assertIsSuccessWithValue(test, result, []);
- test.done();
- };
- exports.zeroOrMoreWithSeparatorParsesSingleInstanceOfRuleAndReturnsSingleElementArray = function(test) {
- var parser = rules.zeroOrMoreWithSeparator(identifier(), identifier(","));
- var result = parseString(parser, "blah");
- assertIsSuccessWithValue(test, result, ["blah"]);
- test.done();
- };
- exports.zeroOrMoreWithSeparatorParsesMultipleInstanceOfRuleAndReturnsArray = function(test) {
- var parser = rules.zeroOrMoreWithSeparator(identifier(), identifier(","));
- var result = parseString(parser, "apple , banana , coconut");
- assertIsSuccessWithValue(test, result, ["apple", "banana", "coconut"]);
- test.done();
- };
- exports.zeroOrMoreWithSeparatorDoesNotConsumeFinalSeparatorIfItIsNotFollowedByMainRule = function(test) {
- var parser = rules.zeroOrMoreWithSeparator(identifier(), identifier(","));
- var result = parseString(parser, "apple , banana ,");
- assertIsSuccess(test, result, {
- remaining: [
- token("identifier", ",", stringSourceRange("apple , banana ,", 15, 16)),
- token("end", null, stringSourceRange("apple , banana ,", 16, 16))
- ],
- });
- test.done();
- };
- exports.zeroOrMoreReturnsErrorIfFirstUseOfRuleReturnsError = function(test) {
- var parser = rules.zeroOrMoreWithSeparator(
- rules.sequence(identifier(), rules.sequence.cut(), identifier()),
- identifier(",")
- );
- var result = parseString(parser, "apple");
- assertIsError(test, result);
- test.done();
- };
- exports.zeroOrMoreParsesEmptyStringAndReturnsEmptyArray = function(test) {
- var parser = rules.zeroOrMore(identifier());
- var result = parseString(parser, "");
- assertIsSuccessWithValue(test, result, []);
- test.done();
- };
- exports.zeroOrMoreParsesSingleInstanceOfRuleAndReturnsSingleElementArray = function(test) {
- var parser = rules.zeroOrMore(identifier());
- var result = parseString(parser, "blah");
- assertIsSuccessWithValue(test, result, ["blah"]);
- test.done();
- };
- exports.zeroOrMoreParsesMultipleInstanceOfRuleAndReturnsArray = function(test) {
- var parser = rules.zeroOrMore(identifier());
- var result = parseString(parser, "( , )");
- assertIsSuccessWithValue(test, result, ["(", ",", ")"]);
- test.done();
- };
- exports.zeroOrMoreReturnsErrorIfSubRuleReturnsError = function(test) {
- var parser = rules.zeroOrMore(
- rules.sequence(identifier(), rules.sequence.cut(), identifier(";"))
- );
- var result = parseString(parser, "blah");
- assertIsError(test, result, {
- remaining:[
- token("end", null, stringSourceRange("blah", 4, 4))
- ],
- errors: [errors.error({
- expected: "identifier \";\"",
- actual: "end",
- location: stringSourceRange("blah", 4, 4)
- })]
- });
- test.done();
- };
- exports.oneOrMoreWithSeparatorFailsOnEmptyString = function(test) {
- var parser = rules.oneOrMoreWithSeparator(identifier(), identifier(","));
- var result = parseString(parser, "");
- assertIsFailure(test, result, {
- remaining:[
- token("end", null, stringSourceRange("", 0, 0))
- ],
- errors: [errors.error({
- expected: "identifier",
- actual: "end",
- location: stringSourceRange("", 0, 0)
- })]
- });
- test.done();
- };
- exports.oneOrMoreWithSeparatorParsesSingleInstanceOfRuleAndReturnsSingleElementArray = function(test) {
- var parser = rules.oneOrMoreWithSeparator(identifier(), identifier(","));
- var result = parseString(parser, "blah");
- assertIsSuccessWithValue(test, result, ["blah"]);
- test.done();
- };
- exports.oneOrMoreWithSeparatorParsesMultipleInstanceOfRuleAndReturnsArray = function(test) {
- var parser = rules.oneOrMoreWithSeparator(identifier(), identifier(","));
- var result = parseString(parser, "apple , banana , coconut");
- assertIsSuccessWithValue(test, result, ["apple", "banana", "coconut"]);
- test.done();
- };
- exports.oneOrMoreFailsOnEmptyString = function(test) {
- var parser = rules.oneOrMore(identifier());
- var result = parseString(parser, "");
- assertIsFailure(test, result, {
- remaining:[
- token("end", null, stringSourceRange("", 0, 0))
- ],
- errors: [errors.error({
- expected: "identifier",
- actual: "end",
- location: stringSourceRange("", 0, 0)
- })]
- });
- test.done();
- };
- exports.oneOrMoreParsesSingleInstanceOfRuleAndReturnsSingleElementArray = function(test) {
- var parser = rules.oneOrMore(identifier());
- var result = parseString(parser, "blah");
- assertIsSuccessWithValue(test, result, ["blah"]);
- test.done();
- };
- exports.oneOrMoreParsesMultipleInstanceOfRuleAndReturnsArray = function(test) {
- var parser = rules.oneOrMore(identifier());
- var result = parseString(parser, "apple banana coconut");
- assertIsSuccessWithValue(test, result, ["apple", "banana", "coconut"]);
- test.done();
- };
- exports.leftAssociativeConsumesNothingIfLeftHandSideDoesntMatch = function(test) {
- var parser = rules.leftAssociative(
- keyword(),
- identifier("+"),
- function(left, right) {
- return [left, right];
- }
- );
- var result = parseString(parser, "+ +");
- assertIsFailure(test, result, {
- remaining:[
- token("identifier", "+", stringSourceRange("+ +", 0, 1)),
- token("identifier", "+", stringSourceRange("+ +", 2, 3)),
- token("end", null, stringSourceRange("+ +", 3, 3))
- ],
- errors: [errors.error({
- expected: "keyword",
- actual: "identifier \"+\"",
- location: stringSourceRange("+ +", 0, 1)
- })]
- });
- test.done();
- };
- exports.leftAssociativeReturnsValueOfLeftHandSideIfRightHandSideDoesntMatch = function(test) {
- var parser = rules.leftAssociative(
- identifier(),
- identifier("+"),
- function(left, right) {
- return [left, right];
- }
- );
- var result = parseString(parser, "apple");
- assertIsSuccessWithValue(test, result, "apple");
- test.done();
- };
- exports.leftAssociativeAllowsLeftAssociativeRules = function(test) {
- var parser = rules.leftAssociative(
- identifier(),
- identifier("+"),
- function(left, right) {
- return [left, right];
- }
- );
- var result = parseString(parser, "apple + +");
- assertIsSuccessWithValue(test, result, [["apple", "+"], "+"]);
- test.done();
- };
- exports.leftAssociativeCanHaveMultipleChoicesForRight = function(test) {
- var parser = rules.leftAssociative(
- identifier(),
- rules.leftAssociative.firstOf(
- {rule: identifier("+"), func: function(left, right) { return [left, right]; }},
- {rule: identifier(","), func: function(left, right) { return [left]; }}
- )
- );
- var result = parseString(parser, "apple + ,");
- assertIsSuccessWithValue(test, result, [["apple", "+"]]);
- test.done();
- };
- exports.leftAssociativeReturnsErrorIfRightHandSideReturnsError = function(test) {
- var parser = rules.leftAssociative(
- identifier(),
- rules.leftAssociative.firstOf(
- {rule: rules.sequence(rules.sequence.cut(), identifier("+")), func: function() {}}
- )
- );
- var result = parseString(parser, "apple");
- assertIsError(test, result);
- test.done();
- };
- exports.nonConsumingRuleDoesNotConsumeInput = function(test) {
- var parser = rules.nonConsuming(rules.token("keyword", "true"));
- var result = parseString(parser, "true");
- assertIsSuccess(test, result, {
- value: "true",
- source: stringSourceRange("true", 0, 4),
- remaining: [token("keyword", "true"), token("end", null)]
- });
- test.done();
- };
- var parseString = function(parser, string) {
- var keywords = ["true", "false"];
- var tokens = new Tokeniser({keywords: keywords}).tokenise(string);
- return parser(new TokenIterator(tokens));
- };
- var parseTokens = function(parser, tokens) {
- return parser(new TokenIterator(tokens));
- };
|