UNPKG

2.48 kBJavaScriptView Raw
1'use strict'
2const {
3 Expr,
4 Token,
5 SourceTag,
6 TokenTypeData,
7 isExpression,
8 WrappedPrimitive,
9 UnwrappedExpr
10} = require('./lang');
11const currentLine = require('./currentLine');
12const {convertArrayAndObjectsToExpr, createExpr} = require('./expressionBuilder');
13const {wrap} = require('./unwrapable-proxy');
14const {searchExpressions} = require('./expr-search');
15const privateUnwrap = (item) => item[UnwrappedExpr] ? item[UnwrappedExpr] : item;
16
17function throwOnSelfReferencesToPlaceholder(expr, abstract) {
18 if (expr[0] === abstract[0]) {
19 throw new Error(
20 `trying to implement abstract ${abstract[1]} with itself`
21 );
22 }
23 searchExpressions(subExpr => {
24 subExpr.forEach(token => {
25 if (privateUnwrap(token) === abstract) {
26 throw new Error(
27 `trying to implement abstract ${abstract[1]} with expression that references the abstract
28this causes an endless loop ${subExpr[0][SourceTag]}`
29 );
30 }
31 });
32 }, [expr]);
33}
34
35const chain = val => wrap(convertArrayAndObjectsToExpr(val))
36const abstract = title => {
37 if (typeof title !== 'string') {
38 throw new Error('the title of abstract must be a string');
39 }
40 return wrap(createExpr(new Token('abstract', currentLine()), title, new Error(`failed to implement ${title}`)));
41}
42const implement = (abstract, expr) => {
43 const target = privateUnwrap(abstract);
44 if (typeof expr === 'boolean' || typeof expr === 'string' || typeof expr === 'number') {
45 expr = new WrappedPrimitive(expr);
46 }
47 if (expr instanceof WrappedPrimitive) {
48 expr = Expr(new Token('quote', currentLine()), expr.toJSON());
49 }
50 if (!isExpression(target) || target[0].$type !== 'abstract') {
51 throw new Error('can only implement an abstract');
52 }
53 throwOnSelfReferencesToPlaceholder(expr, target)
54 target.splice(0, target.length, ...expr);
55 return abstract;
56}
57const template = (parts, ...args) => parts.slice(1).reduce((result, current, index) => result.plus(args[index]).plus(chain(current)), chain(parts[0]));
58
59const frontendApi = {chain, abstract, implement, template};
60
61Object.keys(TokenTypeData).forEach(t => {
62 if (TokenTypeData[t].private) {
63 return; // privates aren't exported - only used in optimizing code or internally
64 }
65 if (TokenTypeData[t].nonVerb) {
66 frontendApi[t] = wrap(new Token(t));
67 } else if (TokenTypeData[t].nonChained) {
68 frontendApi[t] = (...args) => wrap(createExpr(new Token(t, currentLine()), ...args));
69 }
70});
71
72module.exports = frontendApi