UNPKG

12.8 kBJavaScriptView Raw
1/*
2 * Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 * http://www.apache.org/licenses/LICENSE-2.0
7 *
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14
15'use strict';
16
17var P = require('parsimmon');
18var uuid = require('uuid');
19var flatten = require('./util').flatten;
20var CommonMarkUtils = require('@accordproject/markdown-common').CommonMarkUtils;
21
22/**
23 * Creates a variable output
24 * @param {object} variable the variable ast node
25 * @param {*} value the variable value
26 * @returns {object} the variable
27 */
28function mkVariable(variable, value) {
29 var result = {};
30 result.name = variable.name;
31 result.elementType = variable.elementType;
32 result.value = value;
33 return result;
34}
35
36/**
37 * Are two variable values equal
38 * @param {object} value1 the first value
39 * @param {object} value2 the second value
40 * @returns {object} the compound variable
41 */
42function variableEqual(value1, value2) {
43 var type1 = typeof value1;
44 var type2 = typeof value2;
45 if (type1 === 'object' && type2 === 'object') {
46 for (var key1 in value1) {
47 if (!value2[key1]) {
48 return false;
49 }
50 if (!variableEqual(value1[key1], value2[key1])) {
51 return false;
52 }
53 }
54 for (var key2 in value2) {
55 if (!value1[key2]) {
56 return false;
57 }
58 }
59 } else if (value1 !== value2) {
60 return false;
61 }
62 return true;
63}
64
65/**
66 * Creates a compound variable output
67 * @param {object} elementType the type of the variable
68 * @param {*} value the variable components
69 * @returns {object} the compound variable
70 */
71function mkCompoundVariable(elementType, value) {
72 var result = {};
73 result.$class = elementType;
74 for (var i = 0; i < value.length; i++) {
75 var field = value[i];
76 if (result[field.name]) {
77 if (!variableEqual(result[field.name], field.value)) {
78 var message = "Inconsistent values for variable ".concat(field.name, ": ").concat(result[field.name], " and ").concat(field.value);
79 throw new Error(message);
80 }
81 } else {
82 result[field.name] = field.value;
83 }
84 }
85 if (result.this) {
86 result = result.this;
87 }
88 return result;
89}
90
91/**
92 * Creates a conditional output
93 * @param {object} condNode the conditional ast node
94 * @param {*} value the variable value
95 * @returns {object} the conditional
96 */
97function mkCond(condNode, value) {
98 var result = {};
99 result.name = condNode.name;
100 result.elementType = 'Boolean';
101 result.value = value;
102 return result;
103}
104
105/**
106 * Creates an optional output
107 * @param {object} optNode the optional ast node
108 * @param {*} value the variable value
109 * @returns {object} the optional
110 */
111function mkOpt(optNode, value) {
112 var result = {};
113 result.name = optNode.name;
114 result.elementType = optNode.elementType;
115 result.value = value;
116 return result;
117}
118
119/**
120 * Creates a List output
121 * @param {object} listNode the list ast node
122 * @param {*} value the variable value
123 * @returns {object} the conditional
124 */
125function mkList(listNode, value) {
126 var result = {};
127 result.name = listNode.name;
128 result.elementType = 'List';
129 result.value = [];
130 for (var i = 0; i < value.length; i++) {
131 var item = mkCompoundVariable(listNode.elementType, value[i]);
132 result.value.push(item);
133 }
134 return result;
135}
136
137/**
138 * Creates a Join output
139 * @param {object} joinNode the join ast node
140 * @param {*} value the variable value
141 * @returns {object} the conditional
142 */
143function mkJoin(joinNode, value) {
144 var result = {};
145 result.name = joinNode.name;
146 result.elementType = 'List';
147 result.value = [];
148 for (var i = 0; i < value.length; i++) {
149 result.value.push(mkCompoundVariable(joinNode.elementType, value[i]));
150 }
151 return result;
152}
153
154/**
155 * Creates a clause output
156 * @param {object} clause the clause ast node
157 * @param {*} value the clause value
158 * @returns {object} the clause
159 */
160function mkClause(clause, value) {
161 return mkCompoundVariable(clause.elementType, value.concat({
162 'name': 'clauseId',
163 'elementType': 'String',
164 'value': uuid.v4()
165 }));
166}
167
168/**
169 * Creates a wrapped clause output
170 * @param {object} clause the wrapped clause ast node
171 * @param {string} src the clause source url
172 * @param {*} value the wrapped clause value
173 * @returns {object} the clause
174 */
175function mkWrappedClause(clause, src, value) {
176 var result = {
177 'name': clause.name,
178 'elementType': clause.elementType,
179 'value': mkClause(clause, value)
180 };
181 if (src) {
182 result.src = src;
183 }
184 return [result];
185}
186
187/**
188 * Creates a contract output
189 * @param {object} contract the contract ast node
190 * @param {*} value the contract value
191 * @returns {object} the contract
192 */
193function mkContract(contract, value) {
194 return mkCompoundVariable(contract.elementType, value.concat({
195 'name': 'contractId',
196 'elementType': 'String',
197 'value': uuid.v4()
198 }));
199}
200
201/**
202 * Core parsing components
203 */
204
205/**
206 * Creates a parser for Text chunks
207 * @param {string} text the text
208 * @returns {object} the parser
209 */
210function textParser(text) {
211 return P.string(CommonMarkUtils.escapeText(text));
212}
213
214/**
215 * Creates a parser for a String
216 * @returns {object} the parser
217 */
218function stringLiteralParser() {
219 return P.regexp(/"[^"]*"/).desc('A String literal "..."');
220}
221
222/**
223 * Creates a parser for a name
224 * @returns {object} the parser
225 */
226function nameParser() {
227 return P.regexp(/[A-Za-z0-9_-]+/).desc('A name');
228}
229
230/**
231 * Creates a parser for choices
232 * @param {object[]} parsers - the individual parsers
233 * @returns {object} the parser
234 */
235function choiceParser(parsers) {
236 return P.alt.apply(null, parsers);
237}
238
239/**
240 * Creates a parser for choices of strings
241 * @param {string[]} values - the individual strings
242 * @returns {object} the parser
243 */
244function choiceStringsParser(values) {
245 return choiceParser(values.map(function (x) {
246 return P.string(x);
247 }));
248}
249
250/**
251 * Creates a parser for sequences
252 * @param {object[]} parsers - the individual parsers
253 * @returns {object} the parser
254 */
255function seqParser(parsers) {
256 return P.seqMap.apply(null, parsers.concat([function () {
257 var args = Array.prototype.slice.call(arguments);
258 return args.filter(function (x) {
259 return !(typeof x === 'string');
260 });
261 }]));
262}
263
264/**
265 * Creates a parser for sequences of function parsers
266 * @param {object[]} parsers - the individual parsers
267 * @returns {object} the parser
268 */
269function seqFunParser(parsers) {
270 return r => P.seqMap.apply(null, parsers.map(x => x(r)).concat([function () {
271 var args = Array.prototype.slice.call(arguments);
272 return args.filter(function (x) {
273 return !(typeof x === 'string');
274 });
275 }]));
276}
277
278/**
279 * Creates a parser for a computed value
280 * @returns {object} the parser
281 */
282function computedParser() {
283 return P.regexp(/{{%[^%]*%}}/).desc('A computed variable {{ ... }}');
284}
285
286/**
287 * Creates a parser for Enums
288 * @param {string[]} enums - the enum values
289 * @returns {object} the parser
290 */
291function enumParser(enums) {
292 return choiceStringsParser(enums).map(function (x) {
293 return x;
294 });
295}
296
297/**
298 * Creates a parser for a conditional block
299 * @param {object} condNode the conditional ast node
300 * @param {object} whenTrue the parser when the condition is true
301 * @param {object} whenFalse the parser when the condition is false
302 * @returns {object} the parser
303 */
304function conditionalParser(condNode, whenTrue, whenFalse) {
305 return P.alt(whenTrue.map(x => true), whenFalse.map(x => false)).map(function (x) {
306 return mkCond(condNode, x);
307 });
308}
309
310/**
311 * Creates a parser for an optional block
312 * @param {object} optNode the optional ast node
313 * @param {object} whenSome the parser when the option is present
314 * @param {object} whenNone the parser when the option is absent
315 * @returns {object} the parser
316 */
317function optionalParser(optNode, whenSome, whenNone) {
318 return P.alt(whenSome.map(function (x) {
319 return mkCompoundVariable(optNode.elementType, flatten(x));
320 }), whenNone.map(x => null)).map(function (x) {
321 return mkOpt(optNode, x);
322 });
323}
324
325/**
326 * Creates a parser for list blocks
327 * @param {object} listNode the list ast node
328 * @param {object} bullet the parser for the bullet
329 * @param {object} content the parser for the content of the list
330 * @returns {object} the parser
331 */
332function listBlockParser(listNode, bullet, content) {
333 // XXX optionally insert a new line for non-tight lists
334 return P.seq(P.alt(bullet, P.seq(P.string('\n'), bullet)), content).map(function (x) {
335 return x[1]; // XXX First element is bullet
336 }).many().map(function (x) {
337 return mkList(listNode, x);
338 });
339}
340
341/**
342 * Creates a parser for an unordered list block
343 * @param {object} listNode - the list ast node
344 * @param {object} content - the parser for the content of the list
345 * @param {string} prefix - the list item prefix
346 * @returns {object} the parser
347 */
348function ulistBlockParser(listNode, content, prefix) {
349 return listBlockParser(listNode, P.seq(P.string(prefix), P.string('- ')), content);
350}
351
352/**
353 * Creates a parser for an ordered list block
354 * @param {object} listNode the list ast node
355 * @param {object} content the parser for the content of the list
356 * @param {string} prefix - the list item prefix
357 * @returns {object} the parser
358 */
359function olistBlockParser(listNode, content, prefix) {
360 return listBlockParser(listNode, P.seq(P.string(prefix), P.regexp(/[0-9]+/), P.string('. ')), content);
361}
362
363/**
364 * Creates a parser for joine blocks
365 * @param {object} joinNode the join ast node
366 * @param {object} content the parser for the content of the list
367 * @returns {object} the parser
368 */
369function joinBlockParser(joinNode, content) {
370 var separator = joinNode.separator;
371 return P.seq(content, P.seq(P.string(separator), content).map(x => x[1]).many()).map(function (x) {
372 return [x[0]].concat(x[1]);
373 }).map(function (x) {
374 return mkJoin(joinNode, x);
375 });
376}
377
378/**
379 * Creates a parser for a with block
380 * @param {object} elementType the type for the with clause
381 * @param {object} content the parser for the content of the with
382 * @returns {object} the parser
383 */
384function withParser(elementType, content) {
385 return content.map(function (x) {
386 return mkCompoundVariable(elementType, flatten(x));
387 });
388}
389
390/**
391 * Creates a parser for clause content
392 * @param {object} clause the clause ast node
393 * @param {object} content the parser for the content of the clause
394 * @returns {object} the parser
395 */
396function clauseParser(clause, content) {
397 return content.skip(P.optWhitespace).map(function (x) {
398 return mkClause(clause, flatten(x));
399 });
400}
401
402/**
403 * Creates a parser for contract content
404 * @param {object} contract the contract ast node
405 * @param {object} content the parser for the content of the contract
406 * @returns {object} the parser
407 */
408function contractParser(contract, content) {
409 return content.skip(P.optWhitespace).map(function (x) {
410 return mkContract(contract, flatten(x));
411 });
412}
413
414/**
415 * Creates a parser for a clause
416 * @param {object} clause the clause ast node
417 * @param {object} content the parser for the content of the clause
418 * @returns {object} the parser
419 */
420function wrappedClauseParser(clause, content) {
421 var clauseEnd = P.string('}}\n');
422 var clauseBefore = P.seq(P.string('\n\n{{#clause '), nameParser(), P.alt(P.seq(P.string(' src='), stringLiteralParser(), clauseEnd), clauseEnd));
423 var clauseAfter = P.string('\n{{/clause}}');
424 return P.seq(clauseBefore, content, clauseAfter).map(function (x) {
425 var srcAttr = x[0][2];
426 var src = srcAttr ? srcAttr[1].substring(1, srcAttr[1].length - 1) : null;
427 return mkWrappedClause(clause, src, flatten(x[1]));
428 });
429}
430module.exports.mkVariable = mkVariable;
431module.exports.mkCompoundVariable = mkCompoundVariable;
432module.exports.stringLiteralParser = stringLiteralParser;
433module.exports.textParser = textParser;
434module.exports.seqParser = seqParser;
435module.exports.seqFunParser = seqFunParser;
436module.exports.choiceStringsParser = choiceStringsParser;
437module.exports.computedParser = computedParser;
438module.exports.enumParser = enumParser;
439module.exports.conditionalParser = conditionalParser;
440module.exports.optionalParser = optionalParser;
441module.exports.ulistBlockParser = ulistBlockParser;
442module.exports.olistBlockParser = olistBlockParser;
443module.exports.joinBlockParser = joinBlockParser;
444module.exports.withParser = withParser;
445module.exports.clauseParser = clauseParser;
446module.exports.wrappedClauseParser = wrappedClauseParser;
447module.exports.contractParser = contractParser;
\No newline at end of file