1 | var rules = require("../lib/rules");
|
2 | var bottomUp = require("../lib/bottom-up");
|
3 | var testing = require("../lib/testing");
|
4 | var TokenIterator = require("../lib/TokenIterator");
|
5 | var errors = require("../lib/errors");
|
6 | var results = require("../lib/parsing-results");
|
7 | var StringSource = require("../lib/StringSource");
|
8 | var assertIsSuccess = testing.assertIsSuccess;
|
9 | var assertIsSuccessWithValue = testing.assertIsSuccessWithValue;
|
10 | var assertIsFailure = testing.assertIsFailure;
|
11 | var assertIsFailureWithRemaining = testing.assertIsFailureWithRemaining;
|
12 | var assertIsError = testing.assertIsError;
|
13 | var Tokeniser = require("./Tokeniser");
|
14 | var Token = require("../lib/Token");
|
15 |
|
16 | var source = function(string, startIndex, endIndex) {
|
17 | return new StringSource(string).range(startIndex, endIndex);
|
18 | };
|
19 |
|
20 | var token = function(tokenType, value, source) {
|
21 | return new Token(tokenType, value, source);
|
22 | };
|
23 |
|
24 | var partialCallRule = bottomUp.infix("call", function(parser) {
|
25 | return rules.sequence(
|
26 | rules.token("symbol", "("),
|
27 | rules.sequence.capture(parser.rule()),
|
28 | rules.token("symbol", ")")
|
29 | ).head();
|
30 | }).map(function(left, arg) {
|
31 | return [left, arg];
|
32 | });
|
33 |
|
34 | var partialAddRule = bottomUp.infix("add", function(parser) {
|
35 | return rules.sequence(
|
36 | rules.token("symbol", "+"),
|
37 | rules.sequence.capture(parser.leftAssociative("add"))
|
38 | ).head();
|
39 | }).map(function(left, right) {
|
40 | return ["+", left, right];
|
41 | });
|
42 |
|
43 | var partialMultiplyRule = bottomUp.infix("multiply", function(parser) {
|
44 | return rules.sequence(
|
45 | rules.token("symbol", "*"),
|
46 | rules.sequence.capture(parser.leftAssociative("multiply"))
|
47 | ).head();
|
48 | }).map(function(left, right) {
|
49 | return ["*", left, right];
|
50 | });
|
51 |
|
52 | var partialPowerRule = bottomUp.infix("power", function(parser) {
|
53 | return rules.sequence(
|
54 | rules.token("symbol", "^"),
|
55 | rules.sequence.capture(parser.rightAssociative("power"))
|
56 | ).head();
|
57 | }).map(function(left, right) {
|
58 | return ["^", left, right];
|
59 | });
|
60 |
|
61 | exports.canParsePrefixExpression = function(test) {
|
62 | var rule = bottomUp.parser("expression",
|
63 | [rules.tokenOfType("identifier")],
|
64 | []
|
65 | ).rule();
|
66 | var result = parse(rule, [
|
67 | token("identifier", "blah", source("blah", 0, 4)),
|
68 | token("end", null, source("blah", 4, 4))
|
69 | ]);
|
70 | assertIsSuccess(test, result, {
|
71 | value: "blah",
|
72 | source: source("blah", 0, 4)
|
73 | });
|
74 | test.done();
|
75 | };
|
76 |
|
77 | exports.canParseSimpleInfixExpression = function(test) {
|
78 | var rule = bottomUp.parser("expression",
|
79 | [rules.tokenOfType("identifier")],
|
80 | [partialCallRule]
|
81 | ).rule();
|
82 |
|
83 | var result = parse(rule, [
|
84 | token("identifier", "print", source("print(name)", 0, 5)),
|
85 | token("symbol", "(", source("print(name)", 5, 6)),
|
86 | token("identifier", "name", source("print(name)", 6, 10)),
|
87 | token("symbol", ")", source("print(name)", 10, 11)),
|
88 | token("end", null, source("print(name)", 11, 11))
|
89 | ]);
|
90 | assertIsSuccess(test, result, {
|
91 | value: ["print", "name"],
|
92 | source: source("print(name)", 0, 11)
|
93 | });
|
94 | test.done();
|
95 | };
|
96 |
|
97 | exports.parsingStopsIfPrefixRuleFails = function(test) {
|
98 | var rule = bottomUp.parser("expression",
|
99 | [rules.tokenOfType("identifier")],
|
100 | [partialCallRule]
|
101 | ).rule();
|
102 |
|
103 | var result = parse(rule, [
|
104 | token("symbol", "(", source("(name)", 0, 1)),
|
105 | token("identifier", "name", source("(name)", 1, 5)),
|
106 | token("symbol", ")", source("(name)", 5, 6)),
|
107 | token("end", null, source("(name)", 6, 6))
|
108 | ]);
|
109 | assertIsFailure(test, result, {
|
110 | remaining: [
|
111 | token("symbol", "(", source("(name)", 0, 1)),
|
112 | token("identifier", "name", source("(name)", 1, 5)),
|
113 | token("symbol", ")", source("(name)", 5, 6)),
|
114 | token("end", null, source("(name)", 6, 6))
|
115 | ],
|
116 | errors: [errors.error({
|
117 | expected: "expression",
|
118 | actual: "symbol \"(\"",
|
119 | location: source("(name)", 0, 1)
|
120 | })]
|
121 | });
|
122 | test.done();
|
123 | };
|
124 |
|
125 | exports.canParseExpressionWithTwoLeftAssociativeOperators = function(test) {
|
126 | var expressionParser = bottomUp.parser("expression",
|
127 | [rules.tokenOfType("number")],
|
128 | [
|
129 | partialMultiplyRule,
|
130 | partialAddRule
|
131 | ]
|
132 | );
|
133 |
|
134 | var rule = expressionParser.rule();
|
135 |
|
136 | var result = parse(rule, [
|
137 | token("number", "1", source("1 * 2 * 3 + 4 * 5", 0, 1)),
|
138 | token("symbol", "*", source("1 * 2 * 3 + 4 * 5", 2, 3)),
|
139 | token("number", "2", source("1 * 2 * 3 + 4 * 5", 4, 5)),
|
140 | token("symbol", "*", source("1 * 2 * 3 + 4 * 5", 6, 7)),
|
141 | token("number", "3", source("1 * 2 * 3 + 4 * 5", 8, 9)),
|
142 | token("symbol", "+", source("1 * 2 * 3 + 4 * 5", 10, 11)),
|
143 | token("number", "4", source("1 * 2 * 3 + 4 * 5", 12, 13)),
|
144 | token("symbol", "*", source("1 * 2 * 3 + 4 * 5", 14, 15)),
|
145 | token("number", "5", source("1 * 2 * 3 + 4 * 5", 16, 17)),
|
146 | token("end", null, source("1 * 2 * 3 + 4 * 5", 17, 17))
|
147 | ]);
|
148 | assertIsSuccess(test, result, {
|
149 | value: ["+", ["*", ["*", "1", "2"], "3"], ["*", "4", "5"]],
|
150 | source: source("1 * 2 * 3 + 4 * 5", 0, 17)
|
151 | });
|
152 | test.done();
|
153 | };
|
154 |
|
155 | exports.canParseExpressionWithRightAssociativeOperators = function(test) {
|
156 | var expressionParser = bottomUp.parser("expression",
|
157 | [rules.tokenOfType("number")],
|
158 | [
|
159 | partialPowerRule,
|
160 | partialAddRule
|
161 | ]
|
162 | );
|
163 |
|
164 | var rule = expressionParser.rule();
|
165 |
|
166 | var result = parse(rule, [
|
167 | token("number", "1", source("1 ^ 2 ^ 3 + 4 ^ 5", 0, 1)),
|
168 | token("symbol", "^", source("1 ^ 2 ^ 3 + 4 ^ 5", 2, 3)),
|
169 | token("number", "2", source("1 ^ 2 ^ 3 + 4 ^ 5", 4, 5)),
|
170 | token("symbol", "^", source("1 ^ 2 ^ 3 + 4 ^ 5", 6, 7)),
|
171 | token("number", "3", source("1 ^ 2 ^ 3 + 4 ^ 5", 8, 9)),
|
172 | token("symbol", "+", source("1 ^ 2 ^ 3 + 4 ^ 5", 10, 11)),
|
173 | token("number", "4", source("1 ^ 2 ^ 3 + 4 ^ 5", 12, 13)),
|
174 | token("symbol", "^", source("1 ^ 2 ^ 3 + 4 ^ 5", 14, 15)),
|
175 | token("number", "5", source("1 ^ 2 ^ 3 + 4 ^ 5", 16, 17)),
|
176 | token("end", null, source("1 ^ 2 ^ 3 + 4 ^ 5", 17, 17))
|
177 | ]);
|
178 | assertIsSuccess(test, result, {
|
179 | value: ["+", ["^", "1", ["^", "2", "3"]], ["^", "4", "5"]],
|
180 | source: source("1 ^ 2 ^ 3 + 4 ^ 5", 0, 17)
|
181 | });
|
182 | test.done();
|
183 | };
|
184 |
|
185 | var parse = function(parser, tokens) {
|
186 | return parser(new TokenIterator(tokens));
|
187 | };
|