UNPKG

9.53 kBJavaScriptView Raw
1var _ = require("underscore");
2var options = require("option");
3var results = require("./parsing-results");
4var errors = require("./errors");
5var lazyIterators = require("./lazy-iterators");
6
7exports.token = function(tokenType, value) {
8 var matchValue = value !== undefined;
9 return function(input) {
10 var token = input.head();
11 if (token && token.name === tokenType && (!matchValue || token.value === value)) {
12 return results.success(token.value, input.tail(), token.source);
13 } else {
14 var expected = describeToken({name: tokenType, value: value});
15 return describeTokenMismatch(input, expected);
16 }
17 };
18};
19
20exports.tokenOfType = function(tokenType) {
21 return exports.token(tokenType);
22};
23
24exports.firstOf = function(name, parsers) {
25 if (!_.isArray(parsers)) {
26 parsers = Array.prototype.slice.call(arguments, 1);
27 }
28 return function(input) {
29 return lazyIterators
30 .fromArray(parsers)
31 .map(function(parser) {
32 return parser(input);
33 })
34 .filter(function(result) {
35 return result.isSuccess() || result.isError();
36 })
37 .first() || describeTokenMismatch(input, name);
38 };
39};
40
41exports.then = function(parser, func) {
42 return function(input) {
43 var result = parser(input);
44 if (!result.map) {
45 console.log(result);
46 }
47 return result.map(func);
48 };
49};
50
51exports.sequence = function() {
52 var parsers = Array.prototype.slice.call(arguments, 0);
53 var rule = function(input) {
54 var result = _.foldl(parsers, function(memo, parser) {
55 var result = memo.result;
56 var hasCut = memo.hasCut;
57 if (!result.isSuccess()) {
58 return {result: result, hasCut: hasCut};
59 }
60 var subResult = parser(result.remaining());
61 if (subResult.isCut()) {
62 return {result: result, hasCut: true};
63 } else if (subResult.isSuccess()) {
64 var values;
65 if (parser.isCaptured) {
66 values = result.value().withValue(parser, subResult.value());
67 } else {
68 values = result.value();
69 }
70 var remaining = subResult.remaining();
71 var source = input.to(remaining);
72 return {
73 result: results.success(values, remaining, source),
74 hasCut: hasCut
75 };
76 } else if (hasCut) {
77 return {result: results.error(subResult.errors(), subResult.remaining()), hasCut: hasCut};
78 } else {
79 return {result: subResult, hasCut: hasCut};
80 }
81 }, {result: results.success(new SequenceValues(), input), hasCut: false}).result;
82 var source = input.to(result.remaining());
83 return result.map(function(values) {
84 return values.withValue(exports.sequence.source, source);
85 });
86 };
87 rule.head = function() {
88 var firstCapture = _.find(parsers, isCapturedRule);
89 return exports.then(
90 rule,
91 exports.sequence.extract(firstCapture)
92 );
93 };
94 rule.map = function(func) {
95 return exports.then(
96 rule,
97 function(result) {
98 return func.apply(this, result.toArray());
99 }
100 );
101 };
102
103 function isCapturedRule(subRule) {
104 return subRule.isCaptured;
105 }
106
107 return rule;
108};
109
110var SequenceValues = function(values, valuesArray) {
111 this._values = values || {};
112 this._valuesArray = valuesArray || [];
113};
114
115SequenceValues.prototype.withValue = function(rule, value) {
116 if (rule.captureName && rule.captureName in this._values) {
117 throw new Error("Cannot add second value for capture \"" + rule.captureName + "\"");
118 } else {
119 var newValues = _.clone(this._values);
120 newValues[rule.captureName] = value;
121 var newValuesArray = this._valuesArray.concat([value]);
122 return new SequenceValues(newValues, newValuesArray);
123 }
124};
125
126SequenceValues.prototype.get = function(rule) {
127 if (rule.captureName in this._values) {
128 return this._values[rule.captureName];
129 } else {
130 throw new Error("No value for capture \"" + rule.captureName + "\"");
131 }
132};
133
134SequenceValues.prototype.toArray = function() {
135 return this._valuesArray;
136};
137
138exports.sequence.capture = function(rule, name) {
139 var captureRule = function() {
140 return rule.apply(this, arguments);
141 };
142 captureRule.captureName = name;
143 captureRule.isCaptured = true;
144 return captureRule;
145};
146
147exports.sequence.extract = function(rule) {
148 return function(result) {
149 return result.get(rule);
150 };
151};
152
153exports.sequence.applyValues = function(func) {
154 // TODO: check captureName doesn't conflict with source or other captures
155 var rules = Array.prototype.slice.call(arguments, 1);
156 return function(result) {
157 var values = rules.map(function(rule) {
158 return result.get(rule);
159 });
160 return func.apply(this, values);
161 };
162};
163
164exports.sequence.source = {
165 captureName: "☃source☃"
166};
167
168exports.sequence.cut = function() {
169 return function(input) {
170 return results.cut(input);
171 };
172};
173
174exports.optional = function(rule) {
175 return function(input) {
176 var result = rule(input);
177 if (result.isSuccess()) {
178 return result.map(options.some);
179 } else if (result.isFailure()) {
180 return results.success(options.none, input);
181 } else {
182 return result;
183 }
184 };
185};
186
187exports.zeroOrMoreWithSeparator = function(rule, separator) {
188 return repeatedWithSeparator(rule, separator, false);
189};
190
191exports.oneOrMoreWithSeparator = function(rule, separator) {
192 return repeatedWithSeparator(rule, separator, true);
193};
194
195var zeroOrMore = exports.zeroOrMore = function(rule) {
196 return function(input) {
197 var values = [];
198 var result;
199 while ((result = rule(input)) && result.isSuccess()) {
200 input = result.remaining();
201 values.push(result.value());
202 }
203 if (result.isError()) {
204 return result;
205 } else {
206 return results.success(values, input);
207 }
208 };
209};
210
211exports.oneOrMore = function(rule) {
212 return exports.oneOrMoreWithSeparator(rule, noOpRule);
213};
214
215function noOpRule(input) {
216 return results.success(null, input);
217}
218
219var repeatedWithSeparator = function(rule, separator, isOneOrMore) {
220 return function(input) {
221 var result = rule(input);
222 if (result.isSuccess()) {
223 var mainRule = exports.sequence.capture(rule, "main");
224 var remainingRule = zeroOrMore(exports.then(
225 exports.sequence(separator, mainRule),
226 exports.sequence.extract(mainRule)
227 ));
228 var remainingResult = remainingRule(result.remaining());
229 return results.success([result.value()].concat(remainingResult.value()), remainingResult.remaining());
230 } else if (isOneOrMore || result.isError()) {
231 return result;
232 } else {
233 return results.success([], input);
234 }
235 };
236};
237
238exports.leftAssociative = function(leftRule, rightRule, func) {
239 var rights;
240 if (func) {
241 rights = [{func: func, rule: rightRule}];
242 } else {
243 rights = rightRule;
244 }
245 rights = rights.map(function(right) {
246 return exports.then(right.rule, function(rightValue) {
247 return function(leftValue, source) {
248 return right.func(leftValue, rightValue, source);
249 };
250 });
251 });
252 var repeatedRule = exports.firstOf.apply(null, ["rules"].concat(rights));
253
254 return function(input) {
255 var start = input;
256 var leftResult = leftRule(input);
257 if (!leftResult.isSuccess()) {
258 return leftResult;
259 }
260 var repeatedResult = repeatedRule(leftResult.remaining());
261 while (repeatedResult.isSuccess()) {
262 var remaining = repeatedResult.remaining();
263 var source = start.to(repeatedResult.remaining());
264 var right = repeatedResult.value();
265 leftResult = results.success(
266 right(leftResult.value(), source),
267 remaining,
268 source
269 );
270 repeatedResult = repeatedRule(leftResult.remaining());
271 }
272 if (repeatedResult.isError()) {
273 return repeatedResult;
274 }
275 return leftResult;
276 };
277};
278
279exports.leftAssociative.firstOf = function() {
280 return Array.prototype.slice.call(arguments, 0);
281};
282
283exports.nonConsuming = function(rule) {
284 return function(input) {
285 return rule(input).changeRemaining(input);
286 };
287};
288
289var describeToken = function(token) {
290 if (token.value) {
291 return token.name + " \"" + token.value + "\"";
292 } else {
293 return token.name;
294 }
295};
296
297function describeTokenMismatch(input, expected) {
298 var error;
299 var token = input.head();
300 if (token) {
301 error = errors.error({
302 expected: expected,
303 actual: describeToken(token),
304 location: token.source
305 });
306 } else {
307 error = errors.error({
308 expected: expected,
309 actual: "end of tokens"
310 });
311 }
312 return results.failure([error], input);
313}