UNPKG

21.6 kBJavaScriptView Raw
1var options = require("option");
2
3var rules = require("../lib/rules");
4var testing = require("../lib/testing");
5var TokenIterator = require("../lib/TokenIterator");
6var errors = require("../lib/errors");
7var results = require("../lib/parsing-results");
8var StringSource = require("../lib/StringSource");
9var assertIsSuccess = testing.assertIsSuccess;
10var assertIsSuccessWithValue = testing.assertIsSuccessWithValue;
11var assertIsFailure = testing.assertIsFailure;
12var assertIsFailureWithRemaining = testing.assertIsFailureWithRemaining;
13var assertIsError = testing.assertIsError;
14var Tokeniser = require("./Tokeniser");
15var Token = require("../lib/Token");
16
17var stringSourceRange = function(string, startIndex, endIndex) {
18 return new StringSource(string).range(startIndex, endIndex);
19};
20
21var token = function(tokenType, value, source) {
22 return new Token(tokenType, value, source);
23};
24
25var keyword = function(value) {
26 return rules.token("keyword", value);
27};
28
29var identifier = function(value) {
30 return rules.token("identifier", value);
31};
32
33exports.tokenRuleFailsIfInputIsEmpty = function(test) {
34 var tokens = [];
35 var rule = rules.token("keyword", "true");
36 var result = rule(new TokenIterator(tokens));
37 assertIsFailure(test, result, {
38 remaining: [],
39 errors: [errors.error({
40 expected: "keyword \"true\"",
41 actual: "end of tokens"
42 })]
43 });
44 test.done();
45};
46
47exports.tokenRuleConsumeTokenWhenTokenIsOfCorrectType = function(test) {
48 var parser = rules.token("keyword", "true");
49 var result = parseString(parser, "true");
50 assertIsSuccess(test, result, {
51 value: "true",
52 source: stringSourceRange("true", 0, 4)
53 });
54 test.done();
55};
56
57exports.parsingTokenFailsIfTokenIsOfWrongType = function(test) {
58 var parser = rules.token("keyword", "true");
59 var result = parseString(parser, "blah");
60 assertIsFailure(test, result, {
61 remaining: [
62 token("identifier", "blah", stringSourceRange("blah", 0, 4)),
63 token("end", null, stringSourceRange("blah", 4, 4))
64 ],
65 errors: [errors.error({
66 expected: "keyword \"true\"",
67 actual: "identifier \"blah\"",
68 location: stringSourceRange("blah", 0, 4)
69 })]
70 });
71 test.done();
72};
73
74exports.parsingTokenFailsIfTokenIsOfWrongValue = function(test) {
75 var parser = rules.token("keyword", "true");
76 var result = parseString(parser, "false");
77 assertIsFailure(test, result, {
78 remaining: [
79 token("keyword", "false", stringSourceRange("false", 0, 5)),
80 token("end", null, stringSourceRange("false", 5, 5))
81 ],
82 errors: [errors.error({
83 expected: "keyword \"true\"",
84 actual: "keyword \"false\"",
85 location: stringSourceRange("false", 0, 5)
86 })]
87 });
88 test.done();
89};
90
91exports.anyValueIsAcceptedIfValueOfTokenIsNotSpecified = function(test) {
92 var parser = rules.token("keyword");
93 var result = parseString(parser, "true");
94 assertIsSuccess(test, result, {
95 value: "true",
96 source: stringSourceRange("true", 0, 4)
97 });
98 test.done();
99};
100
101exports.firstSuccessIsReturnedByFirstOf = function(test) {
102 var trueParser = keyword("true");
103 var falseParser = keyword("false");
104 var evilParser = function() {
105 throw new Error("Hahaha!");
106 };
107 var result = parseString(rules.firstOf("Boolean", trueParser, falseParser, evilParser), "false");
108 assertIsSuccessWithValue(test, result, "false");
109 test.done();
110};
111
112exports.firstOfFailsIfNoParsersMatch = function(test) {
113 var trueParser = keyword("true");
114 var falseParser = keyword("false");
115 var result = parseString(rules.firstOf("Boolean", trueParser, falseParser), "blah");
116 assertIsFailure(test, result, {
117 remaining:[
118 identifier("blah", stringSourceRange("blah", 0, 4)),
119 token("end", null, stringSourceRange("blah", 4, 4))
120 ],
121 errors: [errors.error({
122 expected: "Boolean",
123 actual: "identifier \"blah\"",
124 location: stringSourceRange("blah", 0, 4)
125 })]
126 });
127 test.done();
128};
129
130exports.firstOfReturnsErrorIfSubRuleReturnsErrorEvenIfLaterRuleSucceeds = function(test) {
131 var trueParser = rules.sequence(rules.sequence.cut(), keyword("true"));
132 var falseParser = keyword("false");
133 var result = parseString(rules.firstOf("Boolean", trueParser, falseParser), "false");
134 assertIsError(test, result, {
135 remaining:[
136 keyword("false", stringSourceRange("false", 0, 5)),
137 token("end", null, stringSourceRange("false", 5, 5))
138 ],
139 errors: [errors.error({
140 expected: "keyword \"true\"",
141 actual: "keyword \"false\"",
142 location: stringSourceRange("false", 0, 5)
143 })]
144 });
145 test.done();
146};
147
148exports.thenReturnsFailureIfOriginalResultIsFailure = function(test) {
149 var parser = rules.then(keyword("true"), function() { return true; });
150 var result = parseString(parser, "blah");
151 assertIsFailure(test, result, {
152 remaining:[
153 identifier("blah", stringSourceRange("blah", 0, 4)),
154 token("end", null, stringSourceRange("blah", 4, 4))
155 ],
156 errors: [errors.error({
157 expected: "keyword \"true\"",
158 actual: "identifier \"blah\"",
159 location: stringSourceRange("blah", 0, 4)
160 })]
161 });
162 test.done();
163};
164
165exports.thenMapsOverValueIfOriginalResultIsSuccess = function(test) {
166 var parser = rules.then(keyword("true"), function() { return true; });
167 var result = parseString(parser, "true");
168 assertIsSuccessWithValue(test, result, true);
169 test.done();
170};
171
172exports.sequenceSucceedsIfSubParsersCanBeAppliedInOrder = function(test) {
173 var parser = rules.sequence(identifier("one"), identifier("two"));
174 var result = parseString(parser, "one two");
175 assertIsSuccess(test, result, {
176 source: stringSourceRange("one two", 0, 7)
177 });
178 test.done();
179};
180
181exports.sequenceFailIfSubParserFails = function(test) {
182 var parser = rules.sequence(identifier("("), identifier(")"));
183 var result = parseString(parser, "(");
184 assertIsFailure(test, result, {
185 remaining:[token("end", null, stringSourceRange("(", 1, 1))],
186 errors: [errors.error({
187 expected: "identifier \")\"",
188 actual: "end",
189 location: stringSourceRange("(", 1, 1)
190 })]
191 });
192 test.done();
193};
194
195exports.sequenceFailIfSubParserFailsAndFinalParserSucceeds = function(test) {
196 var parser = rules.sequence(identifier("("), identifier(")"));
197 var result = parseString(parser, ")");
198 assertIsFailure(test, result, {
199 remaining:[
200 identifier(")", stringSourceRange(")", 0, 1)),
201 token("end", null, stringSourceRange(")", 1, 1))
202 ],
203 errors: [errors.error({
204 expected: "identifier \"(\"",
205 actual: "identifier \")\"",
206 location: stringSourceRange(")", 0, 1)
207 })]
208 });
209 test.done();
210};
211
212exports.sequenceReturnsMapOfCapturedValues = function(test) {
213 var name = rules.sequence.capture(identifier(), "name");
214 var parser = rules.sequence(identifier("("), name, identifier(")"));
215 var result = parseString(parser, "( bob )");
216 assertIsSuccess(test, result);
217 test.deepEqual(result.value().get(name), "bob");
218 test.done();
219};
220
221exports.failureInSubRuleInSequenceBeforeCutCausesSequenceToFail = function(test) {
222 var parser = rules.sequence(identifier("("), rules.sequence.cut(), identifier(), identifier(")"));
223 var result = parseString(parser, "bob");
224 assertIsFailure(test, result);
225 test.done();
226};
227
228exports.failureInSubRuleInSequenceAfterCutCausesError = function(test) {
229 var parser = rules.sequence(identifier("("), rules.sequence.cut(), identifier(), identifier(")"));
230 var result = parseString(parser, "( true");
231 assertIsError(test, result, {
232 remaining: [
233 token("keyword", "true", stringSourceRange("( true", 2, 6)),
234 token("end", null, stringSourceRange("( true", 6, 6))
235 ],
236 errors: [errors.error({
237 expected: "identifier",
238 actual: "keyword \"true\"",
239 location: stringSourceRange("( true", 2, 6)
240 })]
241 });
242 test.done();
243};
244
245exports.canPullSingleValueOutOfCapturedValuesUsingExtract = function(test) {
246 var name = rules.sequence.capture(identifier(), "name");
247 var parser = rules.then(
248 rules.sequence(identifier("("), name, identifier(")")),
249 rules.sequence.extract(name)
250 );
251 var result = parseString(parser, "( bob )");
252 assertIsSuccessWithValue(test, result, "bob");
253 test.done();
254};
255
256exports.canPullSingleValueOutOfCapturedValuesUsingHeadOnSequenceRule = function(test) {
257 var name = rules.sequence.capture(identifier(), "name");
258 var parser =
259 rules.sequence(identifier("("), name, identifier(")"))
260 .head();
261 var result = parseString(parser, "( bob )");
262 assertIsSuccessWithValue(test, result, "bob");
263 test.done();
264};
265
266exports.canApplyValuesFromSequenceToFunction = function(test) {
267 var firstName = rules.sequence.capture(identifier(), "firstName");
268 var secondName = rules.sequence.capture(identifier(), "secondName");
269 var parser = rules.then(
270 rules.sequence(
271 secondName,
272 identifier(","),
273 firstName
274 ),
275 rules.sequence.applyValues(function(firstName, secondName) {
276 return {first: firstName, second: secondName};
277 }, firstName, secondName)
278 );
279 var result = parseString(parser, "Bobertson , Bob");
280 assertIsSuccessWithValue(test, result, {first: "Bob", second: "Bobertson"});
281 test.done();
282};
283
284exports.canApplyValuesAndSourceFromSequenceToFunctionUsingMapOnSequenceRule = function(test) {
285 var firstName = rules.sequence.capture(identifier(), "firstName");
286 var secondName = rules.sequence.capture(identifier());
287 var parser = rules.sequence(
288 secondName,
289 identifier(","),
290 firstName
291 ).map(function(secondName, firstName, source) {
292 return {first: firstName, second: secondName, source: source};
293 });
294 var result = parseString(parser, "Bobertson , Bob");
295 assertIsSuccessWithValue(test, result, {
296 first: "Bob",
297 second: "Bobertson",
298 source: stringSourceRange("Bobertson , Bob", 0, 15)
299 });
300 test.done();
301};
302
303exports.canApplyValuesWithSourceFromSequenceToFunction = function(test) {
304 var firstName = rules.sequence.capture(identifier(), "firstName");
305 var secondName = rules.sequence.capture(identifier(), "secondName");
306 var parser = rules.then(
307 rules.sequence(
308 secondName,
309 identifier(","),
310 firstName
311 ),
312 rules.sequence.applyValues(function(firstName, secondName, source) {
313 return {first: firstName, second: secondName, source: source};
314 }, firstName, secondName, rules.sequence.source)
315 );
316 var result = parseString(parser, "Bobertson , Bob");
317 assertIsSuccessWithValue(test, result, {
318 first: "Bob",
319 second: "Bobertson",
320 source: stringSourceRange("Bobertson , Bob", 0, 15)
321 });
322 test.done();
323};
324
325exports.exceptionIfTryingToReadAValueThatHasntBeenCaptured = function(test) {
326 var name = rules.sequence.capture(identifier(), "name");
327 var parser = rules.sequence(identifier("("), identifier(")"));
328 var result = parseString(parser, "( )");
329 assertIsSuccess(test, result);
330 try {
331 result.value().get(name);
332 test.ok(false, "Expected exception");
333 } catch (error) {
334 test.equal(error.message, "No value for capture \"name\"");
335 }
336 test.done();
337};
338
339exports.exceptionIfTryingToCaptureValueWithUsedName = function(test) {
340 var firstName = rules.sequence.capture(identifier(), "name");
341 var secondName = rules.sequence.capture(identifier(), "name");
342 var parser = rules.sequence(secondName, identifier(","), firstName);
343 try {
344 parseString(parser, "Bobertson , Bob")
345 test.ok(false, "Expected exception");
346 } catch (error) {
347 test.equal(error.message, "Cannot add second value for capture \"name\"");
348 }
349 test.done();
350};
351
352exports.optionalRuleDoesNothingIfValueDoesNotMatch = function(test) {
353 var parser = rules.optional(identifier("("));
354 var result = parseString(parser, "");
355 assertIsSuccess(test, result);
356 test.deepEqual(result.value(), options.none);
357 test.done();
358};
359
360exports.optionalRuleConsumesInputIfPossible = function(test) {
361 var parser = rules.optional(identifier("("));
362 var result = parseString(parser, "(");
363 assertIsSuccess(test, result);
364 test.deepEqual(result.value(), options.some("("));
365 test.done();
366};
367
368exports.optionalRulePreservesErrors = function(test) {
369 var error = results.error([errors.error({
370 expected: "something",
371 actual: "something else"
372 })]);
373 var parser = rules.optional(function(input) {
374 return error;
375 });
376 var result = parseString(parser, "");
377 test.deepEqual(result, error);
378 test.done();
379};
380
381exports.zeroOrMoreWithSeparatorParsesEmptyStringAndReturnsEmptyArray = function(test) {
382 var parser = rules.zeroOrMoreWithSeparator(identifier(), identifier(","));
383 var result = parseString(parser, "");
384 assertIsSuccessWithValue(test, result, []);
385 test.done();
386};
387
388exports.zeroOrMoreWithSeparatorParsesSingleInstanceOfRuleAndReturnsSingleElementArray = function(test) {
389 var parser = rules.zeroOrMoreWithSeparator(identifier(), identifier(","));
390 var result = parseString(parser, "blah");
391 assertIsSuccessWithValue(test, result, ["blah"]);
392 test.done();
393};
394
395exports.zeroOrMoreWithSeparatorParsesMultipleInstanceOfRuleAndReturnsArray = function(test) {
396 var parser = rules.zeroOrMoreWithSeparator(identifier(), identifier(","));
397 var result = parseString(parser, "apple , banana , coconut");
398 assertIsSuccessWithValue(test, result, ["apple", "banana", "coconut"]);
399 test.done();
400};
401
402exports.zeroOrMoreWithSeparatorDoesNotConsumeFinalSeparatorIfItIsNotFollowedByMainRule = function(test) {
403 var parser = rules.zeroOrMoreWithSeparator(identifier(), identifier(","));
404 var result = parseString(parser, "apple , banana ,");
405 assertIsSuccess(test, result, {
406 remaining: [
407 token("identifier", ",", stringSourceRange("apple , banana ,", 15, 16)),
408 token("end", null, stringSourceRange("apple , banana ,", 16, 16))
409 ],
410 });
411 test.done();
412};
413
414exports.zeroOrMoreReturnsErrorIfFirstUseOfRuleReturnsError = function(test) {
415 var parser = rules.zeroOrMoreWithSeparator(
416 rules.sequence(identifier(), rules.sequence.cut(), identifier()),
417 identifier(",")
418 );
419 var result = parseString(parser, "apple");
420 assertIsError(test, result);
421 test.done();
422};
423
424exports.zeroOrMoreParsesEmptyStringAndReturnsEmptyArray = function(test) {
425 var parser = rules.zeroOrMore(identifier());
426 var result = parseString(parser, "");
427 assertIsSuccessWithValue(test, result, []);
428 test.done();
429};
430
431exports.zeroOrMoreParsesSingleInstanceOfRuleAndReturnsSingleElementArray = function(test) {
432 var parser = rules.zeroOrMore(identifier());
433 var result = parseString(parser, "blah");
434 assertIsSuccessWithValue(test, result, ["blah"]);
435 test.done();
436};
437
438exports.zeroOrMoreParsesMultipleInstanceOfRuleAndReturnsArray = function(test) {
439 var parser = rules.zeroOrMore(identifier());
440 var result = parseString(parser, "( , )");
441 assertIsSuccessWithValue(test, result, ["(", ",", ")"]);
442 test.done();
443};
444
445exports.zeroOrMoreReturnsErrorIfSubRuleReturnsError = function(test) {
446 var parser = rules.zeroOrMore(
447 rules.sequence(identifier(), rules.sequence.cut(), identifier(";"))
448 );
449 var result = parseString(parser, "blah");
450 assertIsError(test, result, {
451 remaining:[
452 token("end", null, stringSourceRange("blah", 4, 4))
453 ],
454 errors: [errors.error({
455 expected: "identifier \";\"",
456 actual: "end",
457 location: stringSourceRange("blah", 4, 4)
458 })]
459 });
460 test.done();
461};
462
463exports.oneOrMoreWithSeparatorFailsOnEmptyString = function(test) {
464 var parser = rules.oneOrMoreWithSeparator(identifier(), identifier(","));
465 var result = parseString(parser, "");
466 assertIsFailure(test, result, {
467 remaining:[
468 token("end", null, stringSourceRange("", 0, 0))
469 ],
470 errors: [errors.error({
471 expected: "identifier",
472 actual: "end",
473 location: stringSourceRange("", 0, 0)
474 })]
475 });
476 test.done();
477};
478
479exports.oneOrMoreWithSeparatorParsesSingleInstanceOfRuleAndReturnsSingleElementArray = function(test) {
480 var parser = rules.oneOrMoreWithSeparator(identifier(), identifier(","));
481 var result = parseString(parser, "blah");
482 assertIsSuccessWithValue(test, result, ["blah"]);
483 test.done();
484};
485
486exports.oneOrMoreWithSeparatorParsesMultipleInstanceOfRuleAndReturnsArray = function(test) {
487 var parser = rules.oneOrMoreWithSeparator(identifier(), identifier(","));
488 var result = parseString(parser, "apple , banana , coconut");
489 assertIsSuccessWithValue(test, result, ["apple", "banana", "coconut"]);
490 test.done();
491};
492
493exports.oneOrMoreFailsOnEmptyString = function(test) {
494 var parser = rules.oneOrMore(identifier());
495 var result = parseString(parser, "");
496 assertIsFailure(test, result, {
497 remaining:[
498 token("end", null, stringSourceRange("", 0, 0))
499 ],
500 errors: [errors.error({
501 expected: "identifier",
502 actual: "end",
503 location: stringSourceRange("", 0, 0)
504 })]
505 });
506 test.done();
507};
508
509exports.oneOrMoreParsesSingleInstanceOfRuleAndReturnsSingleElementArray = function(test) {
510 var parser = rules.oneOrMore(identifier());
511 var result = parseString(parser, "blah");
512 assertIsSuccessWithValue(test, result, ["blah"]);
513 test.done();
514};
515
516exports.oneOrMoreParsesMultipleInstanceOfRuleAndReturnsArray = function(test) {
517 var parser = rules.oneOrMore(identifier());
518 var result = parseString(parser, "apple banana coconut");
519 assertIsSuccessWithValue(test, result, ["apple", "banana", "coconut"]);
520 test.done();
521};
522
523exports.leftAssociativeConsumesNothingIfLeftHandSideDoesntMatch = function(test) {
524 var parser = rules.leftAssociative(
525 keyword(),
526 identifier("+"),
527 function(left, right) {
528 return [left, right];
529 }
530 );
531 var result = parseString(parser, "+ +");
532 assertIsFailure(test, result, {
533 remaining:[
534 token("identifier", "+", stringSourceRange("+ +", 0, 1)),
535 token("identifier", "+", stringSourceRange("+ +", 2, 3)),
536 token("end", null, stringSourceRange("+ +", 3, 3))
537 ],
538 errors: [errors.error({
539 expected: "keyword",
540 actual: "identifier \"+\"",
541 location: stringSourceRange("+ +", 0, 1)
542 })]
543 });
544 test.done();
545};
546
547exports.leftAssociativeReturnsValueOfLeftHandSideIfRightHandSideDoesntMatch = function(test) {
548 var parser = rules.leftAssociative(
549 identifier(),
550 identifier("+"),
551 function(left, right) {
552 return [left, right];
553 }
554 );
555 var result = parseString(parser, "apple");
556 assertIsSuccessWithValue(test, result, "apple");
557 test.done();
558};
559
560exports.leftAssociativeAllowsLeftAssociativeRules = function(test) {
561 var parser = rules.leftAssociative(
562 identifier(),
563 identifier("+"),
564 function(left, right) {
565 return [left, right];
566 }
567 );
568 var result = parseString(parser, "apple + +");
569 assertIsSuccessWithValue(test, result, [["apple", "+"], "+"]);
570 test.done();
571};
572
573exports.leftAssociativeCanHaveMultipleChoicesForRight = function(test) {
574 var parser = rules.leftAssociative(
575 identifier(),
576 rules.leftAssociative.firstOf(
577 {rule: identifier("+"), func: function(left, right) { return [left, right]; }},
578 {rule: identifier(","), func: function(left, right) { return [left]; }}
579 )
580 );
581 var result = parseString(parser, "apple + ,");
582 assertIsSuccessWithValue(test, result, [["apple", "+"]]);
583 test.done();
584};
585
586exports.leftAssociativeReturnsErrorIfRightHandSideReturnsError = function(test) {
587 var parser = rules.leftAssociative(
588 identifier(),
589 rules.leftAssociative.firstOf(
590 {rule: rules.sequence(rules.sequence.cut(), identifier("+")), func: function() {}}
591 )
592 );
593 var result = parseString(parser, "apple");
594 assertIsError(test, result);
595 test.done();
596};
597
598exports.nonConsumingRuleDoesNotConsumeInput = function(test) {
599 var parser = rules.nonConsuming(rules.token("keyword", "true"));
600 var result = parseString(parser, "true");
601 assertIsSuccess(test, result, {
602 value: "true",
603 source: stringSourceRange("true", 0, 4),
604 remaining: [token("keyword", "true"), token("end", null)]
605 });
606 test.done();
607};
608
609var parseString = function(parser, string) {
610 var keywords = ["true", "false"];
611 var tokens = new Tokeniser({keywords: keywords}).tokenise(string);
612 return parser(new TokenIterator(tokens));
613};
614
615var parseTokens = function(parser, tokens) {
616 return parser(new TokenIterator(tokens));
617};