1 | 'use strict'
|
2 |
|
3 | const _ = require('lodash')
|
4 | const colors = require('ansi-colors')
|
5 | const {
|
6 | TokenTypeData,
|
7 | Token,
|
8 | cloneToken,
|
9 | SourceTag,
|
10 | UnwrappedExpr
|
11 | } = require('./lang');
|
12 | const currentLine = require('./currentLine');
|
13 | const {searchExpressionsWithoutInnerFunctions} = require('./expr-search');
|
14 |
|
15 | const tokensNotAllowedToReuseFromOtherExpressions = {
|
16 | val: true,
|
17 | key: true,
|
18 | loop: true,
|
19 | context: true
|
20 | }
|
21 |
|
22 | function throwOnTokensFromOtherFuncs(expr, type, tag) {
|
23 | searchExpressionsWithoutInnerFunctions(subExpr => {
|
24 | subExpr.forEach(token => {
|
25 | if (
|
26 | token instanceof Token &&
|
27 | token[SourceTag] &&
|
28 | token[SourceTag] !== tag &&
|
29 | tokensNotAllowedToReuseFromOtherExpressions[token.$type]
|
30 | ) {
|
31 | const emph = txt => colors.yellow(txt)
|
32 | throw new Error(
|
33 | `
|
34 | Using arguments (key/value) from one carmi function in the scope of another carmi function is not allowed.
|
35 |
|
36 | In expression ${emph(subExpr[0].$type)} at ${emph(subExpr[0][SourceTag])},
|
37 | expression ${emph(JSON.stringify(token))} from outer function at ${emph(token[SourceTag])}
|
38 | is used in inner function ${emph(type)} at ${emph(tag.toString())}
|
39 |
|
40 | Use a context in the inner function instead.
|
41 | `
|
42 | );
|
43 | }
|
44 | });
|
45 | }, [expr]);
|
46 | }
|
47 |
|
48 | const handler = {}
|
49 | const init = _.once(({
|
50 | sugar,
|
51 | unwrapableProxy: {wrap},
|
52 | expressionBuilder: {createExpr},
|
53 | frontend: {chain}
|
54 | }) => {
|
55 | handler.get = (target, key) => {
|
56 | const tokenData = TokenTypeData[key];
|
57 | if (
|
58 | !tokenData &&
|
59 | typeof key === 'string' &&
|
60 | key !== '$type' &&
|
61 | key !== '$primitive' &&
|
62 | key !== 'length' &&
|
63 | key !== 'forEach' &&
|
64 | key !== 'inspect' &&
|
65 | key !== 'toJSON' &&
|
66 | Number.isNaN(parseInt(key, 10))
|
67 | ) {
|
68 | if (sugar[key]) {
|
69 | return (...args) => sugar[key](chain(target), ...args);
|
70 | }
|
71 | throw new Error(`unknown token: "${key}" at ${currentLine()}`);
|
72 | }
|
73 | if (key === UnwrappedExpr) {
|
74 | if (target[UnwrappedExpr]) {
|
75 | return target[UnwrappedExpr]
|
76 | }
|
77 | return target;
|
78 | }
|
79 | if (!tokenData || tokenData.nonVerb || !tokenData.chainIndex) {
|
80 |
|
81 | return Reflect.get(target, key);
|
82 | }
|
83 | return (...args) => {
|
84 |
|
85 | const sourceTag = currentLine()
|
86 | args = [new Token(key, sourceTag), ...args];
|
87 | if (tokenData.chainIndex) {
|
88 | if (tokenData.collectionVerb && tokenData.chainIndex === 2) {
|
89 | if (typeof args[1] === 'function') {
|
90 | const origFunction = args[1];
|
91 | const funcArgs = tokenData.recursive ? ['loop', 'val', 'key', 'context'] : ['val', 'key', 'context'];
|
92 | const funcArgsTokens = funcArgs.map(t => wrap(new Token(t, sourceTag)));
|
93 | args[1] = origFunction.apply(null, funcArgsTokens);
|
94 | throwOnTokensFromOtherFuncs(args[1], args[0].$type, sourceTag);
|
95 | } else if (typeof args[1] === 'string') {
|
96 | args[1] = createExpr(new Token('get'), args[1], new Token('val'));
|
97 | }
|
98 | args[1] = createExpr(new Token('func'), args[1]);
|
99 | }
|
100 | args.splice(tokenData.chainIndex, 0, target);
|
101 | }
|
102 | return wrap(createExpr(...args));
|
103 | };
|
104 | };
|
105 |
|
106 | handler.apply = (target, thisArg, args) => {
|
107 | if (target instanceof Token) {
|
108 | wrap(createExpr(cloneToken(target), ...args));
|
109 | } else {
|
110 | throw `${String(target)} not a function`;
|
111 | }
|
112 | };
|
113 | })
|
114 |
|
115 |
|
116 | module.exports = {
|
117 | initProxyHandler: init,
|
118 | proxyHandler: handler
|
119 | };
|