1 | 'use strict'
|
2 | const {
|
3 | Expr,
|
4 | Token,
|
5 | SourceTag,
|
6 | TokenTypeData,
|
7 | isExpression,
|
8 | WrappedPrimitive,
|
9 | UnwrappedExpr
|
10 | } = require('./lang');
|
11 | const currentLine = require('./currentLine');
|
12 | const {convertArrayAndObjectsToExpr, createExpr} = require('./expressionBuilder');
|
13 | const {wrap} = require('./unwrapable-proxy');
|
14 | const {searchExpressions} = require('./expr-search');
|
15 | const privateUnwrap = (item) => item[UnwrappedExpr] ? item[UnwrappedExpr] : item;
|
16 |
|
17 | function 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
|
28 | this causes an endless loop ${subExpr[0][SourceTag]}`
|
29 | );
|
30 | }
|
31 | });
|
32 | }, [expr]);
|
33 | }
|
34 |
|
35 | const chain = val => wrap(convertArrayAndObjectsToExpr(val))
|
36 | const 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 | }
|
42 | const 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 | }
|
57 | const template = (parts, ...args) => parts.slice(1).reduce((result, current, index) => result.plus(args[index]).plus(chain(current)), chain(parts[0]));
|
58 |
|
59 | const frontendApi = {chain, abstract, implement, template};
|
60 |
|
61 | Object.keys(TokenTypeData).forEach(t => {
|
62 | if (TokenTypeData[t].private) {
|
63 | return;
|
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 |
|
72 | module.exports = frontendApi
|