yaml-flow 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/card-compute.js +446 -0
- package/browser/live-cards.js +1381 -0
- package/browser/live-cards.schema.json +246 -0
- package/dist/card-compute/index.cjs +324 -0
- package/dist/card-compute/index.cjs.map +1 -0
- package/dist/card-compute/index.d.cts +68 -0
- package/dist/card-compute/index.d.ts +68 -0
- package/dist/card-compute/index.js +319 -0
- package/dist/card-compute/index.js.map +1 -0
- package/dist/index.cjs +322 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +322 -7
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
// card_compute.js — Pure JSON expression evaluator for LiveCards nodes
|
|
2
|
+
//
|
|
3
|
+
// Isomorphic: works in browser (global), Node.js (require), and ESM (import).
|
|
4
|
+
// No DOM dependency. Usable on both client and server.
|
|
5
|
+
//
|
|
6
|
+
// API:
|
|
7
|
+
// CardCompute.run(node) → mutates node.state with computed values, returns node
|
|
8
|
+
// CardCompute.eval(expr, node) → evaluates a single compute_expr, returns value
|
|
9
|
+
// CardCompute.resolve(node, path) → deep-get a state path like "state.foo.bar"
|
|
10
|
+
// CardCompute.registerFunction(name, fn) → add custom compute function
|
|
11
|
+
// CardCompute.functions → read-only map of all registered functions
|
|
12
|
+
//
|
|
13
|
+
// Compute declarations (node.compute):
|
|
14
|
+
// {
|
|
15
|
+
// "total_value": { "fn": "sum", "input": "state.raw_quotes" },
|
|
16
|
+
// "avg_price": { "fn": "avg", "input": "state.raw_quotes" },
|
|
17
|
+
// "direction": { "fn": "if", "cond": { "fn": "gt", "input": ["state.latest", "state.prev"] }, "then": "up", "else": "down" }
|
|
18
|
+
// }
|
|
19
|
+
|
|
20
|
+
(function (root, factory) {
|
|
21
|
+
if (typeof module === 'object' && module.exports) {
|
|
22
|
+
module.exports = factory(); // Node / CommonJS
|
|
23
|
+
} else if (typeof define === 'function' && define.amd) {
|
|
24
|
+
define(factory); // AMD
|
|
25
|
+
} else {
|
|
26
|
+
root.CardCompute = factory(); // Browser global
|
|
27
|
+
}
|
|
28
|
+
}(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
29
|
+
'use strict';
|
|
30
|
+
|
|
31
|
+
// ===========================================================================
|
|
32
|
+
// Deep path utilities
|
|
33
|
+
// ===========================================================================
|
|
34
|
+
|
|
35
|
+
function _deepGet(obj, path) {
|
|
36
|
+
if (!path || !obj) return undefined;
|
|
37
|
+
const parts = path.split('.');
|
|
38
|
+
let cur = obj;
|
|
39
|
+
for (let i = 0; i < parts.length; i++) {
|
|
40
|
+
if (cur == null) return undefined;
|
|
41
|
+
cur = cur[parts[i]];
|
|
42
|
+
}
|
|
43
|
+
return cur;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function _deepSet(obj, path, value) {
|
|
47
|
+
const parts = path.split('.');
|
|
48
|
+
let cur = obj;
|
|
49
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
50
|
+
if (cur[parts[i]] == null || typeof cur[parts[i]] !== 'object') cur[parts[i]] = {};
|
|
51
|
+
cur = cur[parts[i]];
|
|
52
|
+
}
|
|
53
|
+
cur[parts[parts.length - 1]] = value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolve(node, path) {
|
|
57
|
+
if (!path) return undefined;
|
|
58
|
+
return _deepGet(node, path);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ===========================================================================
|
|
62
|
+
// Built-in function registry
|
|
63
|
+
// ===========================================================================
|
|
64
|
+
|
|
65
|
+
var _fns = {};
|
|
66
|
+
|
|
67
|
+
// ---- Aggregates ----
|
|
68
|
+
|
|
69
|
+
_fns.sum = function (input, _eval, opts) {
|
|
70
|
+
var a = Array.isArray(input) ? input : [];
|
|
71
|
+
return opts.field
|
|
72
|
+
? a.reduce(function (s, r) { return s + (Number(r[opts.field]) || 0); }, 0)
|
|
73
|
+
: a.reduce(function (s, v) { return s + (Number(v) || 0); }, 0);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
_fns.avg = function (input, _eval, opts) {
|
|
77
|
+
var s = _fns.sum(input, _eval, opts);
|
|
78
|
+
var n = Array.isArray(input) ? input.length : 1;
|
|
79
|
+
return n ? s / n : 0;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
_fns.min = function (input, _eval, opts) {
|
|
83
|
+
var a = Array.isArray(input) ? input : [];
|
|
84
|
+
var vals = opts.field ? a.map(function (r) { return Number(r[opts.field]); }) : a.map(Number);
|
|
85
|
+
return vals.length ? Math.min.apply(null, vals) : 0;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
_fns.max = function (input, _eval, opts) {
|
|
89
|
+
var a = Array.isArray(input) ? input : [];
|
|
90
|
+
var vals = opts.field ? a.map(function (r) { return Number(r[opts.field]); }) : a.map(Number);
|
|
91
|
+
return vals.length ? Math.max.apply(null, vals) : 0;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
_fns.count = function (input) {
|
|
95
|
+
return Array.isArray(input) ? input.length : (input != null ? 1 : 0);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
_fns.first = function (input) {
|
|
99
|
+
return Array.isArray(input) ? input[0] : input;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
_fns.last = function (input) {
|
|
103
|
+
return Array.isArray(input) ? input[input.length - 1] : input;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// ---- Math ----
|
|
107
|
+
|
|
108
|
+
_fns.add = function (input) {
|
|
109
|
+
var a = Array.isArray(input) ? input : [];
|
|
110
|
+
return a.reduce(function (s, v) { return s + Number(v); }, 0);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
_fns.sub = function (input) {
|
|
114
|
+
var a = Array.isArray(input) ? input : [];
|
|
115
|
+
return a.length >= 2 ? Number(a[0]) - Number(a[1]) : 0;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
_fns.mul = function (input) {
|
|
119
|
+
var a = Array.isArray(input) ? input : [];
|
|
120
|
+
return a.reduce(function (s, v) { return s * Number(v); }, 1);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
_fns.div = function (input) {
|
|
124
|
+
var a = Array.isArray(input) ? input : [];
|
|
125
|
+
return a.length >= 2 && Number(a[1]) !== 0 ? Number(a[0]) / Number(a[1]) : 0;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
_fns.round = function (input, _eval, opts) {
|
|
129
|
+
var decimals = opts.decimals != null ? opts.decimals : 0;
|
|
130
|
+
var factor = Math.pow(10, decimals);
|
|
131
|
+
return Math.round(Number(input) * factor) / factor;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
_fns.abs = function (input) { return Math.abs(Number(input)); };
|
|
135
|
+
|
|
136
|
+
_fns.mod = function (input) {
|
|
137
|
+
var a = Array.isArray(input) ? input : [];
|
|
138
|
+
return a.length >= 2 ? Number(a[0]) % Number(a[1]) : 0;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ---- Compare ----
|
|
142
|
+
|
|
143
|
+
_fns.gt = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && Number(a[0]) > Number(a[1]); };
|
|
144
|
+
_fns.gte = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && Number(a[0]) >= Number(a[1]); };
|
|
145
|
+
_fns.lt = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && Number(a[0]) < Number(a[1]); };
|
|
146
|
+
_fns.lte = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && Number(a[0]) <= Number(a[1]); };
|
|
147
|
+
_fns.eq = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && a[0] === a[1]; };
|
|
148
|
+
_fns.neq = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && a[0] !== a[1]; };
|
|
149
|
+
|
|
150
|
+
// ---- Logic ----
|
|
151
|
+
|
|
152
|
+
_fns.and = function (input) { var a = Array.isArray(input) ? input : []; return a.every(Boolean); };
|
|
153
|
+
_fns.or = function (input) { var a = Array.isArray(input) ? input : []; return a.some(Boolean); };
|
|
154
|
+
_fns.not = function (input) { return !input; };
|
|
155
|
+
// "if" is handled specially in evalExpr
|
|
156
|
+
|
|
157
|
+
// ---- String ----
|
|
158
|
+
|
|
159
|
+
_fns.concat = function (input) {
|
|
160
|
+
var a = Array.isArray(input) ? input : [];
|
|
161
|
+
return a.map(function (v) { return v != null ? String(v) : ''; }).join('');
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
_fns.upper = function (input) { return String(input || '').toUpperCase(); };
|
|
165
|
+
_fns.lower = function (input) { return String(input || '').toLowerCase(); };
|
|
166
|
+
|
|
167
|
+
_fns.template = function (input, _eval, opts) {
|
|
168
|
+
var t = String(opts.format || '');
|
|
169
|
+
if (input && typeof input === 'object') {
|
|
170
|
+
Object.keys(input).forEach(function (k) {
|
|
171
|
+
t = t.split('{{' + k + '}}').join(input[k] != null ? String(input[k]) : '');
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return t;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
_fns.join = function (input, _eval, opts) {
|
|
178
|
+
var a = Array.isArray(input) ? input : [];
|
|
179
|
+
var sep = opts.separator != null ? opts.separator : ', ';
|
|
180
|
+
return a.map(function (v) { return v != null ? String(v) : ''; }).join(sep);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
_fns.split = function (input, _eval, opts) {
|
|
184
|
+
var sep = opts.separator != null ? opts.separator : ',';
|
|
185
|
+
return String(input || '').split(sep).map(function (s) { return s.trim(); });
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
_fns.trim = function (input) { return String(input || '').trim(); };
|
|
189
|
+
|
|
190
|
+
// ---- Collection ----
|
|
191
|
+
|
|
192
|
+
_fns.pluck = function (input, _eval, opts) {
|
|
193
|
+
return Array.isArray(input) ? input.map(function (r) { return r[opts.field]; }) : [];
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
_fns.filter = function (input, evalFn, opts) {
|
|
197
|
+
// Handled specially in evalExpr for the where clause; fallback for simple truthy filter
|
|
198
|
+
if (!Array.isArray(input)) return [];
|
|
199
|
+
if (opts.field) return input.filter(function (r) { return !!r[opts.field]; });
|
|
200
|
+
return input.filter(Boolean);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
_fns.map = function (input) {
|
|
204
|
+
// Handled specially in evalExpr for the apply clause
|
|
205
|
+
return Array.isArray(input) ? input.slice() : [];
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
_fns.sort = function (input, _eval, opts) {
|
|
209
|
+
var a = Array.isArray(input) ? input.slice() : [];
|
|
210
|
+
var f = opts.field;
|
|
211
|
+
var dir = opts.direction === 'desc' ? -1 : 1;
|
|
212
|
+
if (f) return a.sort(function (x, y) { return x[f] > y[f] ? dir : x[f] < y[f] ? -dir : 0; });
|
|
213
|
+
return a.sort(function (x, y) { return x > y ? dir : x < y ? -dir : 0; });
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
_fns.slice = function (input, _eval, opts) {
|
|
217
|
+
return Array.isArray(input) ? input.slice(opts.start || 0, opts.end) : input;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
_fns.flat = function (input, _eval, opts) {
|
|
221
|
+
var depth = opts.depth != null ? opts.depth : 1;
|
|
222
|
+
return Array.isArray(input) ? input.flat(depth) : [input];
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
_fns.unique = function (input) {
|
|
226
|
+
if (!Array.isArray(input)) return [input];
|
|
227
|
+
// For primitives, use Set. For objects, use JSON comparison.
|
|
228
|
+
var seen = new Set();
|
|
229
|
+
return input.filter(function (v) {
|
|
230
|
+
var key = typeof v === 'object' ? JSON.stringify(v) : v;
|
|
231
|
+
if (seen.has(key)) return false;
|
|
232
|
+
seen.add(key);
|
|
233
|
+
return true;
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
_fns.group = function (input, _eval, opts) {
|
|
238
|
+
var a = Array.isArray(input) ? input : [];
|
|
239
|
+
var g = {};
|
|
240
|
+
a.forEach(function (r) {
|
|
241
|
+
var k = String(r[opts.field] || '');
|
|
242
|
+
if (!g[k]) g[k] = [];
|
|
243
|
+
g[k].push(r);
|
|
244
|
+
});
|
|
245
|
+
return g;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
_fns.flatten_keys = function (input) {
|
|
249
|
+
// { a: [1,2], b: [3] } → [{ key: "a", value: 1 }, { key: "a", value: 2 }, { key: "b", value: 3 }]
|
|
250
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) return [];
|
|
251
|
+
var result = [];
|
|
252
|
+
Object.keys(input).forEach(function (k) {
|
|
253
|
+
var vals = Array.isArray(input[k]) ? input[k] : [input[k]];
|
|
254
|
+
vals.forEach(function (v) { result.push({ key: k, value: v }); });
|
|
255
|
+
});
|
|
256
|
+
return result;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
_fns.entries = function (input) {
|
|
260
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) return [];
|
|
261
|
+
return Object.keys(input).map(function (k) { return { key: k, value: input[k] }; });
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
_fns.from_entries = function (input) {
|
|
265
|
+
if (!Array.isArray(input)) return {};
|
|
266
|
+
var obj = {};
|
|
267
|
+
input.forEach(function (item) { if (item.key != null) obj[item.key] = item.value; });
|
|
268
|
+
return obj;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
_fns.length = function (input) {
|
|
272
|
+
if (Array.isArray(input)) return input.length;
|
|
273
|
+
if (typeof input === 'string') return input.length;
|
|
274
|
+
if (input && typeof input === 'object') return Object.keys(input).length;
|
|
275
|
+
return 0;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// ---- Lookup ----
|
|
279
|
+
|
|
280
|
+
_fns.get = function (input, _eval, opts) {
|
|
281
|
+
return _deepGet(input, opts.field || opts.path || '');
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
_fns.default = function (input, _eval, opts) {
|
|
285
|
+
return input != null ? input : opts.value;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
_fns.coalesce = function (input) {
|
|
289
|
+
var a = Array.isArray(input) ? input : [];
|
|
290
|
+
for (var i = 0; i < a.length; i++) { if (a[i] != null) return a[i]; }
|
|
291
|
+
return null;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// ---- Date ----
|
|
295
|
+
|
|
296
|
+
_fns.now = function () { return new Date().toISOString(); };
|
|
297
|
+
|
|
298
|
+
_fns.diff_days = function (input) {
|
|
299
|
+
var a = Array.isArray(input) ? input : [];
|
|
300
|
+
return a.length >= 2 ? Math.floor((new Date(a[0]) - new Date(a[1])) / 86400000) : 0;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
_fns.format_date = function (input, _eval, opts) {
|
|
304
|
+
try {
|
|
305
|
+
var d = new Date(input);
|
|
306
|
+
if (opts.format === 'iso') return d.toISOString();
|
|
307
|
+
if (opts.format === 'date') return d.toLocaleDateString();
|
|
308
|
+
if (opts.format === 'time') return d.toLocaleTimeString();
|
|
309
|
+
return d.toLocaleDateString();
|
|
310
|
+
} catch (e) {
|
|
311
|
+
return String(input);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
_fns.parse_date = function (input) {
|
|
316
|
+
try { return new Date(input).toISOString(); } catch (e) { return null; }
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// ---- Type ----
|
|
320
|
+
|
|
321
|
+
_fns.to_number = function (input) { return Number(input) || 0; };
|
|
322
|
+
_fns.to_string = function (input) { return input != null ? String(input) : ''; };
|
|
323
|
+
_fns.to_bool = function (input) { return !!input; };
|
|
324
|
+
_fns.type_of = function (input) { return Array.isArray(input) ? 'array' : typeof input; };
|
|
325
|
+
_fns.is_null = function (input) { return input == null; };
|
|
326
|
+
_fns.is_empty = function (input) {
|
|
327
|
+
if (input == null) return true;
|
|
328
|
+
if (Array.isArray(input)) return input.length === 0;
|
|
329
|
+
if (typeof input === 'string') return input.length === 0;
|
|
330
|
+
if (typeof input === 'object') return Object.keys(input).length === 0;
|
|
331
|
+
return false;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// ===========================================================================
|
|
335
|
+
// Expression evaluator
|
|
336
|
+
// ===========================================================================
|
|
337
|
+
|
|
338
|
+
var _customFns = {};
|
|
339
|
+
|
|
340
|
+
function evalExpr(expr, node) {
|
|
341
|
+
if (expr == null) return expr;
|
|
342
|
+
|
|
343
|
+
// Literal values pass through
|
|
344
|
+
if (typeof expr !== 'object' || Array.isArray(expr)) return expr;
|
|
345
|
+
|
|
346
|
+
// Must have fn to be an expression
|
|
347
|
+
if (!expr.fn) return expr;
|
|
348
|
+
|
|
349
|
+
// Resolve input
|
|
350
|
+
var input = expr.input;
|
|
351
|
+
if (typeof input === 'string' && input.startsWith('state.')) {
|
|
352
|
+
input = resolve(node, input);
|
|
353
|
+
} else if (Array.isArray(input)) {
|
|
354
|
+
input = input.map(function (v) {
|
|
355
|
+
if (typeof v === 'string' && v.startsWith('state.')) return resolve(node, v);
|
|
356
|
+
if (v && typeof v === 'object' && v.fn) return evalExpr(v, node);
|
|
357
|
+
return v;
|
|
358
|
+
});
|
|
359
|
+
} else if (input && typeof input === 'object' && input.fn) {
|
|
360
|
+
input = evalExpr(input, node);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Special: if
|
|
364
|
+
if (expr.fn === 'if') {
|
|
365
|
+
var cond = evalExpr(expr.cond, node);
|
|
366
|
+
if (cond) {
|
|
367
|
+
return (expr.then && typeof expr.then === 'object' && expr.then.fn) ? evalExpr(expr.then, node) : expr.then;
|
|
368
|
+
} else {
|
|
369
|
+
return (expr.else && typeof expr.else === 'object' && expr.else.fn) ? evalExpr(expr.else, node) : expr.else;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Special: filter with where clause
|
|
374
|
+
if (expr.fn === 'filter' && Array.isArray(input) && expr.where) {
|
|
375
|
+
return input.filter(function (item) {
|
|
376
|
+
var tmp = { state: Object.assign({}, node.state, { $: item }) };
|
|
377
|
+
return evalExpr(expr.where, tmp);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Special: map with apply clause
|
|
382
|
+
if (expr.fn === 'map' && Array.isArray(input) && expr.apply) {
|
|
383
|
+
return input.map(function (item) {
|
|
384
|
+
var tmp = { state: Object.assign({}, node.state, { $: item }) };
|
|
385
|
+
return evalExpr(expr.apply, tmp);
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Look up function
|
|
390
|
+
var fn = _customFns[expr.fn] || _fns[expr.fn];
|
|
391
|
+
if (!fn) {
|
|
392
|
+
console.warn('CardCompute: unknown function "' + expr.fn + '"');
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return fn(input, evalExpr, expr);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ===========================================================================
|
|
400
|
+
// run — evaluate all compute declarations on a node
|
|
401
|
+
// ===========================================================================
|
|
402
|
+
|
|
403
|
+
function run(node) {
|
|
404
|
+
if (!node || !node.compute) return node;
|
|
405
|
+
if (!node.state) node.state = {};
|
|
406
|
+
|
|
407
|
+
var keys = Object.keys(node.compute);
|
|
408
|
+
for (var i = 0; i < keys.length; i++) {
|
|
409
|
+
var key = keys[i];
|
|
410
|
+
try {
|
|
411
|
+
var val = evalExpr(node.compute[key], node);
|
|
412
|
+
_deepSet(node.state, key, val);
|
|
413
|
+
} catch (e) {
|
|
414
|
+
console.error('CardCompute.run error on "' + (node.id || '?') + '.' + key + '":', e);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return node;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ===========================================================================
|
|
422
|
+
// registerFunction — extend the vocabulary
|
|
423
|
+
// ===========================================================================
|
|
424
|
+
|
|
425
|
+
function registerFunction(name, fn) {
|
|
426
|
+
_customFns[name] = fn;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ===========================================================================
|
|
430
|
+
// Export
|
|
431
|
+
// ===========================================================================
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
run: run,
|
|
435
|
+
eval: evalExpr,
|
|
436
|
+
resolve: resolve,
|
|
437
|
+
registerFunction: registerFunction,
|
|
438
|
+
get functions() {
|
|
439
|
+
var all = {};
|
|
440
|
+
Object.keys(_fns).forEach(function (k) { all[k] = _fns[k]; });
|
|
441
|
+
Object.keys(_customFns).forEach(function (k) { all[k] = _customFns[k]; });
|
|
442
|
+
return all;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
}));
|