UNPKG

3.57 kBJavaScriptView Raw
1'use strict'
2
3const _ = require('lodash')
4const colors = require('ansi-colors')
5const {
6 TokenTypeData,
7 Token,
8 cloneToken,
9 SourceTag,
10 UnwrappedExpr
11} = require('./lang');
12const currentLine = require('./currentLine');
13const {searchExpressionsWithoutInnerFunctions} = require('./expr-search');
14
15const tokensNotAllowedToReuseFromOtherExpressions = {
16 val: true,
17 key: true,
18 loop: true,
19 context: true
20}
21
22function 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
48const handler = {}
49const 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 // console.log(target, key);
81 return Reflect.get(target, key);
82 }
83 return (...args) => {
84 // console.log(key, args);
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
116module.exports = {
117 initProxyHandler: init,
118 proxyHandler: handler
119};