@mirascript/mirascript 0.1.2 → 0.1.4

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 (63) hide show
  1. package/dist/{chunk-3RUWGMBP.js → chunk-JB6LPPFJ.js} +190 -24
  2. package/dist/chunk-JB6LPPFJ.js.map +6 -0
  3. package/dist/{chunk-AOINGBRS.js → chunk-RYSPVMVZ.js} +189 -191
  4. package/dist/chunk-RYSPVMVZ.js.map +6 -0
  5. package/dist/cli/execute.d.ts +1 -1
  6. package/dist/cli/execute.d.ts.map +1 -1
  7. package/dist/cli/index.js +42 -21
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/print.d.ts.map +1 -1
  10. package/dist/compiler/diagnostic.d.ts +14 -3
  11. package/dist/compiler/diagnostic.d.ts.map +1 -1
  12. package/dist/compiler/emit.d.ts +2 -1
  13. package/dist/compiler/emit.d.ts.map +1 -1
  14. package/dist/compiler/index.d.ts.map +1 -1
  15. package/dist/compiler/worker.d.ts.map +1 -1
  16. package/dist/compiler/worker.js +6 -3
  17. package/dist/compiler/worker.js.map +1 -1
  18. package/dist/helpers/serialize.d.ts +10 -2
  19. package/dist/helpers/serialize.d.ts.map +1 -1
  20. package/dist/index.js +4 -2
  21. package/dist/subtle.d.ts +1 -1
  22. package/dist/subtle.d.ts.map +1 -1
  23. package/dist/subtle.js +17 -7
  24. package/dist/vm/lib/_helpers.d.ts.map +1 -1
  25. package/dist/vm/lib/global/sequence/with.d.ts +2 -2
  26. package/dist/vm/lib/global/sequence/with.d.ts.map +1 -1
  27. package/dist/vm/operations.d.ts.map +1 -1
  28. package/dist/vm/types/boundary.d.ts.map +1 -1
  29. package/dist/vm/types/checker.d.ts +1 -1
  30. package/dist/vm/types/checker.d.ts.map +1 -1
  31. package/dist/vm/types/context.d.ts +14 -6
  32. package/dist/vm/types/context.d.ts.map +1 -1
  33. package/dist/vm/types/extern.d.ts +2 -0
  34. package/dist/vm/types/extern.d.ts.map +1 -1
  35. package/dist/vm/types/index.d.ts +6 -10
  36. package/dist/vm/types/index.d.ts.map +1 -1
  37. package/dist/vm/types/module.d.ts +2 -0
  38. package/dist/vm/types/module.d.ts.map +1 -1
  39. package/dist/vm/types/wrapper.d.ts +2 -0
  40. package/dist/vm/types/wrapper.d.ts.map +1 -1
  41. package/package.json +7 -6
  42. package/src/cli/execute.ts +8 -3
  43. package/src/cli/index.ts +22 -5
  44. package/src/cli/print.ts +13 -9
  45. package/src/compiler/diagnostic.ts +32 -14
  46. package/src/compiler/emit.ts +45 -19
  47. package/src/compiler/index.ts +10 -2
  48. package/src/compiler/worker.ts +5 -1
  49. package/src/helpers/serialize.ts +6 -6
  50. package/src/subtle.ts +11 -1
  51. package/src/vm/lib/_helpers.ts +2 -10
  52. package/src/vm/lib/global/math-arr.ts +1 -1
  53. package/src/vm/lib/global/sequence/with.ts +111 -17
  54. package/src/vm/operations.ts +23 -24
  55. package/src/vm/types/boundary.ts +4 -4
  56. package/src/vm/types/checker.ts +8 -15
  57. package/src/vm/types/context.ts +28 -16
  58. package/src/vm/types/extern.ts +11 -2
  59. package/src/vm/types/index.ts +7 -26
  60. package/src/vm/types/module.ts +7 -0
  61. package/src/vm/types/wrapper.ts +10 -1
  62. package/dist/chunk-3RUWGMBP.js.map +0 -6
  63. package/dist/chunk-AOINGBRS.js.map +0 -6
@@ -1,6 +1,7 @@
1
1
  import type { ScriptInput, TranspileOptions } from './types.js';
2
2
  import { emit } from './emit.js';
3
3
  import { generateBytecode } from './generate-bytecode.js';
4
+ import { DiagnosticCode, parseDiagnostics } from './diagnostic.js';
4
5
 
5
6
  /**
6
7
  * 生成 MiraScript 对应的 JavaScript 代码
@@ -13,7 +14,10 @@ export async function compile(
13
14
  if (bytecode == null) {
14
15
  return [undefined, errors];
15
16
  }
16
- const generatedCode = emit(script, bytecode, options);
17
+ const sourcemaps = options.sourceMap
18
+ ? parseDiagnostics(script, errors, (c) => c === DiagnosticCode.SourceMap).sourcemaps
19
+ : [];
20
+ const generatedCode = emit(script, bytecode, sourcemaps, options);
17
21
  return [generatedCode, errors];
18
22
  }
19
23
 
@@ -67,7 +67,7 @@ const DEFAULT_OPTIONS = Object.freeze({
67
67
  } satisfies SerializeOptions);
68
68
 
69
69
  /** 获取选项 */
70
- export function getSerializeOptions(options: Partial<SerializeOptions> | undefined): SerializeOptions {
70
+ function getSerializeOptions(options: Partial<SerializeOptions> | undefined): SerializeOptions {
71
71
  if (options == null) return DEFAULT_OPTIONS;
72
72
  const opt = { ...DEFAULT_OPTIONS };
73
73
  for (const key in options) {
@@ -157,17 +157,17 @@ export function serializePropName(value: string, options?: Partial<SerializeOpti
157
157
  }
158
158
 
159
159
  /** 序列化 nil 值 */
160
- function serializeNil(): string {
160
+ export function serializeNil(): string {
161
161
  return 'nil';
162
162
  }
163
163
 
164
164
  /** 序列化布尔值 */
165
- function serializeBoolean(value: boolean): string {
165
+ export function serializeBoolean(value: boolean): string {
166
166
  return value ? 'true' : 'false';
167
167
  }
168
168
 
169
169
  /** 序列化数字 */
170
- function serializeNumber(value: number): string {
170
+ export function serializeNumber(value: number): string {
171
171
  if (isNaN(value)) return 'nan';
172
172
  if (!isFinite(value)) return value < 0 ? '-inf' : 'inf';
173
173
  if (value === 0) {
@@ -178,7 +178,7 @@ function serializeNumber(value: number): string {
178
178
  }
179
179
 
180
180
  /** 序列化数组 */
181
- function serializeArray(value: VmArray, depth: number, options: SerializeOptions): string {
181
+ export function serializeArray(value: VmArray, depth: number, options: SerializeOptions): string {
182
182
  if (depth > options.maxDepth) return `[]`;
183
183
  if (value.length === 0) return '[]';
184
184
  let str = '[';
@@ -207,7 +207,7 @@ function customValueOf(value: VmRecord): VmAny | undefined {
207
207
  }
208
208
 
209
209
  /** 序列化记录 */
210
- function serializeRecord(value: VmRecord, depth: number, options: SerializeOptions): string {
210
+ export function serializeRecord(value: VmRecord, depth: number, options: SerializeOptions): string {
211
211
  const customValue = customValueOf(value);
212
212
  if (customValue !== undefined) {
213
213
  return serializeImpl(customValue, depth - 1, options);
package/src/subtle.ts CHANGED
@@ -1,7 +1,17 @@
1
1
  export * as constants from './helpers/constants.js';
2
2
  export { VmSharedContext, DefaultVmContext } from './vm/types/context.js';
3
3
  export * as operations from './vm/operations.js';
4
- export { serialize, serializeString, serializePropName, type SerializeOptions } from './helpers/serialize.js';
4
+ export {
5
+ serialize,
6
+ serializeNil,
7
+ serializeBoolean,
8
+ serializeNumber,
9
+ serializeString,
10
+ serializePropName,
11
+ serializeArray,
12
+ serializeRecord,
13
+ type SerializeOptions,
14
+ } from './helpers/serialize.js';
5
15
  export { lib } from './vm/lib/_loader.js';
6
16
  export * from './compiler/diagnostic.js';
7
17
  export { generateBytecode, generateBytecodeSync, emitScript } from './compiler/index.js';
@@ -167,11 +167,7 @@ export function map(
167
167
  Cp();
168
168
  const ret = mapper(data[i] ?? null, i, data);
169
169
  if (ret === undefined) continue;
170
- if (isVmConst(ret)) {
171
- result.push(ret);
172
- } else {
173
- result.push(null);
174
- }
170
+ result.push(ret);
175
171
  }
176
172
  return result;
177
173
  } else {
@@ -180,11 +176,7 @@ export function map(
180
176
  Cp();
181
177
  const ret = mapper(value ?? null, key, data);
182
178
  if (ret === undefined) continue;
183
- if (isVmConst(ret)) {
184
- e.push([key, ret]);
185
- } else {
186
- e.push([key, null]);
187
- }
179
+ e.push([key, ret]);
188
180
  }
189
181
  return fromEntries(e);
190
182
  }
@@ -36,7 +36,7 @@ export const hypot = VmLib(build(Math.hypot), {
36
36
  export const sum = VmLib(
37
37
  (...values: readonly VmAny[]) => {
38
38
  const numbers = getNumbers(values);
39
- return numbers.reduce((a, b) => a + b, 0);
39
+ return numbers.reduce((a, b) => a + b, -0);
40
40
  },
41
41
  {
42
42
  summary: '返回一组数的总和',
@@ -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]);
@@ -1,6 +1,6 @@
1
1
  import { isVmFunction, type VmFunctionLike, type VmFunction } from './function.js';
2
- import { VmExtern } from './extern.js';
3
- import { VmWrapper } from './wrapper.js';
2
+ import { isVmExtern, VmExtern } from './extern.js';
3
+ import { isVmWrapper } from './wrapper.js';
4
4
  import type { VmAny, VmConst, VmModule, VmPrimitive, VmValue } from './index.js';
5
5
  import { $Call } from '../operations.js';
6
6
  import { defineProperty, apply } from '../../helpers/utils.js';
@@ -55,7 +55,7 @@ export function wrapToVmValue(
55
55
  return new VmExtern(value as () => never, thisArg);
56
56
  }
57
57
  case 'object': {
58
- if (value instanceof VmWrapper) return value as VmModule | VmExtern;
58
+ if (isVmWrapper(value)) return value as VmModule | VmExtern;
59
59
  if (value instanceof Date) return value.valueOf();
60
60
  if (assumeVmValue?.(value)) return value;
61
61
  // Only functions preserve thisArg
@@ -80,7 +80,7 @@ export function unwrapFromVmValue(value: VmAny): unknown {
80
80
  return toVmFunctionProxy(value);
81
81
  }
82
82
  if (value == null || typeof value != 'object') return value;
83
- if (!(value instanceof VmExtern)) return value;
83
+ if (!isVmExtern(value)) return value;
84
84
 
85
85
  if (value.thisArg == null || typeof value.value != 'function') {
86
86
  return value.value;
@@ -1,15 +1,8 @@
1
1
  import { getPrototypeOf, isArray, values } from '../../helpers/utils.js';
2
- import {
3
- isVmFunction,
4
- VmModule,
5
- type VmAny,
6
- type VmArray,
7
- type VmConst,
8
- type VmImmutable,
9
- type VmRecord,
10
- type VmValue,
11
- } from './index.js';
12
- import { VmWrapper } from './wrapper.js';
2
+ import type { VmAny, VmArray, VmConst, VmImmutable, VmRecord, VmValue } from './index.js';
3
+ import { isVmWrapper } from './wrapper.js';
4
+ import { isVmModule } from './module.js';
5
+ import { isVmFunction } from './function.js';
13
6
 
14
7
  const MAX_DEPTH = 32;
15
8
  /**
@@ -59,7 +52,7 @@ function isVmConstInner(value: unknown, depth: number): value is VmConst {
59
52
  return true;
60
53
  case 'object':
61
54
  if (value == null) return true;
62
- if (value instanceof VmWrapper) return false;
55
+ if (isVmWrapper(value)) return false;
63
56
  if (isArray(value)) {
64
57
  return isVmArrayDeep(value, depth);
65
58
  } else {
@@ -92,7 +85,7 @@ export function isVmConst(value: unknown, checkDeep = false): value is VmConst {
92
85
  return true;
93
86
  case 'object':
94
87
  if (value == null) return true;
95
- if (value instanceof VmWrapper) return false;
88
+ if (isVmWrapper(value)) return false;
96
89
  if (!checkDeep) {
97
90
  if (isArray(value)) {
98
91
  return isVmArrayDeep(value, 0);
@@ -122,7 +115,7 @@ export function isVmImmutable(value: unknown, checkDeep: boolean): value is VmIm
122
115
  * 检查是否为 Mirascript 不可变值
123
116
  */
124
117
  export function isVmImmutable(value: unknown, checkDeep = false): value is VmImmutable {
125
- return value instanceof VmModule || isVmFunction(value) || isVmConst(value, checkDeep);
118
+ return isVmModule(value) || isVmFunction(value) || isVmConst(value, checkDeep);
126
119
  }
127
120
  /**
128
121
  * 检查是否为 Mirascript 合法值
@@ -152,7 +145,7 @@ export function isVmAny(value: unknown, checkDeep: boolean): value is VmAny {
152
145
  return true;
153
146
  case 'object':
154
147
  if (value == null) return true;
155
- if (value instanceof VmWrapper) return true;
148
+ if (isVmWrapper(value)) return true;
156
149
  return isVmConst(value, checkDeep);
157
150
  case 'function':
158
151
  return isVmFunction(value);
@@ -29,6 +29,8 @@ export interface VmContext {
29
29
  readonly [kVmContext]: true;
30
30
  /** 枚举所有 key,仅在 LSP 中使用 */
31
31
  keys(): Iterable<string>;
32
+ /** 描述值,返回 MarkDown 文本,仅在 LSP 中使用 */
33
+ describe?(key: string): string | undefined;
32
34
  /** 获取指定 key 的值 `global[key]` */
33
35
  get(key: string): VmValue;
34
36
  /** 查找指定 key 是否存在 `key in global` */
@@ -91,7 +93,11 @@ class ValueVmContext implements VmContext {
91
93
  has(key: string): boolean {
92
94
  return key in this.env;
93
95
  }
94
- constructor(private readonly env: VmContextRecord) {}
96
+ constructor(
97
+ private readonly env: VmContextRecord,
98
+ /** @inheritdoc */
99
+ readonly describe?: (key: string) => string | undefined,
100
+ ) {}
95
101
  }
96
102
 
97
103
  /** 以工厂函数为后备的实现 */
@@ -115,30 +121,36 @@ class FactoryVmContext implements VmContext {
115
121
  constructor(
116
122
  private readonly getter: (key: string) => VmValue | undefined,
117
123
  private readonly enumerator?: () => Iterable<string>,
124
+ /** @inheritdoc */
125
+ readonly describe?: (key: string) => string | undefined,
118
126
  ) {}
119
127
  }
120
128
 
129
+ /** 以值为后备的实现 */
130
+ type CreateVmContextWithValues = readonly [
131
+ vmValues?: VmContextRecord | null | undefined,
132
+ externValues?: Record<string, unknown> | null | undefined,
133
+ describer?: ((key: string) => string | undefined) | null | undefined,
134
+ ];
135
+ /** 以工厂函数为后备的实现 */
136
+ type CreateVmContextWithFactory = readonly [
137
+ getter: (key: string) => VmValue | undefined,
138
+ enumerator?: (() => Iterable<string>) | null | undefined,
139
+ describer?: ((key: string) => string | undefined) | null | undefined,
140
+ ];
141
+
121
142
  /** 创建用于执行脚本的执行上下文 */
122
- export function createVmContext(
123
- ...args:
124
- | readonly [
125
- vmValues?: VmContextRecord | null | undefined,
126
- externValues?: Record<string, unknown> | null | undefined,
127
- ]
128
- | readonly [
129
- getter: (key: string) => VmValue | undefined,
130
- enumerator?: (() => Iterable<string>) | null | undefined,
131
- ]
132
- ): VmContext {
143
+ export function createVmContext(...args: CreateVmContextWithValues | CreateVmContextWithFactory): VmContext {
133
144
  if (args[0] == null && args[1] == null) {
134
145
  return { ...DefaultVmContext };
135
146
  }
136
147
 
137
148
  if (typeof args[0] == 'function') {
138
- return new FactoryVmContext(args[0], args[1] as (() => Iterable<string>) | undefined);
149
+ const [getter, enumerator, describer] = args as CreateVmContextWithFactory;
150
+ return new FactoryVmContext(getter, enumerator ?? undefined, describer ?? undefined);
139
151
  }
140
152
 
141
- const [vmValues, externValues] = args;
153
+ const [vmValues, externValues, describer] = args as CreateVmContextWithValues;
142
154
  const env = create(VmSharedContext) as VmContextRecord;
143
155
  if (vmValues) {
144
156
  for (const [key, value] of entries(vmValues)) {
@@ -147,11 +159,11 @@ export function createVmContext(
147
159
  }
148
160
  }
149
161
  if (externValues) {
150
- for (const [key, value] of entries(externValues as Record<string, unknown>)) {
162
+ for (const [key, value] of entries(externValues)) {
151
163
  env[key] = value == null ? null : wrapToVmValue(value, null);
152
164
  }
153
165
  }
154
- return new ValueVmContext(env);
166
+ return new ValueVmContext(env, describer ?? undefined);
155
167
  }
156
168
 
157
169
  /** 检查是否为执行上下文 */
@@ -7,6 +7,8 @@ import { unwrapFromVmValue, wrapToVmValue } from './boundary.js';
7
7
  const ObjectPrototype = Object.prototype;
8
8
  // eslint-disable-next-line @typescript-eslint/unbound-method
9
9
  const ObjectToString = ObjectPrototype.toString;
10
+ // eslint-disable-next-line @typescript-eslint/unbound-method
11
+ const FunctionToString = Function.prototype.toString;
10
12
  /** 包装 Mirascript `extern` 类型的对象 */
11
13
  export class VmExtern<const T extends object = object> extends VmWrapper<T> {
12
14
  constructor(
@@ -82,14 +84,14 @@ export class VmExtern<const T extends object = object> extends VmWrapper<T> {
82
84
  }
83
85
  /** @inheritdoc */
84
86
  override same(other: VmAny): boolean {
85
- if (!(other instanceof VmExtern)) return false;
87
+ if (!isVmExtern(other)) return false;
86
88
  return this.value === other.value && this.thisArg === other.thisArg;
87
89
  }
88
90
  /** @inheritdoc */
89
91
  override toString(): string {
90
92
  // eslint-disable-next-line @typescript-eslint/unbound-method
91
93
  const { toString } = this.value;
92
- if (typeof toString != 'function' || toString === ObjectToString) {
94
+ if (typeof toString != 'function' || toString === ObjectToString || toString === FunctionToString) {
93
95
  return super.toString();
94
96
  }
95
97
  try {
@@ -128,3 +130,10 @@ export class VmExtern<const T extends object = object> extends VmWrapper<T> {
128
130
  return tag;
129
131
  }
130
132
  }
133
+
134
+ const kVmExtern = Symbol.for('mirascript.vm.extern');
135
+ Object.defineProperty(VmExtern.prototype, kVmExtern, { value: true });
136
+ /** 检查值是否为 Mirascript 外部值 */
137
+ export function isVmExtern<T extends object>(value: unknown): value is VmExtern<T> {
138
+ return value != null && typeof value == 'object' && kVmExtern in value;
139
+ }