alle new
This commit is contained in:
erik committed 2026-10-01 06:46:51 +02:00
1 parent da64472251
commit 5b20adee48
1605 files changed
+396517 -1674

No files matched your search

Generated Vendored Executable
+21
View File
@@ -0,0 +1,21 @@
var StringSource = require("../lib/StringSource");
exports.stringSourceRangeDescriptionIncludesLineAndCharacterNumber = function(test) {
test.equal(
"Line number: 1\nCharacter number: 3",
new StringSource("blah").range(2, 3).describe()
);
test.equal(
"Line number: 3\nCharacter number: 5",
new StringSource("one\ntwo\nthree\nfour").range(12, 15).describe()
);
test.done();
};
exports.stringSourceRangeDescriptionIncludesDescriptionIfProvided = function(test) {
test.equal(
"File: /some/file\nLine number: 1\nCharacter number: 3",
new StringSource("blah", "File: /some/file").range(2, 3).describe()
);
test.done();
};
Generated Vendored Executable
+17
View File
@@ -0,0 +1,17 @@
var TokenIterator = require("../lib/TokenIterator");
var Token = require("../lib/Token");
var StringSource = require("../lib/StringSource");
exports.canCreateSourceRangeToIteratorBeyondEnd = function(test) {
var source = function(startIndex, endIndex) {
return new StringSource("blah").range(startIndex, endIndex);
};
var startIterator = new TokenIterator([
new Token("identifier", "blah", source(0, 4)),
new Token("end", null, source(4, 4))
]);
var endIterator = startIterator.tail().tail();
var range = startIterator.to(endIterator);
test.deepEqual(source(0, 4), range);
test.done();
};
Generated Vendored Executable
+50
View File
@@ -0,0 +1,50 @@
var Token = require("../lib/Token");
var StringSource = require("../lib/StringSource");
var Tokeniser = module.exports = function(options) {
var keywords = options.keywords;
var tokenise = function(input) {
var source = new StringSource(input);
var createToken = function(startIndex, endIndex) {
var value = input.substring(startIndex, endIndex);
var tokenType = keywords.indexOf(value) === -1 ? "identifier" : "keyword";
return new Token(
tokenType,
value,
source.range(startIndex, endIndex)
);
};
var position = 0;
var tokens = [];
var done = input === "";
while (!done) {
var nextWhitespace = indexOfRegex(input, /\s/, position);
if (nextWhitespace === -1) {
done = true;
tokens.push(createToken(position, input.length));
} else {
tokens.push(createToken(position, nextWhitespace));
position = indexOfRegex(input, /\S/, nextWhitespace);
}
}
tokens.push(new Token("end", null, source.range(input.length, input.length)));
return tokens;
};
var indexOfRegex = function(string, regex, startIndex) {
startIndex = startIndex || 0;
var index = string.substring(startIndex).search(regex);
if (index === -1) {
return -1;
} else {
return index + startIndex;
}
};
return {
tokenise: tokenise
};
};
Generated Vendored Executable
+38
View File
@@ -0,0 +1,38 @@
var Tokeniser = require("./Tokeniser");
var Token = require("../lib/Token");
var StringSource = require("../lib/StringSource");
exports.stringIsSingleIdentifier = stringIsTokenisedTo("blah", [
new Token("identifier", "blah", stringSourceRange("blah", 0, 4)),
new Token("end", null, stringSourceRange("blah", 4, 4))
]);
exports.identifiersAreSeparatedByWhitespace = stringIsTokenisedTo("one two", [
new Token("identifier", "one", stringSourceRange("one two", 0, 3)),
new Token("identifier", "two", stringSourceRange("one two", 4, 7)),
new Token("end", null, stringSourceRange("one two", 7, 7))
]);
exports.canDetectKeywords = stringIsTokenisedTo("true", [
new Token("keyword", "true", stringSourceRange("true", 0, 4)),
new Token("end", null, stringSourceRange("true", 4, 4))
]);
exports.emptyStringIsTokenisedToSingleEndToken = stringIsTokenisedTo("", [
new Token("end", null, stringSourceRange("", 0, 0))
]);
function stringIsTokenisedTo(input, expected) {
return function(test) {
test.deepEqual(expected, tokenise(input));
test.done();
};
};
function stringSourceRange(string, startIndex, endIndex) {
return new StringSource(string).range(startIndex, endIndex);
};
function tokenise(input) {
return new Tokeniser({keywords: ["true"]}).tokenise(input);
};
+187
View File
@@ -0,0 +1,187 @@
var rules = require("../lib/rules");
var bottomUp = require("../lib/bottom-up");
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 source = function(string, startIndex, endIndex) {
return new StringSource(string).range(startIndex, endIndex);
};
var token = function(tokenType, value, source) {
return new Token(tokenType, value, source);
};
var partialCallRule = bottomUp.infix("call", function(parser) {
return rules.sequence(
rules.token("symbol", "("),
rules.sequence.capture(parser.rule()),
rules.token("symbol", ")")
).head();
}).map(function(left, arg) {
return [left, arg];
});
var partialAddRule = bottomUp.infix("add", function(parser) {
return rules.sequence(
rules.token("symbol", "+"),
rules.sequence.capture(parser.leftAssociative("add"))
).head();
}).map(function(left, right) {
return ["+", left, right];
});
var partialMultiplyRule = bottomUp.infix("multiply", function(parser) {
return rules.sequence(
rules.token("symbol", "*"),
rules.sequence.capture(parser.leftAssociative("multiply"))
).head();
}).map(function(left, right) {
return ["*", left, right];
});
var partialPowerRule = bottomUp.infix("power", function(parser) {
return rules.sequence(
rules.token("symbol", "^"),
rules.sequence.capture(parser.rightAssociative("power"))
).head();
}).map(function(left, right) {
return ["^", left, right];
});
exports.canParsePrefixExpression = function(test) {
var rule = bottomUp.parser("expression",
[rules.tokenOfType("identifier")],
[]
).rule();
var result = parse(rule, [
token("identifier", "blah", source("blah", 0, 4)),
token("end", null, source("blah", 4, 4))
]);
assertIsSuccess(test, result, {
value: "blah",
source: source("blah", 0, 4)
});
test.done();
};
exports.canParseSimpleInfixExpression = function(test) {
var rule = bottomUp.parser("expression",
[rules.tokenOfType("identifier")],
[partialCallRule]
).rule();
var result = parse(rule, [
token("identifier", "print", source("print(name)", 0, 5)),
token("symbol", "(", source("print(name)", 5, 6)),
token("identifier", "name", source("print(name)", 6, 10)),
token("symbol", ")", source("print(name)", 10, 11)),
token("end", null, source("print(name)", 11, 11))
]);
assertIsSuccess(test, result, {
value: ["print", "name"],
source: source("print(name)", 0, 11)
});
test.done();
};
exports.parsingStopsIfPrefixRuleFails = function(test) {
var rule = bottomUp.parser("expression",
[rules.tokenOfType("identifier")],
[partialCallRule]
).rule();
var result = parse(rule, [
token("symbol", "(", source("(name)", 0, 1)),
token("identifier", "name", source("(name)", 1, 5)),
token("symbol", ")", source("(name)", 5, 6)),
token("end", null, source("(name)", 6, 6))
]);
assertIsFailure(test, result, {
remaining: [
token("symbol", "(", source("(name)", 0, 1)),
token("identifier", "name", source("(name)", 1, 5)),
token("symbol", ")", source("(name)", 5, 6)),
token("end", null, source("(name)", 6, 6))
],
errors: [errors.error({
expected: "expression",
actual: "symbol \"(\"",
location: source("(name)", 0, 1)
})]
});
test.done();
};
exports.canParseExpressionWithTwoLeftAssociativeOperators = function(test) {
var expressionParser = bottomUp.parser("expression",
[rules.tokenOfType("number")],
[
partialMultiplyRule,
partialAddRule
]
);
var rule = expressionParser.rule();
var result = parse(rule, [
token("number", "1", source("1 * 2 * 3 + 4 * 5", 0, 1)),
token("symbol", "*", source("1 * 2 * 3 + 4 * 5", 2, 3)),
token("number", "2", source("1 * 2 * 3 + 4 * 5", 4, 5)),
token("symbol", "*", source("1 * 2 * 3 + 4 * 5", 6, 7)),
token("number", "3", source("1 * 2 * 3 + 4 * 5", 8, 9)),
token("symbol", "+", source("1 * 2 * 3 + 4 * 5", 10, 11)),
token("number", "4", source("1 * 2 * 3 + 4 * 5", 12, 13)),
token("symbol", "*", source("1 * 2 * 3 + 4 * 5", 14, 15)),
token("number", "5", source("1 * 2 * 3 + 4 * 5", 16, 17)),
token("end", null, source("1 * 2 * 3 + 4 * 5", 17, 17))
]);
assertIsSuccess(test, result, {
value: ["+", ["*", ["*", "1", "2"], "3"], ["*", "4", "5"]],
source: source("1 * 2 * 3 + 4 * 5", 0, 17)
});
test.done();
};
exports.canParseExpressionWithRightAssociativeOperators = function(test) {
var expressionParser = bottomUp.parser("expression",
[rules.tokenOfType("number")],
[
partialPowerRule,
partialAddRule
]
);
var rule = expressionParser.rule();
var result = parse(rule, [
token("number", "1", source("1 ^ 2 ^ 3 + 4 ^ 5", 0, 1)),
token("symbol", "^", source("1 ^ 2 ^ 3 + 4 ^ 5", 2, 3)),
token("number", "2", source("1 ^ 2 ^ 3 + 4 ^ 5", 4, 5)),
token("symbol", "^", source("1 ^ 2 ^ 3 + 4 ^ 5", 6, 7)),
token("number", "3", source("1 ^ 2 ^ 3 + 4 ^ 5", 8, 9)),
token("symbol", "+", source("1 ^ 2 ^ 3 + 4 ^ 5", 10, 11)),
token("number", "4", source("1 ^ 2 ^ 3 + 4 ^ 5", 12, 13)),
token("symbol", "^", source("1 ^ 2 ^ 3 + 4 ^ 5", 14, 15)),
token("number", "5", source("1 ^ 2 ^ 3 + 4 ^ 5", 16, 17)),
token("end", null, source("1 ^ 2 ^ 3 + 4 ^ 5", 17, 17))
]);
assertIsSuccess(test, result, {
value: ["+", ["^", "1", ["^", "2", "3"]], ["^", "4", "5"]],
source: source("1 ^ 2 ^ 3 + 4 ^ 5", 0, 17)
});
test.done();
};
var parse = function(parser, tokens) {
return parser(new TokenIterator(tokens));
};
Generated Vendored Executable
+36
View File
@@ -0,0 +1,36 @@
var errors = require("../lib/errors");
var StringSource = require("../lib/StringSource");
exports.errorDescriptionIncludesLocationAndActualValueAndExpectedValue = function(test) {
var error = errors.error({
expected: "Nothing",
actual: "Something",
location: {
describe: function() {
return "Here"
}
}
});
test.equal("Here:\nExpected Nothing\nbut got Something", error.describe());
test.done();
};
exports.canDescribeErrorWithoutLocation = function(test) {
var error = errors.error({
expected: "Nothing",
actual: "Something"
});
test.equal("Expected Nothing\nbut got Something", error.describe());
test.done();
};
exports.canGetPositionFromError = function(test) {
var error = errors.error({
expected: "Nothing",
actual: "Something",
location: new StringSource("abc\ndef\nghi\n", "").range(6, 8)
});
test.equal(2, error.lineNumber());
test.equal(3, error.characterNumber());
test.done();
};
+53
View File
@@ -0,0 +1,53 @@
var lazyIterators = require("../lib/lazy-iterators");
exports.convertingArrayToIteratorAndBack = function(test) {
var original = ["apple", "banana", "coconut"];
var iterator = lazyIterators.fromArray(original);
var reconstituted = iterator.toArray();
test.deepEqual(original, reconstituted);
test.done();
};
exports.mapAppliesFunctionToEveryElement = function(test) {
var original = [1, 2, 3];
var squared = lazyIterators.fromArray(original).map(function(i) {
return i * i;
}).toArray();
test.deepEqual([1, 4, 9], squared);
test.done();
};
exports.mappingFunctionIsOnlyCalledWhenNecessary = function(test) {
var original = [1, 2, 3];
var faultySquared = lazyIterators.fromArray(original).map(function(i) {
if (i === 1) {
return i;
} else {
throw new Error("Can only calculate the square of one");
}
}).first();
test.deepEqual(1, faultySquared);
test.done();
};
exports.filterRetainsElementsThatSatisfyPredicate = function(test) {
var original = [1, 2, 3, 4];
var even = lazyIterators.fromArray(original).filter(function(i) {
return i % 2 === 0;
}).toArray();
test.deepEqual([2, 4], even);
test.done();
};
exports.firstReturnsFirstElementOfArray = function(test) {
var original = [1, 2, 3, 4];
var first = lazyIterators.fromArray(original).first();
test.deepEqual(1, first);
test.done();
};
exports.firstIsNullIfArrayIsEmpty = function(test) {
var first = lazyIterators.fromArray([]).first();
test.same(null, first);
test.done();
};
+27
View File
@@ -0,0 +1,27 @@
var lop = require("../");
var Parser = lop.Parser;
var rules = lop.rules;
var testing = require("../lib/testing");
var Tokeniser = require("./Tokeniser");
exports.canParseUsingParser = function(test) {
var tokens = new Tokeniser({keywords: []}).tokenise("! blah");
var name = rules.sequence.capture(rules.token("identifier"), "name");
var rule = rules.sequence(
rules.token("identifier", "!"),
name
);
var parser = new Parser();
var result = parser.parseTokens(rule, tokens);
testing.assertIsSuccess(test, result);
test.deepEqual(result.value().get(name), "blah");
test.done();
};
var parseString = function(parser, string) {
return parser(new TokenIterator(tokens));
};
+114
View File
@@ -0,0 +1,114 @@
var RegexTokeniser = require("../lib/regex-tokeniser").RegexTokeniser;
var Token = require("../lib/Token");
var StringSource = require("../lib/StringSource");
exports.emptyStringIsTokenisedToEndToken = stringIsTokenisedTo("", [
endToken("")
]);
exports.canMatchSingleToken = stringIsTokenisedTo("blah", [
new Token("identifier", "blah", stringSourceRange("blah", 0, 4)),
endToken("blah")
]);
exports.canMatchMultipleTokens = stringIsTokenisedTo("a.btn", [
new Token("identifier", "a", stringSourceRange("a.btn", 0, 1)),
new Token("dot", ".", stringSourceRange("a.btn", 1, 2)),
new Token("identifier", "btn", stringSourceRange("a.btn", 2, 5)),
endToken("a.btn")
]);
exports.unrecognisedCharactersAreTokenised = stringIsTokenisedTo("!btn", [
new Token("unrecognisedCharacter", "!", stringSourceRange("!btn", 0, 1)),
new Token("identifier", "btn", stringSourceRange("!btn", 1, 4)),
endToken("!btn")
]);
exports.firstMatchingRuleIsUsed = stringIsTokenisedTo(":", [
new Token("colon1", ":", stringSourceRange(":", 0, 1)),
endToken(":")
]);
exports.valuesOfZeroLengthAreIgnored = function(test) {
var expectedTokens = [
new Token("unrecognisedCharacter", "!", stringSourceRange("!btn", 0, 1)),
new Token("identifier", "btn", stringSourceRange("!btn", 1, 4)),
endToken("!btn")
];
var rules = [
{
name: "identifier",
regex: /([a-z]*)/
}
];
var tokeniser = new RegexTokeniser(rules);
test.deepEqual(expectedTokens, tokeniser.tokenise("!btn"));
test.done();
};
exports.tokenValueIsFirstCaptureOfRegex = stringIsTokenisedTo('"a"', [
new Token("string", "a", stringSourceRange('"a"', 0, 3)),
endToken('"a"')
]);
exports.tokenWithNoCaptureHasUndefinedValue = function(test) {
var expectedTokens = [
new Token("bang", undefined, stringSourceRange("!", 0, 1)),
endToken("!")
];
var rules = [
{
name: "bang",
regex: /!/
}
];
var tokeniser = new RegexTokeniser(rules);
test.deepEqual(expectedTokens, tokeniser.tokenise("!"));
test.done();
};
function endToken(input) {
var source = stringSourceRange(input, input.length, input.length);
return new Token("end", null, source);
}
function stringIsTokenisedTo(input, expected) {
return function(test) {
test.deepEqual(expected, tokenise(input));
test.done();
};
};
function stringSourceRange(string, startIndex, endIndex) {
return new StringSource(string).range(startIndex, endIndex);
};
function tokenise(input) {
var rules = [
{
name: "identifier",
regex: /([a-z]+)/
},
{
name: "dot",
regex: /(\.)/
},
{
name: "colon1",
regex: /(:)/
},
{
name: "colon2",
regex: /(:)/
},
{
name: "string",
regex: /"([a-z]*)"/
}
];
var tokeniser = new RegexTokeniser(rules);
return tokeniser.tokenise(input);
};
+8
View File
@@ -0,0 +1,8 @@
var results = require("../lib/parsing-results");
exports.errorIsThrownIfCreatingFailureWithoutAnyErrors = function(test) {
test.throws(function() {
results.failure([]);
});
test.done();
};
+617
View File
@@ -0,0 +1,617 @@
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));
};