@cloudpss/expression 0.6.0-alpha.10 → 0.6.0-alpha.11

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.
@@ -10,7 +10,7 @@ import {
10
10
  } from 'mathjs';
11
11
  import type { Options, Result } from './interface.js';
12
12
  import type { State } from './state.js';
13
- import { constantValue, equalText, len, symbolName } from './utils.js';
13
+ import { constantValue, equalText, globalFnName, len, symbolName } from './utils.js';
14
14
  import { migrateAtomic, migrateExpr, migrateNode } from './node.js';
15
15
  import { concat } from './concat.js';
16
16
  import { toNumber, toString } from './to-type.js';
@@ -63,6 +63,7 @@ function migrateFunctionCall(
63
63
  ): Result {
64
64
  const openE = options.format !== 'no-paren' ? '(' : '';
65
65
  const closeE = options.format !== 'no-paren' ? ')' : '';
66
+ const g = (fnName: string) => globalFnName(state, fnName);
66
67
 
67
68
  if (EXACT_UNARY_FUNCTION.has(fnName) && args.length === 1) {
68
69
  const arg = migrateAtomic(state, args[0]!);
@@ -80,19 +81,19 @@ function migrateFunctionCall(
80
81
  } else if (fnName === 'date' && args.length === 1) {
81
82
  return {
82
83
  type: 'number',
83
- code: `to_timestamp(${migrateAtomic(state, args[0]!).code})`,
84
+ code: `${g('to_timestamp')}(${migrateAtomic(state, args[0]!).code})`,
84
85
  };
85
86
  } else if (fnName === 'size' && args.length === 1) {
86
87
  const arg = migrateAtomic(state, args[0]!);
87
88
  return {
88
89
  type: arg.type,
89
- code: `matrix.size(${arg.code})`,
90
+ code: `${g('matrix')}.size(${arg.code})`,
90
91
  };
91
92
  } else if (fnName === 'inv' && args.length === 1) {
92
93
  const arg = migrateAtomic(state, args[0]!);
93
94
  return {
94
95
  type: arg.type,
95
- code: `matrix.invert(${arg.code})`,
96
+ code: `${g('matrix')}.invert(${arg.code})`,
96
97
  };
97
98
  } else if (fnName === 'count' && args.length === 1) {
98
99
  const arg = migrateAtomic(state, args[0]!);
@@ -104,39 +105,46 @@ function migrateFunctionCall(
104
105
  const arg = migrateAtomic(state, args[0]!);
105
106
  return {
106
107
  type: arg.type,
107
- code: `matrix.transpose(${arg.code})`,
108
+ code: `${g('matrix')}.transpose(${arg.code})`,
108
109
  };
109
110
  } else if (fnName === 'diag' && (args.length === 1 || args.length === 2)) {
110
111
  const a = args.map((a) => migrateAtomic(state, a).code).join(', ');
111
112
  return {
112
113
  type: 'array',
113
- code: `matrix.diagonal(${a})`,
114
+ code: `${g('matrix')}.diagonal(${a})`,
114
115
  };
115
116
  } else if (fnName === 'zeros' || fnName === 'ones' || fnName === 'identity') {
116
117
  return {
117
118
  type: 'array',
118
- code: `matrix.${fnName}(${args.map((a) => migrateAtomic(state, a).code).join(', ')})`,
119
+ code: `${g('matrix')}.${fnName}(${args.map((a) => migrateAtomic(state, a).code).join(', ')})`,
119
120
  };
120
121
  } else if (fnName === 'concat') {
121
122
  const results = args.map((a) => migrateAtomic(state, a));
122
123
  if (results.some((r) => r.type === 'string')) return concat(state, results);
124
+ if (results.some((r) => r.type === 'array')) {
125
+ state.warn(`矩阵连接时结果可能不一致`);
126
+ return {
127
+ type: 'array',
128
+ code: `${g('flatten')}([` + results.map((r) => r.code).join(', ') + `])`,
129
+ };
130
+ }
123
131
  } else if (fnName === 'numeric' && args.length === 1) {
124
132
  return toNumber(state, args[0]!);
125
133
  } else if (fnName === 'flatten' && args.length === 1) {
126
134
  return {
127
135
  type: 'array',
128
- code: `flatten(${migrateAtomic(state, args[0]!).code})`,
136
+ code: `${g('flatten')}(${migrateAtomic(state, args[0]!).code})`,
129
137
  };
130
138
  } else if (fnName === 'norm' && args.length === 1) {
131
139
  if (isArrayNode(args[0])) {
132
140
  return {
133
141
  type: 'number',
134
- code: `hypot(${args[0].items.map((a) => migrateAtomic(state, a).code).join(', ')})`,
142
+ code: `${g('hypot')}(${args[0].items.map((a) => migrateAtomic(state, a).code).join(', ')})`,
135
143
  };
136
144
  }
137
145
  return {
138
146
  type: 'number',
139
- code: `hypot(${migrateAtomic(state, args[0]!).code})`,
147
+ code: `${g('hypot')}(${migrateAtomic(state, args[0]!).code})`,
140
148
  };
141
149
  } else if (fnName === 'equalText' && args.length === 2) {
142
150
  const p = equalText(state, '==', args[0]!, args[1]!);
@@ -147,11 +155,11 @@ function migrateFunctionCall(
147
155
  } else if (fnName === 'toJson' && args.length === 1) {
148
156
  return {
149
157
  type: 'string',
150
- code: `to_json(${migrateAtomic(state, args[0]!).code})`,
158
+ code: `${g('to_json')}(${migrateAtomic(state, args[0]!).code})`,
151
159
  };
152
160
  } else if (fnName === 'fromJson' && args.length === 1) {
153
161
  return {
154
- code: `from_json(${migrateAtomic(state, args[0]!).code})`,
162
+ code: `${g('from_json')}(${migrateAtomic(state, args[0]!).code})`,
155
163
  };
156
164
  } else if (fnName === 'is' && args.length === 2 && isConstantNode(args[1])) {
157
165
  const t = String((args[1] as ConstantNode<string | number>).value);
@@ -254,7 +262,7 @@ function migrateFunctionCall(
254
262
  const inner = migrateAtomic(state, args[0]!);
255
263
  return {
256
264
  type: 'string',
257
- code: `to_string(${inner.code})`,
265
+ code: `${g('to_string')}(${inner.code})`,
258
266
  };
259
267
  } else if (
260
268
  fnName === 'sum' &&
@@ -270,7 +278,7 @@ function migrateFunctionCall(
270
278
  const v = migrateAtomic(state, args[0].items[0].args[1]!);
271
279
  return {
272
280
  type: 'number',
273
- code: `sum(map(${a.code}, fn { it ${args[0].items[0].op} ${v.code} }))`,
281
+ code: `${g('sum')}(${g('map')}(${a.code}, fn { it ${args[0].items[0].op} ${v.code} }))`,
274
282
  };
275
283
  }
276
284
 
@@ -303,10 +311,11 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
303
311
  ) {
304
312
  const thisArg = migrateExpr(state, fn.object);
305
313
  const fnName = String(fn.index.dimensions[0].value);
314
+ const g = (fnName: string) => globalFnName(state, fnName);
306
315
  if (fnName === 'toString' && args.length === 0) {
307
316
  return {
308
317
  type: 'string',
309
- code: `${thisArg.code}::to_string()`,
318
+ code: `${thisArg.code}::${g('to_string')}()`,
310
319
  };
311
320
  } else if (fnName === 'includes' && args.length === 1) {
312
321
  if (thisArg.type === 'array') {
@@ -318,7 +327,7 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
318
327
  if (thisArg.type === 'string') {
319
328
  return {
320
329
  type: 'boolean',
321
- code: `${thisArg.code}::contains(${migrateAtomic(state, args[0]!).code})`,
330
+ code: `${thisArg.code}::${g('contains')}(${migrateAtomic(state, args[0]!).code})`,
322
331
  };
323
332
  }
324
333
  state.helper(
@@ -331,26 +340,26 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
331
340
  } else if ((fnName === 'map' || fnName === 'filter') && args.length === 1) {
332
341
  return {
333
342
  type: 'array',
334
- code: `${thisArg.code}::${fnName}(${migrateAtomic(state, args[0]!).code})`,
343
+ code: `${thisArg.code}::${g(fnName)}(${migrateAtomic(state, args[0]!).code})`,
335
344
  };
336
345
  } else if (fnName === 'find' && args.length === 1) {
337
346
  return {
338
- code: `${thisArg.code}::find(${migrateAtomic(state, args[0]!).code}).1`,
347
+ code: `${thisArg.code}::${g('find')}(${migrateAtomic(state, args[0]!).code}).1`,
339
348
  };
340
349
  } else if (fnName === 'findIndex' && args.length === 1) {
341
350
  return {
342
351
  type: 'number',
343
- code: `(${thisArg.code}::find(${migrateAtomic(state, args[0]!).code}).0 ?? -1)`,
352
+ code: `(${thisArg.code}::${g('find')}(${migrateAtomic(state, args[0]!).code}).0 ?? -1)`,
344
353
  };
345
354
  } else if (fnName === 'flatMap' && args.length === 1) {
346
355
  return {
347
356
  type: 'array',
348
- code: `${thisArg.code}::map(${migrateAtomic(state, args[0]!).code})::flatten()`,
357
+ code: `${thisArg.code}::${g('map')}(${migrateAtomic(state, args[0]!).code})::flatten()`,
349
358
  };
350
359
  } else if (fnName === 'reverse' && args.length === 0) {
351
360
  return {
352
361
  type: 'array',
353
- code: `${thisArg.code}::reverse()`,
362
+ code: `${thisArg.code}::${g('reverse')}()`,
354
363
  };
355
364
  } else if ((fnName === 'replaceAll' || fnName === 'replace') && args.length === 2) {
356
365
  if (fnName === 'replace') {
@@ -358,12 +367,12 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
358
367
  }
359
368
  return {
360
369
  type: 'string',
361
- code: `${thisArg.code}::replace(${migrateAtomic(state, args[0]!).code}, ${migrateAtomic(state, args[1]!).code})`,
370
+ code: `${thisArg.code}::${g('replace')}(${migrateAtomic(state, args[0]!).code}, ${migrateAtomic(state, args[1]!).code})`,
362
371
  };
363
372
  } else if (fnName === 'split' && args.length === 1) {
364
373
  return {
365
374
  type: 'array',
366
- code: `${thisArg.code}::split(${migrateAtomic(state, args[0]!).code})`,
375
+ code: `${thisArg.code}::${g('split')}(${migrateAtomic(state, args[0]!).code})`,
367
376
  };
368
377
  } else if (fnName === 'concat') {
369
378
  if (thisArg.type === 'string') {
@@ -375,7 +384,10 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
375
384
  }
376
385
  return {
377
386
  type: 'array',
378
- code: `flatten([` + [fn.object, ...args].map((a) => migrateAtomic(state, a).code).join(', ') + `])`,
387
+ code:
388
+ `${g('flatten')}([` +
389
+ [fn.object, ...args].map((a) => migrateAtomic(state, a).code).join(', ') +
390
+ `])`,
379
391
  };
380
392
  } else if (
381
393
  fnName === 'toFixed' &&
@@ -384,7 +396,7 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
384
396
  const digits = constantValue(args[0]!) ?? 0;
385
397
  return {
386
398
  type: 'string',
387
- code: `${thisArg.code}::format('.${digits}')`,
399
+ code: `${thisArg.code}::${g('format')}('.${digits}')`,
388
400
  };
389
401
  } else if (isArrayNode(fn.object) && fnName === 'join' && args.length <= 1) {
390
402
  let sep = ',';
@@ -3,18 +3,31 @@ import type { State } from './state.js';
3
3
  import { migrateNode } from './node.js';
4
4
  import { isResult, type Result } from './interface.js';
5
5
 
6
+ /** 生成转义序列 */
7
+ function escapeString(str: string | number): string {
8
+ return String(str)
9
+ .replaceAll('\\', String.raw`\\`)
10
+ .replaceAll('`', '\\`')
11
+ .replaceAll('$', String.raw`\$`)
12
+ .replaceAll('\n', String.raw`\n`)
13
+ .replaceAll('\r', String.raw`\r`)
14
+ .replaceAll('\t', String.raw`\t`);
15
+ }
16
+
6
17
  /** 连接字符串 */
7
18
  export function concat(state: State, args: ReadonlyArray<MathNode | string | number | Result>): Result {
8
19
  const parts: string[] = [];
9
20
  for (const arg of args) {
10
21
  if (typeof arg != 'object') {
11
- parts.push(String(arg).replaceAll('`', '\\`'));
22
+ parts.push(escapeString(arg));
12
23
  } else if (isConstantNode(arg)) {
13
- const value = String(arg.value).replaceAll('`', '\\`');
24
+ const value = escapeString(String(arg.value));
14
25
  parts.push(value);
15
26
  } else if (isResult(arg)) {
16
27
  if (arg.literal && typeof arg.literal != 'object') {
17
28
  parts.push(String(arg.literal).replaceAll('`', '\\`'));
29
+ } else if (arg.type === 'string' && arg.code.startsWith('to_string(') && arg.code.endsWith(')')) {
30
+ parts.push(`$(${arg.code.slice(10, -1)})`);
18
31
  } else {
19
32
  parts.push(`$(${arg.code})`);
20
33
  }
@@ -8,7 +8,7 @@ import {
8
8
  isOperatorNode,
9
9
  isNode,
10
10
  } from 'mathjs';
11
- import { symbolName, constantValue, equalText, scalar } from './utils.js';
11
+ import { symbolName, constantValue, equalText, scalar, globalFnName } from './utils.js';
12
12
  import type { State } from './state.js';
13
13
  import type { Options, Result } from './interface.js';
14
14
  import { migrateAtomic, migrateExpr } from './node.js';
@@ -19,58 +19,58 @@ import { operations, serialize } from '@mirascript/mirascript/subtle';
19
19
  const BINARY_MATH_OPERATORS = {
20
20
  add: [
21
21
  ' + ',
22
- (l, r) => ({
22
+ (state, l, r) => ({
23
23
  type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
24
- code: `matrix.add(${l.code}, ${r.code})`,
24
+ code: `${globalFnName(state, 'matrix')}.add(${l.code}, ${r.code})`,
25
25
  }),
26
26
  ],
27
27
  subtract: [
28
28
  ' - ',
29
- (l, r) => ({
29
+ (state, l, r) => ({
30
30
  type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
31
- code: `matrix.subtract(${l.code}, ${r.code})`,
31
+ code: `${globalFnName(state, 'matrix')}.subtract(${l.code}, ${r.code})`,
32
32
  }),
33
33
  ],
34
34
  multiply: [
35
35
  ' * ',
36
- (l, r) => ({
36
+ (state, l, r) => ({
37
37
  type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
38
- code: `matrix.multiply(${l.code}, ${r.code})`,
38
+ code: `${globalFnName(state, 'matrix')}.multiply(${l.code}, ${r.code})`,
39
39
  }),
40
40
  ],
41
41
  dotMultiply: [
42
42
  ' * ',
43
- (l, r) => ({
43
+ (state, l, r) => ({
44
44
  type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
45
- code: `matrix.entrywise_multiply(${l.code}, ${r.code})`,
45
+ code: `${globalFnName(state, 'matrix')}.entrywise_multiply(${l.code}, ${r.code})`,
46
46
  }),
47
47
  ],
48
48
  divide: [
49
49
  ' / ',
50
- (l, r) => {
50
+ (state, l, r) => {
51
51
  if (r.type === 'array' || !r.type) {
52
52
  return {
53
53
  type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
54
- code: `matrix.multiply(${l.code}, matrix.invert(${r.code}))`,
54
+ code: `${globalFnName(state, 'matrix')}.multiply(${l.code}, ${globalFnName(state, 'matrix')}.invert(${r.code}))`,
55
55
  };
56
56
  }
57
57
  return {
58
58
  type: l.type === 'array' ? 'array' : l.type ? 'number' : undefined,
59
- code: `matrix.entrywise_divide(${l.code}, ${r.code})`,
59
+ code: `${globalFnName(state, 'matrix')}.entrywise_divide(${l.code}, ${r.code})`,
60
60
  };
61
61
  },
62
62
  ],
63
63
  dotDivide: [
64
64
  ' / ',
65
- (l, r) => ({
65
+ (state, l, r) => ({
66
66
  type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
67
- code: `matrix.entrywise_divide(${l.code}, ${r.code})`,
67
+ code: `${globalFnName(state, 'matrix')}.entrywise_divide(${l.code}, ${r.code})`,
68
68
  }),
69
69
  ],
70
70
  mod: [' % '],
71
71
  pow: ['^', false],
72
72
  dotPow: ['^'],
73
- } satisfies Record<string, [op: string, alt?: ((l: Result, r: Result) => Result) | false]>;
73
+ } satisfies Record<string, [op: string, alt?: ((state: State, l: Result, r: Result) => Result) | false]>;
74
74
 
75
75
  const MATH_FUNCTIONS = {
76
76
  factorial: 'factorial',
@@ -107,47 +107,47 @@ function binary(
107
107
  state: State,
108
108
  l: Result,
109
109
  r: Result,
110
- op: (l: Result, r: Result) => Result,
111
- alt?: (l: Result, r: Result) => Result,
110
+ op: (state: State, l: Result, r: Result) => Result,
111
+ alt?: (state: State, l: Result, r: Result) => Result,
112
112
  ): Result {
113
113
  if (l.type && r.type && l.type !== 'array' && r.type !== 'array') {
114
- return op(l, r);
114
+ return op(state, l, r);
115
115
  }
116
116
  if (Array.isArray(l.literal) && l.literal.every((e) => !Array.isArray(e))) {
117
117
  return {
118
118
  type: 'array',
119
- code: `${l.code}::map(fn { ${op({ code: 'it' }, r).code} })`,
119
+ code: `${l.code}::${globalFnName(state, 'map')}(fn { ${op(state, { code: 'it' }, r).code} })`,
120
120
  };
121
121
  }
122
122
  if (Array.isArray(r.literal) && r.literal.every((e) => !Array.isArray(e))) {
123
123
  return {
124
124
  type: 'array',
125
- code: `${r.code}::map(fn { ${op(l, { code: 'it' }).code} })`,
125
+ code: `${r.code}::${globalFnName(state, 'map')}(fn { ${op(state, l, { code: 'it' }).code} })`,
126
126
  };
127
127
  }
128
128
  if (alt) {
129
- return alt(l, r);
129
+ return alt(state, l, r);
130
130
  }
131
131
  return {
132
132
  type: l.type === 'array' || r.type === 'array' ? 'array' : undefined,
133
- code: `matrix.entrywise(${l.code}, ${r.code}, fn (a, b) { ${op({ code: 'a' }, { code: 'b' }).code} })`,
133
+ code: `${globalFnName(state, 'matrix')}.entrywise(${l.code}, ${r.code}, fn (a, b) { ${op(state, { code: 'a' }, { code: 'b' }).code} })`,
134
134
  };
135
135
  }
136
136
 
137
137
  /** 一元操作 */
138
- function unary(state: State, v: Result, op: (v: Result) => Result): Result {
138
+ function unary(state: State, v: Result, op: (state: State, v: Result) => Result): Result {
139
139
  if (v.type && v.type !== 'array') {
140
- return op(v);
140
+ return op(state, v);
141
141
  }
142
142
  if (Array.isArray(v.literal) && v.literal.every((e) => !Array.isArray(e))) {
143
143
  return {
144
144
  type: 'array',
145
- code: `${v.code}::map(fn { ${op({ code: 'it' }).code} })`,
145
+ code: `${v.code}::${globalFnName(state, 'map')}(fn { ${op(state, { code: 'it' }).code} })`,
146
146
  };
147
147
  }
148
148
  return {
149
149
  type: 'array',
150
- code: `matrix.entrywise(${v.code}, nil, fn { ${op({ code: 'it' }).code} })`,
150
+ code: `${globalFnName(state, 'matrix')}.entrywise(${v.code}, nil, fn { ${op(state, { code: 'it' }).code} })`,
151
151
  };
152
152
  }
153
153
 
@@ -179,14 +179,14 @@ export function migrateOperator(
179
179
  state,
180
180
  migrateExpr(state, a0),
181
181
  migrateExpr(state, a1),
182
- (l, r) => ({
182
+ (state, l, r) => ({
183
183
  type: 'number',
184
184
  code: `${l.code}${op}${r.code}`,
185
185
  }),
186
186
  alt === false
187
- ? (l, r) => {
187
+ ? (state, l, r) => {
188
188
  state.warn(`'${op.trim()}' 不支持矩阵,计算结果可能不一致`);
189
- return { code: `${l.code} ^ ${r.code}` };
189
+ return { code: `${l.code}${op}${r.code}` };
190
190
  }
191
191
  : alt,
192
192
  );
@@ -205,7 +205,7 @@ export function migrateOperator(
205
205
  code: serialize(v),
206
206
  };
207
207
  }
208
- return unary(state, migrateExpr(state, a0), (v) => ({
208
+ return unary(state, migrateExpr(state, a0), (state, v) => ({
209
209
  type: 'number',
210
210
  code: `${op}${v.code}`,
211
211
  }));
@@ -315,7 +315,7 @@ export function migrateOperator(
315
315
  return r;
316
316
  };
317
317
 
318
- return binary(state, a(a0), a(a1), (l, r) => {
318
+ return binary(state, a(a0), a(a1), (state, l, r) => {
319
319
  if (l.type === 'boolean' && r.type === 'boolean') {
320
320
  return {
321
321
  type: 'boolean',
@@ -3,11 +3,14 @@ import type { Result } from './interface.js';
3
3
  import { serialize } from '@mirascript/mirascript/subtle';
4
4
  import type { State } from './state.js';
5
5
  import { migrateNode } from './node.js';
6
+ import { globalFnName } from './utils.js';
6
7
 
7
8
  /** 转换 AST */
8
9
  export function toBoolean(state: State, node: MathNode | Result): Result {
9
10
  const helper = (): void => {
10
- state.helper("fn @@to_boolean { it != '' && it != 0 && it is not nan && !!it }");
11
+ state.helper(
12
+ "fn @@to_boolean { if it is nil { return nil; } it != '' && it != '0' && it != 0 && it is not nan && !!it }",
13
+ );
11
14
  };
12
15
  if (!isNode(node)) {
13
16
  if (node.type === 'boolean') return node;
@@ -56,7 +59,7 @@ export function toString(state: State, node: MathNode | Result): Result {
56
59
  }
57
60
  return {
58
61
  type: 'string',
59
- code: `to_string(${node.code})`,
62
+ code: `${globalFnName(state, 'to_string')}(${node.code})`,
60
63
  };
61
64
  }
62
65
  if (isConstantNode(node)) {
@@ -92,7 +95,7 @@ export function toNumber(state: State, node: MathNode | Result): Result {
92
95
  }
93
96
  return {
94
97
  type: 'number',
95
- code: `to_number(${node.code})`,
98
+ code: `${globalFnName(state, 'to_number')}(${node.code})`,
96
99
  };
97
100
  }
98
101
  if (isConstantNode(node)) {
@@ -4,6 +4,14 @@ import type { State } from './state.js';
4
4
  import { toString } from './to-type.js';
5
5
  import { migrateExpr } from './node.js';
6
6
 
7
+ /** 获取全局函数的名字 */
8
+ export function globalFnName(state: State, fnName: string): string {
9
+ if (state.locals.has(fnName)) {
10
+ return `global.${fnName}`;
11
+ }
12
+ return fnName;
13
+ }
14
+
7
15
  /** 获取 symbol name */
8
16
  export function symbolName(node: MathNode): string | undefined {
9
17
  if (!isSymbolNode(node)) return undefined;
@@ -79,7 +87,7 @@ export function len(state: State, obj: Result): Result {
79
87
  code: `len(${obj.code})`,
80
88
  };
81
89
  state.helper(
82
- `fn @@length(x) { if type(x) == 'string' { len(chars(x)) } else if type(x) == 'array' { len(x) } } else { x.length } }`,
90
+ `fn @@length(x) { if type(x) == 'string' { len(chars(x)) } else if type(x) == 'array' { len(x) } else { x.length } }`,
83
91
  );
84
92
  return {
85
93
  type: 'number',