@mirascript/mirascript 0.1.1 → 0.1.3

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.
Files changed (51) hide show
  1. package/dist/{chunk-2PWHYDNX.js → chunk-MVHCSH3E.js} +239 -122
  2. package/dist/chunk-MVHCSH3E.js.map +6 -0
  3. package/dist/cli/index.js +3 -3
  4. package/dist/compiler/diagnostic.d.ts +12 -3
  5. package/dist/compiler/diagnostic.d.ts.map +1 -1
  6. package/dist/helpers/utils.d.ts +1 -0
  7. package/dist/helpers/utils.d.ts.map +1 -1
  8. package/dist/index.js +9 -1
  9. package/dist/subtle.js +1 -1
  10. package/dist/vm/error.d.ts.map +1 -1
  11. package/dist/vm/lib/_helpers.d.ts.map +1 -1
  12. package/dist/vm/lib/global/sequence/find.d.ts.map +1 -1
  13. package/dist/vm/lib/global/sequence/with.d.ts +2 -2
  14. package/dist/vm/lib/global/sequence/with.d.ts.map +1 -1
  15. package/dist/vm/operations.d.ts.map +1 -1
  16. package/dist/vm/types/boundary.d.ts +12 -0
  17. package/dist/vm/types/boundary.d.ts.map +1 -0
  18. package/dist/vm/types/checker.d.ts +1 -1
  19. package/dist/vm/types/checker.d.ts.map +1 -1
  20. package/dist/vm/types/context.d.ts +14 -6
  21. package/dist/vm/types/context.d.ts.map +1 -1
  22. package/dist/vm/types/extern.d.ts +7 -7
  23. package/dist/vm/types/extern.d.ts.map +1 -1
  24. package/dist/vm/types/function.d.ts +0 -4
  25. package/dist/vm/types/function.d.ts.map +1 -1
  26. package/dist/vm/types/index.d.ts +9 -10
  27. package/dist/vm/types/index.d.ts.map +1 -1
  28. package/dist/vm/types/module.d.ts +2 -0
  29. package/dist/vm/types/module.d.ts.map +1 -1
  30. package/dist/vm/types/wrapper.d.ts +2 -0
  31. package/dist/vm/types/wrapper.d.ts.map +1 -1
  32. package/package.json +4 -4
  33. package/src/compiler/diagnostic.ts +14 -12
  34. package/src/helpers/utils.ts +1 -0
  35. package/src/vm/error.ts +15 -3
  36. package/src/vm/lib/_helpers.ts +4 -13
  37. package/src/vm/lib/_loader.ts +4 -4
  38. package/src/vm/lib/global/math.ts +1 -1
  39. package/src/vm/lib/global/sequence/find.ts +13 -11
  40. package/src/vm/lib/global/sequence/map-filter.ts +2 -2
  41. package/src/vm/lib/global/sequence/with.ts +111 -17
  42. package/src/vm/operations.ts +23 -24
  43. package/src/vm/types/boundary.ts +95 -0
  44. package/src/vm/types/checker.ts +8 -15
  45. package/src/vm/types/context.ts +31 -19
  46. package/src/vm/types/extern.ts +30 -57
  47. package/src/vm/types/function.ts +1 -37
  48. package/src/vm/types/index.ts +12 -24
  49. package/src/vm/types/module.ts +7 -0
  50. package/src/vm/types/wrapper.ts +7 -0
  51. package/dist/chunk-2PWHYDNX.js.map +0 -6
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/vm/types/module.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,oBAAoB;AACpB,qBAAa,QAAQ,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,CAAC;IAErG,WAAW;IACX,QAAQ,CAAC,IAAI,EAAE,MAAM;;IADrB,WAAW;IACF,IAAI,EAAE,MAAM;IACrB,WAAW;IACX,KAAK,EAAE,CAAC;IAIZ,kBAAkB;IACT,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAGlC,kBAAkB;IACT,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK;IAIhC,kBAAkB;IACT,IAAI,IAAI,MAAM,EAAE;IAGzB,kBAAkB;IACT,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;IAGpC,kBAAkB;IAClB,IAAa,IAAI,IAAI,QAAQ,CAE5B;IACD,kBAAkB;IAClB,IAAa,QAAQ,IAAI,MAAM,CAE9B;CACJ"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/vm/types/module.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,oBAAoB;AACpB,qBAAa,QAAQ,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,CAAC;IAErG,WAAW;IACX,QAAQ,CAAC,IAAI,EAAE,MAAM;;IADrB,WAAW;IACF,IAAI,EAAE,MAAM;IACrB,WAAW;IACX,KAAK,EAAE,CAAC;IAIZ,kBAAkB;IACT,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAGlC,kBAAkB;IACT,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK;IAIhC,kBAAkB;IACT,IAAI,IAAI,MAAM,EAAE;IAGzB,kBAAkB;IACT,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;IAGpC,kBAAkB;IAClB,IAAa,IAAI,IAAI,QAAQ,CAE5B;IACD,kBAAkB;IAClB,IAAa,QAAQ,IAAI,MAAM,CAE9B;CACJ;AAID,2BAA2B;AAC3B,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,CAElG"}
@@ -22,4 +22,6 @@ export declare abstract class VmWrapper<T extends object> {
22
22
  /** 转为字符串 */
23
23
  toString(): string;
24
24
  }
25
+ /** 检查值是否为 MiraScript 包装器 */
26
+ export declare function isVmWrapper<T extends object>(value: unknown): value is VmWrapper<T>;
25
27
  //# sourceMappingURL=wrapper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../../src/vm/types/wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,8BAAsB,SAAS,CAAC,CAAC,SAAS,MAAM;IAChC,QAAQ,CAAC,KAAK,EAAE,CAAC;gBAAR,KAAK,EAAE,CAAC;IAC7B,cAAc;IACd,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAClC,cAAc;IACd,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK;IAChC,WAAW;IACX,QAAQ,CAAC,IAAI,IAAI,MAAM,EAAE;IACzB,aAAa;IACb,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;IACpC,WAAW;IACX,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC;IAC9B,WAAW;IACX,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC;IAChC,iCAAiC;IACjC,MAAM,IAAI,SAAS;IAGnB,YAAY;IACZ,QAAQ,IAAI,MAAM;CAGrB"}
1
+ {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../../src/vm/types/wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,8BAAsB,SAAS,CAAC,CAAC,SAAS,MAAM;IAChC,QAAQ,CAAC,KAAK,EAAE,CAAC;gBAAR,KAAK,EAAE,CAAC;IAC7B,cAAc;IACd,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAClC,cAAc;IACd,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK;IAChC,WAAW;IACX,QAAQ,CAAC,IAAI,IAAI,MAAM,EAAE;IACzB,aAAa;IACb,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;IACpC,WAAW;IACX,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC;IAC9B,WAAW;IACX,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC;IAChC,iCAAiC;IACjC,MAAM,IAAI,SAAS;IAGnB,YAAY;IACZ,QAAQ,IAAI,MAAM;CAGrB;AAID,4BAA4B;AAC5B,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,CAEnF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mirascript/mirascript",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "An expression based scripting language.",
6
6
  "main": "./dist/index.js",
@@ -35,11 +35,11 @@
35
35
  "ansi-styles": "^6.2.3",
36
36
  "commander": "^14.0.2",
37
37
  "js-base64": "^3.7.8",
38
- "@mirascript/napi": "~0.1.1",
39
- "@mirascript/wasm": "~0.1.1"
38
+ "@mirascript/napi": "~0.1.3",
39
+ "@mirascript/wasm": "~0.1.3"
40
40
  },
41
41
  "devDependencies": {
42
- "@types/node": "^24.9.1",
42
+ "@types/node": "^24.10.0",
43
43
  "@types/sinon": "^17.0.4",
44
44
  "ava": "^6.4.1",
45
45
  "c8": "^10.1.3",
@@ -60,20 +60,27 @@ export interface SourceReference<T extends DiagnosticCode = DiagnosticCode> exte
60
60
  readonly diagnostic: SourceDiagnostic<T>;
61
61
  }
62
62
 
63
- /** 分析诊断信息 */
64
- export function parseDiagnostics(
65
- source: ScriptInput,
66
- diagnostics: Uint32Array,
67
- ): {
63
+ /** 解析后的诊断信息 */
64
+ interface ParsedDiagnostics {
65
+ /** 错误诊断信息 */
68
66
  errors: SourceDiagnostic[];
67
+ /** 警告诊断信息 */
69
68
  warnings: SourceDiagnostic[];
69
+ /** 信息诊断信息 */
70
70
  infos: SourceDiagnostic[];
71
+ /** 提示诊断信息 */
71
72
  hints: SourceDiagnostic[];
73
+ /** 标签诊断信息 */
72
74
  tags: SourceDiagnostic[];
73
75
 
76
+ /** 引用诊断信息 */
74
77
  references: SourceReference[];
78
+ /** 标签引用诊断信息 */
75
79
  tagsReferences: SourceReference[];
76
- } {
80
+ }
81
+
82
+ /** 分析诊断信息,{@link diagnostic_position_encoding} 不能设为 `None` */
83
+ export function parseDiagnostics(source: ScriptInput, diagnostics: Uint32Array): ParsedDiagnostics {
77
84
  const parsed = [];
78
85
  const bufLen = diagnostics.length;
79
86
  for (let i = 0; i < bufLen; i += 5) {
@@ -148,12 +155,7 @@ export function parseDiagnostics(
148
155
  }
149
156
 
150
157
  /** 生成诊断 range 的字符串 */
151
- function formatRange(range: {
152
- startLineNumber: number;
153
- startColumn: number;
154
- endLineNumber: number;
155
- endColumn: number;
156
- }): string {
158
+ function formatRange(range: IRange): string {
157
159
  if (range.startLineNumber === range.endLineNumber) {
158
160
  if (range.startColumn === range.endColumn) {
159
161
  return `${range.startLineNumber}:${range.startColumn}`;
@@ -1,6 +1,7 @@
1
1
  export const { isArray } = Array;
2
2
  export const { isFinite, isNaN, isInteger, isSafeInteger } = Number;
3
3
  export const { hasOwn, keys, values, entries, create, getPrototypeOf, fromEntries, defineProperty } = Object;
4
+ export const { apply } = Reflect;
4
5
 
5
6
  /**
6
7
  * Determines whether an object has an enumerable property with the specified name.
package/src/vm/error.ts CHANGED
@@ -14,9 +14,21 @@ export class VmError extends Error {
14
14
 
15
15
  /** 从其他错误构造 */
16
16
  static from(prefix: string, error: unknown, recovered: VmAny): VmError {
17
- if (prefix && !prefix.endsWith(': ')) prefix += ': ';
18
- const vmError = new VmError(`${prefix}${error instanceof Error ? error.message : String(error)}`, recovered);
19
- vmError.stack = error instanceof Error ? error.stack : undefined;
17
+ if (prefix) {
18
+ if (prefix.endsWith(':')) {
19
+ prefix += ' ';
20
+ } else if (!prefix.endsWith(': ')) {
21
+ prefix += ': ';
22
+ }
23
+ }
24
+ let vmError: VmError;
25
+ if (error instanceof Error) {
26
+ vmError = new VmError(`${prefix}${error.message}`, recovered);
27
+ vmError.stack = error.stack;
28
+ } else {
29
+ vmError = new VmError(`${prefix}${String(error)}`, recovered);
30
+ }
31
+ vmError.cause = error;
20
32
  return vmError;
21
33
  }
22
34
  }
@@ -3,7 +3,6 @@ import { VmError } from '../error.js';
3
3
  import { $ToNumber, $Type } from '../operations.js';
4
4
  import {
5
5
  isVmArray,
6
- isVmExtern,
7
6
  isVmFunction,
8
7
  type VmExtern,
9
8
  type VmFunction,
@@ -17,6 +16,7 @@ import {
17
16
  type VmConst,
18
17
  isVmConst,
19
18
  VM_ARRAY_MAX_LENGTH,
19
+ isVmCallable,
20
20
  } from '../types/index.js';
21
21
  import type { VmFunctionLike, VmFunctionOption } from '../types/function.js';
22
22
  import { Cp } from '../helpers.js';
@@ -127,8 +127,7 @@ export function expectCallable(
127
127
  recovered: VmAny | (() => VmAny),
128
128
  ): asserts value is VmFunction | VmExtern {
129
129
  required(name, value, recovered);
130
- const callable = isVmFunction(value) || isVmExtern(value);
131
- if (!callable) {
130
+ if (!isVmCallable(value)) {
132
131
  throwUnexpectedTypeError(name, 'callable', value, recovered);
133
132
  }
134
133
  }
@@ -168,11 +167,7 @@ export function map(
168
167
  Cp();
169
168
  const ret = mapper(data[i] ?? null, i, data);
170
169
  if (ret === undefined) continue;
171
- if (isVmConst(ret)) {
172
- result.push(ret);
173
- } else {
174
- result.push(null);
175
- }
170
+ result.push(ret);
176
171
  }
177
172
  return result;
178
173
  } else {
@@ -181,11 +176,7 @@ export function map(
181
176
  Cp();
182
177
  const ret = mapper(value ?? null, key, data);
183
178
  if (ret === undefined) continue;
184
- if (isVmConst(ret)) {
185
- e.push([key, ret]);
186
- } else {
187
- e.push([key, null]);
188
- }
179
+ e.push([key, ret]);
189
180
  }
190
181
  return fromEntries(e);
191
182
  }
@@ -6,7 +6,7 @@ import type { VmLib, VmLibOption } from './_helpers.js';
6
6
  import * as global from './global/index.js';
7
7
 
8
8
  for (const [name, value] of entries(global)) {
9
- VmSharedContext[name] = wrapEntry(name, value as RawValue);
9
+ VmSharedContext[name] = wrapEntry(name, value as RawValue, 'global');
10
10
  }
11
11
 
12
12
  /** 原始值 */
@@ -14,7 +14,7 @@ type RawValue = VmLib | VmConst | VmModule;
14
14
  /** 包装值 */
15
15
  type ToWrappedValue<V extends RawValue> = V extends VmFunctionLike ? VmFunction<V> : V;
16
16
  /** 包装值 */
17
- function wrapEntry<const T extends RawValue>(name: string, value: T): ToWrappedValue<T> {
17
+ function wrapEntry<const T extends RawValue>(name: string, value: T, module: string): ToWrappedValue<T> {
18
18
  if (typeof value == 'function') {
19
19
  if (value.name !== name) {
20
20
  // 如果函数名和导出名不一致,则重命名
@@ -26,7 +26,7 @@ function wrapEntry<const T extends RawValue>(name: string, value: T): ToWrappedV
26
26
  return VmFunction(value, {
27
27
  isLib: true,
28
28
  injectCp: true,
29
- fullName: `global.${name}`,
29
+ fullName: `${module}.${name}`,
30
30
  ...(value as VmLibOption),
31
31
  }) as ToWrappedValue<T>;
32
32
  } else {
@@ -43,7 +43,7 @@ export type ToWrappedModule<T extends Record<string, RawValue>> = VmModule<{
43
43
  export function createModule<const T extends Record<string, RawValue>>(name: string, lib: T): ToWrappedModule<T> {
44
44
  const mod = create(null) as Record<string, VmImmutable>;
45
45
  for (const [key, value] of entries(lib)) {
46
- mod[key] = wrapEntry(key, value);
46
+ mod[key] = wrapEntry(key, value, name);
47
47
  }
48
48
  return new VmModule(name, mod) as ToWrappedModule<T>;
49
49
  }
@@ -15,7 +15,7 @@ export const pow = VmLib((x, y) => _pow($ToNumber(x), $ToNumber(y)), {
15
15
  returnsType: 'number',
16
16
  });
17
17
  export const random = VmLib(Math.random, {
18
- summary: '返回 0 1 之间的伪随机数',
18
+ summary: '返回 [0, 1) 之间的伪随机数',
19
19
  params: {},
20
20
  paramsType: {},
21
21
  returnsType: 'number',
@@ -1,17 +1,19 @@
1
1
  import { entries } from '../../../../helpers/utils.js';
2
2
  import { Cp } from '../../../helpers.js';
3
- import { $Call, $ToBoolean } from '../../../operations.js';
4
- import { type VmValue, isVmArray } from '../../../types/index.js';
5
- import { VmLib, expectArrayOrRecord, expectCallable } from '../../_helpers.js';
3
+ import { $Call, $Same, $ToBoolean } from '../../../operations.js';
4
+ import { type VmValue, isVmArray, isVmCallable } from '../../../types/index.js';
5
+ import { VmLib, expectArrayOrRecord, required } from '../../_helpers.js';
6
6
 
7
7
  export const find = VmLib(
8
8
  (data, predicate) => {
9
9
  expectArrayOrRecord('data', data, null);
10
- expectCallable('predicate', predicate, data);
11
- const p = (value: VmValue, key: string | number, data: VmValue) => {
12
- const ret = $Call(predicate, [value, key, data]);
13
- return $ToBoolean(ret);
14
- };
10
+ required('predicate', predicate, null);
11
+ const p = isVmCallable(predicate)
12
+ ? (value: VmValue, key: string | number, data: VmValue) => {
13
+ const ret = $Call(predicate, [value, key, data]);
14
+ return $ToBoolean(ret);
15
+ }
16
+ : (value: VmValue) => $Same(predicate, value);
15
17
  if (isVmArray(data)) {
16
18
  const { length } = data;
17
19
  for (let i = 0; i < length; i++) {
@@ -37,13 +39,13 @@ export const find = VmLib(
37
39
  summary: '查找数组或记录中的键值对,返回第一个满足条件的键值对',
38
40
  params: {
39
41
  data: '查的数组或记录',
40
- predicate: '用于测试每个键值对的函数,返回 true 或 false',
42
+ predicate: '用于测试每个键值对的函数,或要查找的值',
41
43
  },
42
44
  paramsType: {
43
45
  data: 'array | record',
44
- predicate: 'fn(value: any, key: number | string | nil, input: type(data)) -> boolean',
46
+ predicate: '(fn(value: any, key: number | string | nil, input: type(data)) -> boolean) | any',
45
47
  },
46
48
  returnsType: '(string | number, any) | nil',
47
- examples: ['find([3, 5, 8], fn (v) { v % 2 == 0 }) // (2, 8)'],
49
+ examples: ['find([3, 5, 8], fn (v) { v % 2 == 0 }) // (2, 8)', `find((x: 1, y: 2, z: 3), 2) // ('y', 2)`],
48
50
  },
49
51
  );
@@ -49,7 +49,7 @@ export const filter = VmLib(
49
49
  summary: '过滤数组或记录中的元素,返回满足条件的元素',
50
50
  params: {
51
51
  data: '要过滤的数组或记录',
52
- predicate: '用于测试每个元素的函数,返回 true 或 false',
52
+ predicate: '用于测试每个元素的函数',
53
53
  },
54
54
  paramsType: {
55
55
  data: 'array | record',
@@ -70,7 +70,7 @@ export const filter_map = VmLib(
70
70
  summary: '对数组或记录中的每个元素应用函数,并返回非 nil 的结果',
71
71
  params: {
72
72
  data: '要映射的数组或记录',
73
- f: '应用于每个元素的函数,返回 nil 或非 nil 的值',
73
+ f: '应用于每个元素的函数',
74
74
  },
75
75
  paramsType: {
76
76
  data: 'array | record',
@@ -1,43 +1,137 @@
1
+ import { isArray, isSafeInteger } from '../../../../helpers/utils.js';
1
2
  import { Element } from '../../../helpers.js';
2
3
  import { $ToNumber, $ToString } from '../../../operations.js';
3
- import { isVmArray, type VmConst } from '../../../types/index.js';
4
- import { VmLib, expectArrayOrRecord, throwError } from '../../_helpers.js';
5
- import { isSafeInteger } from '../../../../helpers/utils.js';
4
+ import {
5
+ isVmArray,
6
+ isVmRecord,
7
+ VM_ARRAY_MAX_LENGTH,
8
+ type VmArray,
9
+ type VmConst,
10
+ type VmValue,
11
+ } from '../../../types/index.js';
12
+ import { VmLib, expectArrayOrRecord, expectConst, throwError } from '../../_helpers.js';
13
+
14
+ const arrIndex = (index: NonNullable<VmConst>): number => {
15
+ const idx = Math.trunc($ToNumber(index));
16
+ if (!isSafeInteger(idx) || idx < 0 || idx >= VM_ARRAY_MAX_LENGTH) {
17
+ return -1;
18
+ }
19
+ return idx;
20
+ };
21
+
22
+ const withInner = (obj: VmConst | undefined, key: VmArray, keyIndex: number, value: VmConst): VmConst => {
23
+ if (keyIndex >= key.length) {
24
+ return value;
25
+ }
26
+ const k = key[keyIndex]!;
27
+ let result: Array<VmConst | undefined> | Record<string, VmConst | undefined>;
28
+ if (isVmArray(obj)) {
29
+ result = [...obj];
30
+ } else if (isVmRecord(obj)) {
31
+ result = { ...obj };
32
+ } else if (arrIndex(k) === k) {
33
+ result = [];
34
+ } else {
35
+ result = {};
36
+ }
37
+ if (isArray(result)) {
38
+ const index = arrIndex(k);
39
+ while (index > result.length) {
40
+ result.push(null);
41
+ }
42
+ result[index] = withInner(result[index], key, keyIndex + 1, value);
43
+ } else {
44
+ const prop = $ToString(k);
45
+ result[prop] = withInner(result[prop], key, keyIndex + 1, value);
46
+ }
47
+ return result;
48
+ };
49
+
50
+ const normalizeEntries = (data: VmConst, entries: Array<VmValue | undefined>): Map<NonNullable<VmConst>, VmConst> => {
51
+ if (entries.length % 2 !== 0) {
52
+ throwError('Expected even number of entries', data);
53
+ }
54
+ const entryData = new Map<NonNullable<VmConst>, VmConst>();
55
+ for (let i = 0; i < entries.length; i += 2) {
56
+ let key = entries[i]!;
57
+ expectConst('key', key, data);
58
+ if (key == null) {
59
+ continue;
60
+ }
61
+ if (isVmArray(key)) {
62
+ if (key.length === 0 || key.includes(null) || key.includes(undefined)) {
63
+ continue;
64
+ } else if (key.length === 1) {
65
+ key = key[0]!;
66
+ }
67
+ }
68
+ const value = entries[i + 1]!;
69
+ entryData.set(key, Element(value));
70
+ }
71
+ return entryData;
72
+ };
6
73
 
7
74
  const _with = VmLib(
8
75
  (data, ...entries) => {
9
76
  expectArrayOrRecord('data', data, data);
10
- if (entries.length % 2 !== 0) {
11
- throwError('Expected even number of entries', data);
77
+ if (entries.length === 0) {
78
+ return data;
12
79
  }
80
+
81
+ const entryData = normalizeEntries(data, entries);
13
82
  if (isVmArray(data)) {
14
83
  const result: Array<VmConst | undefined> = [...data];
15
- for (let i = 0; i < entries.length; i += 2) {
16
- const index = Math.trunc($ToNumber(entries[i]));
17
- if (!isSafeInteger(index) || index < 0) continue;
18
- const value = entries[i + 1];
84
+ for (const [key, value] of entryData) {
85
+ let index: number;
86
+ let val: VmConst;
87
+ if (isVmArray(key)) {
88
+ index = arrIndex(key[0]!);
89
+ if (index < 0) continue;
90
+ val = withInner(result[index], key, 1, value);
91
+ } else {
92
+ index = arrIndex(key);
93
+ if (index < 0) continue;
94
+ val = value;
95
+ }
19
96
  while (index > result.length) {
20
97
  result.push(null);
21
98
  }
22
- result[index] = Element(value);
99
+ result[index] = val;
23
100
  }
24
101
  return result;
25
102
  } else {
26
103
  const result: Record<string, VmConst | undefined> = { ...data };
27
- for (let i = 0; i < entries.length; i += 2) {
28
- const key = $ToString(entries[i]);
29
- const value = entries[i + 1];
30
- result[key] = Element(value);
104
+ for (const [key, value] of entryData) {
105
+ let prop: string;
106
+ let val: VmConst;
107
+ if (isVmArray(key)) {
108
+ const firstKey = key[0]!;
109
+ prop = $ToString(firstKey);
110
+ val = withInner(result[prop], key, 1, value);
111
+ } else {
112
+ prop = $ToString(key);
113
+ val = value;
114
+ }
115
+ result[prop] = val;
31
116
  }
32
117
  return result;
33
118
  }
34
119
  },
35
120
  {
36
121
  summary: '在数组或记录中设置多个键值对',
37
- params: { data: '要设置的数组或记录', '..entries': '要设置的键值对,成对出现' },
38
- paramsType: { data: 'array | record', '..entries': '[..[string | number, any][]]' },
122
+ params: {
123
+ data: '要设置的数组或记录',
124
+ '..entries': '要设置的键值对,成对出现',
125
+ },
126
+ paramsType: {
127
+ data: 'array | record',
128
+ '..entries': `[..[string | number | (string | number)[], any][]]`,
129
+ },
39
130
  returnsType: 'type(data)',
40
- examples: ['with([10, 20], 2, 99) // [10, 20, 99]', 'with((a: 1), "b", 2) // (a: 1, b: 2)'],
131
+ examples: [
132
+ `with([10, 20], 2, 99, 3 ,100) // [10, 20, 99, 100]`,
133
+ `(a: 1)::with(["b", 1], 2) // (a: 1, b: [nil, 2])`,
134
+ ],
41
135
  },
42
136
  );
43
137
  export { _with as 'with' };
@@ -1,24 +1,23 @@
1
1
  import { VmError } from './error.js';
2
2
  import {
3
+ isVmPrimitive,
3
4
  isVmArray,
4
- VmModule,
5
- VmExtern,
6
5
  isVmRecord,
6
+ isVmFunction,
7
+ isVmExtern,
8
+ isVmModule,
9
+ isVmWrapper,
7
10
  getVmFunctionInfo,
8
- isVmPrimitive,
9
11
  type TypeName,
10
12
  type VmAny,
11
13
  type VmImmutable,
12
14
  type VmRecord,
13
15
  type VmValue,
14
- isVmFunction,
15
16
  type VmArray,
16
17
  type VmConst,
17
- isVmExtern,
18
18
  type VmPrimitive,
19
19
  type VmFunction,
20
20
  } from './types/index.js';
21
- import { VmWrapper } from './types/wrapper.js';
22
21
  import { hasOwnEnumerable, isNaN, isSafeInteger, keys, create } from '../helpers/utils.js';
23
22
 
24
23
  const { abs, min, trunc, ceil } = Math;
@@ -33,8 +32,8 @@ const isSame = (a: VmValue, b: VmValue): boolean => {
33
32
  // Any primitives and functions arrive here are not equal
34
33
  if (a == null || typeof a != 'object' || b == null || typeof b != 'object') return false;
35
34
  // Handle wrapper values
36
- if (a instanceof VmWrapper) return a.same(b);
37
- if (b instanceof VmWrapper) return b.same(a);
35
+ if (isVmWrapper(a)) return a.same(b);
36
+ if (isVmWrapper(b)) return b.same(a);
38
37
  // Handle array values
39
38
  if (isVmArray(a) && isVmArray(b)) {
40
39
  const len = a.length;
@@ -141,11 +140,6 @@ export const $Eq = (a: VmAny, b: VmAny): boolean => {
141
140
  export const $Neq = (a: VmAny, b: VmAny): boolean => {
142
141
  return !$Eq(a, b);
143
142
  };
144
- const stringComparer = new Intl.Collator('en', {
145
- usage: 'sort',
146
- numeric: false,
147
- sensitivity: 'base',
148
- });
149
143
  export const $Aeq = (a: VmAny, b: VmAny): boolean => {
150
144
  if (overloadNumberString(a, b)) {
151
145
  const an = $ToNumber(a);
@@ -159,11 +153,16 @@ export const $Aeq = (a: VmAny, b: VmAny): boolean => {
159
153
  const base = min(abs(an), abs(bn));
160
154
  return absoluteDifference < base * EPS;
161
155
  } else {
162
- // For strings, we use localeCompare for case-insensitive accent-insensitive comparison
156
+ // For strings, we use normalized case-insensitive comparison
163
157
  const as = $ToString(a);
164
158
  const bs = $ToString(b);
165
159
  if (as === bs) return true;
166
- return stringComparer.compare(as, bs) === 0;
160
+ const ai = as.toLowerCase();
161
+ const bi = bs.toLowerCase();
162
+ if (ai === bi) return true;
163
+ const an = ai.normalize('NFC');
164
+ const bn = bi.normalize('NFC');
165
+ return an === bn;
167
166
  }
168
167
  };
169
168
  export const $Naeq = (a: VmAny, b: VmAny): boolean => {
@@ -196,7 +195,7 @@ export const $In = (value: VmAny, iterable: VmAny): boolean => {
196
195
  return iterable.some((item = null) => isSame(item, value));
197
196
  }
198
197
  // iterable is a record or an extern here, value should be a string
199
- if (iterable instanceof VmWrapper) return iterable.has($ToString(value));
198
+ if (isVmWrapper(iterable)) return iterable.has($ToString(value));
200
199
  if (typeof iterable == 'object') return hasOwnEnumerable(iterable, $ToString(value));
201
200
  iterable satisfies VmPrimitive | VmFunction;
202
201
  return false;
@@ -217,7 +216,7 @@ export const $Length = (value: VmAny): number => {
217
216
  $AssertInit(value);
218
217
  if (isVmArray(value)) return value.length;
219
218
  if (isVmRecord(value)) return keys(value).length;
220
- if (value instanceof VmWrapper) {
219
+ if (isVmWrapper(value)) {
221
220
  return value.keys().length;
222
221
  }
223
222
  return Number.NaN;
@@ -285,7 +284,7 @@ export const $Call = (func: VmValue, args: readonly VmAny[]): VmValue => {
285
284
  for (const a of args) {
286
285
  $AssertInit(a);
287
286
  }
288
- if (func instanceof VmExtern) {
287
+ if (isVmExtern(func)) {
289
288
  return func.call(args as readonly VmValue[]) ?? null;
290
289
  }
291
290
  if (isVmFunction(func)) {
@@ -295,8 +294,8 @@ export const $Call = (func: VmValue, args: readonly VmAny[]): VmValue => {
295
294
  };
296
295
  export const $Type = (value: VmAny): TypeName => {
297
296
  if (value === undefined || value === null) return 'nil';
298
- if (value instanceof VmExtern) return 'extern';
299
- if (value instanceof VmModule) return 'module';
297
+ if (isVmExtern(value)) return 'extern';
298
+ if (isVmModule(value)) return 'module';
300
299
  if (isVmArray(value)) return 'array';
301
300
  if (typeof value == 'object') return 'record';
302
301
  return typeof value as TypeName;
@@ -316,7 +315,7 @@ function numberToString(value: number): string {
316
315
  /** 将值转为字符串 */
317
316
  function innerToString(value: VmAny, useBraces: boolean): string {
318
317
  if (value == null) return 'nil';
319
- if (value instanceof VmWrapper) return value.toString();
318
+ if (isVmWrapper(value)) return value.toString();
320
319
  if (typeof value == 'function') {
321
320
  const name = getVmFunctionInfo(value)?.fullName;
322
321
  return name ? `<function ${name}>` : `<function>`;
@@ -397,7 +396,7 @@ export const $Has = (obj: VmAny, key: VmAny): boolean => {
397
396
  $AssertInit(obj);
398
397
  const pk = $ToString(key);
399
398
  if (obj == null || typeof obj != 'object') return false;
400
- if (obj instanceof VmWrapper) return obj.has(pk);
399
+ if (isVmWrapper(obj)) return obj.has(pk);
401
400
  return hasOwnEnumerable(obj, pk);
402
401
  };
403
402
  export const $Get = (obj: VmAny, key: VmAny): VmValue => {
@@ -409,7 +408,7 @@ export const $Get = (obj: VmAny, key: VmAny): VmValue => {
409
408
  }
410
409
  const pk = $ToString(key);
411
410
  if (obj == null || typeof obj != 'object') return null;
412
- if (obj instanceof VmWrapper) return obj.get(pk) ?? null;
411
+ if (isVmWrapper(obj)) return obj.get(pk) ?? null;
413
412
  if (!hasOwnEnumerable(obj, pk)) return null;
414
413
  return (obj as Record<string, VmImmutable>)[pk] ?? null;
415
414
  };
@@ -423,7 +422,7 @@ export const $Set = (obj: VmAny, key: VmAny, value: VmAny): void => {
423
422
  };
424
423
  export const $Iterable = (value: VmAny): Iterable<VmValue | undefined> => {
425
424
  $AssertInit(value);
426
- if (value instanceof VmWrapper) return value.keys();
425
+ if (isVmWrapper(value)) return value.keys();
427
426
  if (isVmArray(value)) return value;
428
427
  if (value != null && typeof value == 'object') return keys(value);
429
428
  throw new VmError(`Value is not iterable`, isVmFunction(value) ? [] : [value]);