@mirascript/mirascript 0.1.17 → 0.1.19

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 (45) hide show
  1. package/dist/{chunk-JG3D67GF.js → chunk-5AEWM4W6.js} +30 -12
  2. package/dist/{chunk-JG3D67GF.js.map → chunk-5AEWM4W6.js.map} +1 -1
  3. package/dist/{chunk-W2I5XPIE.js → chunk-BFSB6SQK.js} +204 -61
  4. package/dist/chunk-BFSB6SQK.js.map +6 -0
  5. package/dist/cli/execute.d.ts.map +1 -1
  6. package/dist/cli/index.js +11 -12
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/compiler/diagnostic.d.ts.map +1 -1
  9. package/dist/compiler/worker.js +1 -1
  10. package/dist/index.js +2 -2
  11. package/dist/subtle.js +2 -2
  12. package/dist/vm/checkpoint.d.ts.map +1 -1
  13. package/dist/vm/lib/global/debug.d.ts +17 -1
  14. package/dist/vm/lib/global/debug.d.ts.map +1 -1
  15. package/dist/vm/lib/global/json.d.ts.map +1 -1
  16. package/dist/vm/lib/global/math-const.d.ts +24 -2
  17. package/dist/vm/lib/global/math-const.d.ts.map +1 -1
  18. package/dist/vm/lib/helpers.d.ts +7 -3
  19. package/dist/vm/lib/helpers.d.ts.map +1 -1
  20. package/dist/vm/lib/index.d.ts.map +1 -1
  21. package/dist/vm/lib/loader.d.ts +12 -5
  22. package/dist/vm/lib/loader.d.ts.map +1 -1
  23. package/dist/vm/types/context.d.ts +3 -2
  24. package/dist/vm/types/context.d.ts.map +1 -1
  25. package/dist/vm/types/extern.d.ts +1 -1
  26. package/dist/vm/types/extern.d.ts.map +1 -1
  27. package/dist/vm/types/module.d.ts +1 -1
  28. package/dist/vm/types/module.d.ts.map +1 -1
  29. package/dist/vm/types/wrapper.d.ts +4 -2
  30. package/dist/vm/types/wrapper.d.ts.map +1 -1
  31. package/package.json +2 -2
  32. package/src/cli/execute.ts +10 -10
  33. package/src/compiler/diagnostic.ts +29 -11
  34. package/src/vm/checkpoint.ts +19 -10
  35. package/src/vm/lib/global/debug.ts +84 -7
  36. package/src/vm/lib/global/json.ts +3 -4
  37. package/src/vm/lib/global/math-const.ts +34 -2
  38. package/src/vm/lib/helpers.ts +9 -10
  39. package/src/vm/lib/index.ts +7 -3
  40. package/src/vm/lib/loader.ts +56 -19
  41. package/src/vm/types/context.ts +21 -6
  42. package/src/vm/types/extern.ts +1 -1
  43. package/src/vm/types/module.ts +1 -1
  44. package/src/vm/types/wrapper.ts +7 -3
  45. package/dist/chunk-W2I5XPIE.js.map +0 -6
@@ -2,10 +2,17 @@
2
2
  import styles from 'ansi-styles';
3
3
  import supportsColor from 'supports-color';
4
4
  import { compile } from '../index.js';
5
- import { createVmContext, VmFunction, type VmValue } from '../vm/index.js';
6
- import { debug_print } from '../vm/lib/global/debug.js';
5
+ import { createVmContext, type VmValue } from '../vm/index.js';
6
+ import { debug_print, panic } from '../vm/lib/global/debug.js';
7
7
  import { print } from './print.js';
8
8
 
9
+ panic.serializer = debug_print.serializer = (arg, format) => {
10
+ if (format === '%o' || format === '%O' || !format) {
11
+ return print(arg);
12
+ }
13
+ return null;
14
+ };
15
+
9
16
  /** 执行脚本 */
10
17
  export async function execute(
11
18
  script: string,
@@ -15,14 +22,7 @@ export async function execute(
15
22
  ): Promise<void> {
16
23
  try {
17
24
  const f = await compile(script, { input_mode: template ? 'Template' : 'Script', sourceMap: true, fileName });
18
- const r = f(
19
- createVmContext({
20
- debug_print: VmFunction((...values) => {
21
- console.log(...debug_print.prefix, ...values.map((v) => (typeof v == 'string' ? v : print(v))));
22
- }, debug_print),
23
- ...variables,
24
- }),
25
- );
25
+ const r = f(createVmContext(variables));
26
26
  if (template) {
27
27
  console.log(r);
28
28
  } else {
@@ -8,11 +8,12 @@ export { DiagnosticCode };
8
8
  const diagnosticMessages = new Map<DiagnosticCode, string | null>();
9
9
  /** 获取 {@link DiagnosticCode} 对应的消息 */
10
10
  export function getDiagnosticMessage(code: DiagnosticCode): string | null {
11
- if (!isSafeInteger(code) || code < 0 || code >= 0xffff) {
11
+ if (code < 0 || code >= 0xffff || !isSafeInteger(code)) {
12
12
  throw new RangeError(`Invalid DiagnosticCode: ${code}`);
13
13
  }
14
- if (diagnosticMessages.has(code)) {
15
- return diagnosticMessages.get(code) || null;
14
+ const cached = diagnosticMessages.get(code);
15
+ if (cached !== undefined) {
16
+ return cached;
16
17
  }
17
18
  const mod = getModule();
18
19
  const msg = mod.getDiagnosticMessage(code);
@@ -83,6 +84,23 @@ interface ParsedDiagnostics {
83
84
  sourcemaps: IRange[];
84
85
  }
85
86
 
87
+ const {
88
+ ErrorStart,
89
+ ErrorEnd,
90
+ WarningStart,
91
+ WarningEnd,
92
+ InfoStart,
93
+ InfoEnd,
94
+ HintStart,
95
+ HintEnd,
96
+ TagStart,
97
+ TagEnd,
98
+ ReferenceStart,
99
+ ReferenceEnd,
100
+ TagRefStart,
101
+ TagRefEnd,
102
+ SourceMap,
103
+ } = DiagnosticCode;
86
104
  /** 分析诊断信息,{@link diagnostic_position_encoding} 不能设为 `None` */
87
105
  export function parseDiagnostics(
88
106
  source: ScriptInput,
@@ -122,17 +140,17 @@ export function parseDiagnostics(
122
140
  for (let i = 0; i < parsed.length; i++) {
123
141
  const diagnostic = parsed[i]!;
124
142
  const { code } = diagnostic;
125
- if (code > DiagnosticCode.ErrorStart && code < DiagnosticCode.ErrorEnd) {
143
+ if (code > ErrorStart && code < ErrorEnd) {
126
144
  _errors.push(diagnostic);
127
- } else if (code > DiagnosticCode.WarningStart && code < DiagnosticCode.WarningEnd) {
145
+ } else if (code > WarningStart && code < WarningEnd) {
128
146
  _warnings.push(diagnostic);
129
- } else if (code > DiagnosticCode.InfoStart && code < DiagnosticCode.InfoEnd) {
147
+ } else if (code > InfoStart && code < InfoEnd) {
130
148
  _infos.push(diagnostic);
131
- } else if (code > DiagnosticCode.HintStart && code < DiagnosticCode.HintEnd) {
149
+ } else if (code > HintStart && code < HintEnd) {
132
150
  _hints.push(diagnostic);
133
- } else if (code > DiagnosticCode.TagStart && code < DiagnosticCode.TagEnd) {
151
+ } else if (code > TagStart && code < TagEnd) {
134
152
  _tags.push(diagnostic);
135
- } else if (code === DiagnosticCode.SourceMap) {
153
+ } else if (code === SourceMap) {
136
154
  _sourcemaps.push(diagnostic.range);
137
155
  continue;
138
156
  } else {
@@ -143,11 +161,11 @@ export function parseDiagnostics(
143
161
  while (i + 1 < parsed.length) {
144
162
  const ref = parsed[i + 1]!;
145
163
  let isRef = false;
146
- if (ref.code > DiagnosticCode.TagRefStart && ref.code < DiagnosticCode.TagRefEnd) {
164
+ if (ref.code > TagRefStart && ref.code < TagRefEnd) {
147
165
  isRef = true;
148
166
  _tagsReferences.push(ref);
149
167
  }
150
- if (ref.code > DiagnosticCode.ReferenceStart && ref.code < DiagnosticCode.ReferenceEnd) {
168
+ if (ref.code > ReferenceStart && ref.code < ReferenceEnd) {
151
169
  isRef = true;
152
170
  _references.push(ref);
153
171
  }
@@ -1,13 +1,22 @@
1
+ import { isNaN } from '../helpers/utils.js';
2
+ // 不使用 performance.now(),精度够即可,且性能开销更小
3
+ const { now } = Date;
4
+ const TIME_ORIGIN = now() - 1000 * 3600 * 24; // 减去一天,防止系统时间被调整到过去时出问题
5
+ const timestamp = () => now() - TIME_ORIGIN;
6
+
1
7
  const MAX_DEPTH = 128;
8
+ const CP_UNSET = -1;
9
+ /** Default timeout in milliseconds */
10
+ const CP_DEFAULT_TIMEOUT = 100;
2
11
 
3
12
  let cpDepth = 0;
4
- let cp = Number.NaN;
5
- let cpTimeout = 100; // Default timeout in milliseconds
13
+ let cp = CP_UNSET;
14
+ let cpTimeout = CP_DEFAULT_TIMEOUT;
6
15
  /** 检查点 */
7
16
  export function Cp(): void {
8
- if (!cp) {
9
- cp = Date.now();
10
- } else if (Date.now() - cp > cpTimeout) {
17
+ if (cp === CP_UNSET) {
18
+ cp = timestamp();
19
+ } else if (timestamp() - cp > cpTimeout) {
11
20
  throw new RangeError('Execution timeout');
12
21
  }
13
22
  }
@@ -15,7 +24,7 @@ export function Cp(): void {
15
24
  export function CpEnter(): void {
16
25
  cpDepth++;
17
26
  if (cpDepth <= 1) {
18
- cp = Date.now();
27
+ cp = timestamp();
19
28
  cpDepth = 1;
20
29
  } else if (cpDepth > MAX_DEPTH) {
21
30
  throw new RangeError('Maximum call depth exceeded');
@@ -27,16 +36,16 @@ export function CpEnter(): void {
27
36
  export function CpExit(): void {
28
37
  cpDepth--;
29
38
  if (cpDepth < 1) {
30
- cp = Number.NaN;
39
+ cp = CP_UNSET;
31
40
  cpDepth = 0;
32
41
  } else {
33
42
  Cp();
34
43
  }
35
44
  }
36
45
  /** 设置检查点超时时间 */
37
- export function configCheckpoint(timeout = 100): void {
38
- if (typeof timeout !== 'number' || timeout <= 0 || Number.isNaN(timeout)) {
46
+ export function configCheckpoint(timeout = CP_DEFAULT_TIMEOUT): void {
47
+ if (typeof timeout !== 'number' || timeout <= 0 || isNaN(timeout)) {
39
48
  throw new RangeError('Invalid timeout value');
40
49
  }
41
- cpTimeout = timeout;
50
+ cpTimeout = Math.ceil(timeout);
42
51
  }
@@ -1,13 +1,87 @@
1
1
  import supportsColor from 'supports-color';
2
2
  import { VmError } from '../../../helpers/error.js';
3
- import { toString } from '../../../helpers/convert/to-string.js';
3
+ import { toString } from '../../../helpers/convert/index.js';
4
4
  import type { VmAny } from '../../types/index.js';
5
5
  import { VmLib } from '../helpers.js';
6
6
 
7
+ /** 序列化格式 */
8
+ type SerializeFormat =
9
+ /** 表示未指定格式 */
10
+ | ''
11
+ /** 以 `%` 开头的格式占位符 */
12
+ | `%${string}`;
13
+ /**
14
+ * 默认的序列化函数
15
+ * @param arg 要序列化的值
16
+ * @param format 序列化格式
17
+ * @returns 序列化后的字符串,或 null 表示直接将原值传递给控制台
18
+ */
19
+ function defaultSerializer(arg: VmAny, format: SerializeFormat): string | null {
20
+ return null;
21
+ }
22
+
23
+ /** 序列化值 */
24
+ function serializeValue(arg: VmAny, format: SerializeFormat, serializer: typeof defaultSerializer): string | null {
25
+ if (serializer == null || serializer === defaultSerializer) {
26
+ return defaultSerializer(arg, format);
27
+ }
28
+ try {
29
+ return serializer(arg, format);
30
+ } catch {
31
+ return defaultSerializer(arg, format);
32
+ }
33
+ }
34
+
7
35
  export const debug_print = VmLib(
8
36
  (...args) => {
37
+ const { serializer } = debug_print;
38
+ if (args.length <= 1 || typeof args[0] != 'string' || !args[0].includes('%')) {
39
+ // eslint-disable-next-line no-console
40
+ console.log(...debug_print.prefix, ...args.map((v) => serializeValue(v, '', serializer) ?? v));
41
+ return;
42
+ }
43
+ const [prefix, ...additional] = debug_print.prefix;
44
+ const [arg0, ...argsRest] = args;
45
+ const format = `${prefix || ''} ${arg0}`;
46
+ const values = [...additional, ...argsRest];
47
+ const parts = format.split(/(%[%\w])/g);
48
+ const messageToConsole: string[] = [];
49
+ const valuesToConsole: unknown[] = [];
50
+ let valIndex = 0;
51
+ for (let i = 0; i < parts.length; i++) {
52
+ if (i % 2 === 0) {
53
+ // Regular string part
54
+ messageToConsole.push(parts[i]!);
55
+ continue;
56
+ }
57
+ // Specifier part
58
+ const specifier = parts[i]!;
59
+ if (specifier === '%%') {
60
+ messageToConsole.push('%');
61
+ } else {
62
+ if (valIndex >= values.length) {
63
+ messageToConsole.push(specifier);
64
+ continue;
65
+ }
66
+ const arg = values[valIndex++]!;
67
+ const f = serializeValue(arg, specifier as SerializeFormat, serializer);
68
+ if (f != null) {
69
+ messageToConsole.push('%s');
70
+ valuesToConsole.push(f);
71
+ } else {
72
+ messageToConsole.push(specifier);
73
+ valuesToConsole.push(arg);
74
+ }
75
+ }
76
+ }
77
+
78
+ // Append any remaining arguments separated by spaces
79
+ if (valIndex < values.length) {
80
+ const remaining = values.slice(valIndex);
81
+ valuesToConsole.push(...remaining.map((v) => serializeValue(v, '', serializer) ?? v));
82
+ }
9
83
  // eslint-disable-next-line no-console
10
- console.log(...debug_print.prefix, ...args);
84
+ console.log(messageToConsole.join(''), ...valuesToConsole);
11
85
  },
12
86
  {
13
87
  summary: '打印调试信息到控制台',
@@ -17,7 +91,8 @@ export const debug_print = VmLib(
17
91
  examples: ['debug_print("value:", 42);'],
18
92
  },
19
93
  {
20
- prefix: ['MiraScript'] as readonly string[],
94
+ prefix: ['MiraScript'] as readonly [prefix: string, ...additional: readonly string[]],
95
+ serializer: defaultSerializer,
21
96
  },
22
97
  );
23
98
 
@@ -26,7 +101,7 @@ export const panic = VmLib(
26
101
  // eslint-disable-next-line no-console
27
102
  if (message === undefined) console.error(...panic.prefix);
28
103
  // eslint-disable-next-line no-console
29
- else console.error(...panic.prefix, message);
104
+ else console.error(...panic.prefix, serializeValue(message, '', panic.serializer));
30
105
  const mgsStr = toString(message, null);
31
106
  const error = !mgsStr ? 'panic' : 'panic: ' + mgsStr;
32
107
  throw new VmError(error, undefined);
@@ -40,14 +115,16 @@ export const panic = VmLib(
40
115
  },
41
116
  {
42
117
  prefix: ['MiraScript'] as readonly string[],
118
+ serializer: defaultSerializer,
43
119
  },
44
120
  );
45
121
 
46
122
  if (typeof location != 'undefined') {
47
- const badge = '%cMiraScript';
123
+ const badge = '%cMiraScript%c';
48
124
  const common = 'display: inline-block; padding: 1px 4px; border-radius: 3px;';
49
- debug_print.prefix = [badge, `${common} background: #007acc; color: #fff;`];
50
- panic.prefix = [badge, `${common} background: #d23d3d; color: #fff;`];
125
+ const reset = '';
126
+ debug_print.prefix = [badge, `${common} background: #007acc; color: #fff;`, reset];
127
+ panic.prefix = [badge, `${common} background: #d23d3d; color: #fff;`, reset];
51
128
  } else {
52
129
  if (supportsColor.stdout) {
53
130
  debug_print.prefix = ['\u001B[44;37m MiraScript \u001B[0m'];
@@ -1,5 +1,5 @@
1
1
  import { isVmExtern, isVmModule } from '../../types/index.js';
2
- import { required, rethrowError, VmLib } from '../helpers.js';
2
+ import { expectString, required, rethrowError, VmLib } from '../helpers.js';
3
3
  const { parse, stringify } = JSON;
4
4
 
5
5
  export const to_json = VmLib(
@@ -26,10 +26,9 @@ export const to_json = VmLib(
26
26
 
27
27
  export const from_json = VmLib(
28
28
  (json, fallback) => {
29
- required('json', json, null);
30
- if (typeof json != 'string') return json;
29
+ const j = expectString('json', json);
31
30
  try {
32
- return parse(json);
31
+ return parse(j);
33
32
  } catch (ex) {
34
33
  if (fallback !== undefined) return fallback;
35
34
  rethrowError('Invalid JSON', ex, null);
@@ -1,2 +1,34 @@
1
- const { PI, E } = Math;
2
- export { PI as '@pi', E as '@e' };
1
+ import { VmLib } from '../helpers.js';
2
+
3
+ export const PI = VmLib(Math.PI, {
4
+ summary: '圆周率',
5
+ returnsType: 'number',
6
+ });
7
+ export const E = VmLib(Math.E, {
8
+ summary: '自然对数的底数',
9
+ returnsType: 'number',
10
+ });
11
+ export const SQRT1_2 = VmLib(Math.SQRT1_2, {
12
+ summary: '1 的平方根除以 2',
13
+ returnsType: 'number',
14
+ });
15
+ export const SQRT2 = VmLib(Math.SQRT2, {
16
+ summary: '2 的平方根',
17
+ returnsType: 'number',
18
+ });
19
+ export const LN2 = VmLib(Math.LN2, {
20
+ summary: '2 的自然对数',
21
+ returnsType: 'number',
22
+ });
23
+ export const LN10 = VmLib(Math.LN10, {
24
+ summary: '10 的自然对数',
25
+ returnsType: 'number',
26
+ });
27
+ export const LOG2E = VmLib(Math.LOG2E, {
28
+ summary: 'e 以 2 为底的对数',
29
+ returnsType: 'number',
30
+ });
31
+ export const LOG10E = VmLib(Math.LOG10E, {
32
+ summary: 'e 以 10 为底的对数',
33
+ returnsType: 'number',
34
+ });
@@ -243,6 +243,7 @@ export function arrayLen(len: number | null | undefined): number {
243
243
  /** 应用映射函数 */
244
244
  export function map(
245
245
  data: VmConst,
246
+ /** 返回 `undefined` 表示跳过该元素 */
246
247
  mapper: (value: VmConst, index: number | string | null, data: VmConst) => VmConst | undefined,
247
248
  ): VmConst {
248
249
  if (isVmPrimitive(data)) {
@@ -276,19 +277,17 @@ export type VmLibOption = Pick<
276
277
  'summary' | 'params' | 'paramsType' | 'returns' | 'returnsType' | 'examples'
277
278
  >;
278
279
  /** 库函数 */
279
- export type VmLib<T extends VmFunctionLike = VmFunctionLike> = T & VmLibOption;
280
+ export type VmLib<T extends VmFunctionLike | VmConst = VmFunctionLike> = (T extends VmFunctionLike ? T : { value: T }) &
281
+ VmLibOption;
280
282
 
281
283
  /** 创建库函数 */
282
- export function VmLib<const T extends VmFunctionLike, P extends Record<string, unknown> = Record<never, never>>(
283
- fn: T,
284
- option: VmLibOption,
285
- properties?: P,
286
- ): VmLib<T> & P {
287
- /* c8 ignore next 2 */
288
- if (typeof fn != 'function') throw new TypeError('Invalid function');
289
- if (isVmFunction(fn)) throw new TypeError('Cannot create VmLib from a VmFunction');
284
+ export function VmLib<
285
+ const T extends VmFunctionLike | VmConst,
286
+ P extends Record<string, unknown> = Record<never, never>,
287
+ >(value: T, option: VmLibOption, properties?: P): VmLib<T> & P {
288
+ if (isVmFunction(value)) throw new TypeError('Cannot create VmLib from a VmFunction');
290
289
 
291
- const ret = fn as Writable<VmLib<T>> & P;
290
+ const ret = (typeof value == 'function' ? value : { value: value }) as unknown as Writable<VmLib<T>> & P;
292
291
  Object.assign(ret, properties);
293
292
  ret.params = option.params;
294
293
  ret.paramsType = option.paramsType;
@@ -1,16 +1,20 @@
1
1
  import * as global from './global/index.js';
2
2
  import * as mods from './mod/index.js';
3
- import { VM_SHARED_CONTEXT } from '../types/context.js';
3
+ import { VM_SHARED_CONTEXT, VM_SHARED_CONTEXT_DESCRIPTIONS } from '../types/context.js';
4
4
  import { create, entries } from '../../helpers/utils.js';
5
5
  import { createModule, wrapEntry, type RawValue } from './loader.js';
6
6
 
7
7
  for (const [name, value] of entries(global)) {
8
- VM_SHARED_CONTEXT[name] = wrapEntry(name, value as RawValue, 'global');
8
+ const [wrappedValue, description] = wrapEntry(name, value as RawValue, 'global');
9
+ VM_SHARED_CONTEXT[name] = wrappedValue;
10
+ VM_SHARED_CONTEXT_DESCRIPTIONS[name] = description;
9
11
  }
10
12
 
11
13
  for (const [name, value] of entries(mods)) {
12
14
  const mod = createModule(name, value as Record<string, RawValue>);
13
- VM_SHARED_CONTEXT[name] = wrapEntry(name, mod, 'global');
15
+ const [wrappedValue, description] = wrapEntry(name, mod, 'global');
16
+ VM_SHARED_CONTEXT[name] = wrappedValue;
17
+ VM_SHARED_CONTEXT_DESCRIPTIONS[name] = description;
14
18
  }
15
19
 
16
20
  export const lib: Readonly<typeof global & typeof mods> = Object.freeze(Object.assign(create(null), global, mods));
@@ -1,43 +1,80 @@
1
- import { VmFunction, VmModule, type VmConst, type VmFunctionLike, type VmImmutable } from '../types/index.js';
1
+ import {
2
+ isVmModule,
3
+ VmFunction,
4
+ VmModule,
5
+ type VmConst,
6
+ type VmFunctionLike,
7
+ type VmImmutable,
8
+ } from '../types/index.js';
2
9
  import { create, defineProperty, entries } from '../../helpers/utils.js';
3
10
 
4
11
  import type { VmLib, VmLibOption } from './helpers.js';
5
12
 
13
+ /** 内置模块 */
14
+ class VmBuiltinModule<const T extends Record<string, VmImmutable> = Record<string, VmImmutable>> extends VmModule<T> {
15
+ constructor(
16
+ name: string,
17
+ value: T,
18
+ readonly descriptions: Record<keyof T, string | undefined>,
19
+ ) {
20
+ super(name, value);
21
+ }
22
+ /** @inheritdoc */
23
+ override describe(key: string): string | undefined {
24
+ return this.descriptions[key as keyof T];
25
+ }
26
+ }
27
+
6
28
  /** 原始值 */
7
- export type RawValue = VmLib | VmConst | VmModule;
29
+ export type RawValue = VmLib<VmFunctionLike | VmConst> | VmBuiltinModule;
8
30
  /** 包装值 */
9
- type ToWrappedValue<V extends RawValue> = V extends VmFunctionLike ? VmFunction<V> : V;
31
+ type ToWrappedValue<V extends RawValue> = V extends VmLib<infer C> ? (C extends VmFunctionLike ? VmFunction<C> : C) : V;
10
32
  /** 包装值 */
11
- export function wrapEntry<const T extends RawValue>(name: string, value: T, module: string): ToWrappedValue<T> {
12
- if (typeof value == 'function') {
13
- if (value.name !== name) {
14
- // 如果函数名和导出名不一致,则重命名
15
- defineProperty(value, 'name', {
16
- value: name,
17
- configurable: true,
18
- });
33
+ export function wrapEntry<const T extends RawValue>(
34
+ name: string,
35
+ value: T,
36
+ module: string,
37
+ ): [value: ToWrappedValue<T>, description: string | undefined] {
38
+ if (isVmModule(value)) {
39
+ return [value as ToWrappedValue<T>, undefined];
40
+ }
41
+ if (typeof value != 'function') {
42
+ if (value == null || typeof value != 'object' || !('value' in value)) {
43
+ throw new TypeError(`Cannot wrap non-function, non-const value: ${name} in module ${module}`);
19
44
  }
20
- return VmFunction(value, {
45
+ return [value.value as ToWrappedValue<T>, value.summary || undefined];
46
+ }
47
+ if (value.name !== name) {
48
+ // 如果函数名和导出名不一致,则重命名
49
+ defineProperty(value, 'name', {
50
+ value: name,
51
+ configurable: true,
52
+ });
53
+ }
54
+ return [
55
+ VmFunction(value, {
21
56
  isLib: true,
22
57
  injectCp: true,
23
58
  fullName: `${module}.${name}`,
24
59
  ...(value as VmLibOption),
25
- }) as ToWrappedValue<T>;
26
- } else {
27
- return value as ToWrappedValue<T>;
28
- }
60
+ }) as ToWrappedValue<T>,
61
+ value.summary || undefined,
62
+ ];
29
63
  }
30
64
 
31
65
  /** 创建模块 */
32
- export type ToWrappedModule<T extends Record<string, RawValue>> = VmModule<{
66
+ export type ToWrappedModule<T extends Record<string, RawValue>> = VmBuiltinModule<{
33
67
  [key in keyof T]: ToWrappedValue<T[key]>;
34
68
  }>;
35
69
 
36
70
  /** 创建模块 */
37
71
  export function createModule<const T extends Record<string, RawValue>>(name: string, lib: T): ToWrappedModule<T> {
38
72
  const mod = create(null) as Record<string, VmImmutable>;
73
+ const descriptions = create(null) as Record<string, string | undefined>;
39
74
  for (const [key, value] of entries(lib)) {
40
- mod[key] = wrapEntry(key, value, name);
75
+ const [wrappedValue, description] = wrapEntry(key, value, name);
76
+ mod[key] = wrappedValue;
77
+ descriptions[key] = description;
41
78
  }
42
- return new VmModule(name, mod) as ToWrappedModule<T>;
79
+ return new VmBuiltinModule(name, mod, descriptions) as ToWrappedModule<T>;
43
80
  }
@@ -1,4 +1,4 @@
1
- import { create, entries, keys } from '../../helpers/utils.js';
1
+ import { create, entries, hasOwn, keys } from '../../helpers/utils.js';
2
2
  import { isVmAny } from '../../helpers/types.js';
3
3
  import { VmError } from '../../helpers/error.js';
4
4
  import { kVmContext } from '../../helpers/constants.js';
@@ -14,7 +14,7 @@ export interface VmContext {
14
14
  /** 枚举所有 key,仅在 LSP 中使用 */
15
15
  keys(): readonly string[];
16
16
  /** 描述值,返回 MarkDown 文本,仅在 LSP 中使用 */
17
- describe?(key: string): string | undefined;
17
+ describe(key: string): string | undefined;
18
18
  /**
19
19
  * 获取指定 key 的值 `global[key]`
20
20
  * @throws {VmError} 如果值不存在则抛出异常
@@ -28,6 +28,7 @@ export type VmContextRecord = Record<string, VmValue>;
28
28
  /** MiraScript 执行上下文 */
29
29
  export type VmContextRecordLoose = Record<string, VmValue | undefined>;
30
30
  export const VM_SHARED_CONTEXT: Record<string, VmImmutable> = create(null);
31
+ export const VM_SHARED_CONTEXT_DESCRIPTIONS: Record<string, string | undefined> = create(null);
31
32
  /** 缓存 {@link VM_SHARED_CONTEXT} 的 keys */
32
33
  let VM_SHARED_CONTEXT_KEYS: readonly string[] | null = null;
33
34
 
@@ -41,6 +42,7 @@ export function defineVmContextValue(
41
42
  name: string,
42
43
  value: VmImmutable | ((...args: VmAny[]) => VmAny),
43
44
  override = false,
45
+ description: string | null | undefined = undefined,
44
46
  ): void {
45
47
  if (!override && name in VM_SHARED_CONTEXT) throw new Error(`Global variable '${name}' is already defined.`);
46
48
  let v: VmImmutable;
@@ -53,6 +55,7 @@ export function defineVmContextValue(
53
55
  v = value;
54
56
  }
55
57
  VM_SHARED_CONTEXT[name] = v ?? null;
58
+ if (description) VM_SHARED_CONTEXT_DESCRIPTIONS[name] = description;
56
59
  VM_SHARED_CONTEXT_KEYS = null;
57
60
  }
58
61
 
@@ -74,6 +77,10 @@ export const DefaultVmContext: VmContext = freeze({
74
77
  has(key: string): boolean {
75
78
  return key in VM_SHARED_CONTEXT;
76
79
  },
80
+ /** @inheritdoc */
81
+ describe(key: string): string | undefined {
82
+ return VM_SHARED_CONTEXT_DESCRIPTIONS[key];
83
+ },
77
84
  });
78
85
 
79
86
  /** 以值为后备的实现 */
@@ -99,10 +106,14 @@ class ValueVmContext implements VmContext {
99
106
  has(key: string): boolean {
100
107
  return key in this.env;
101
108
  }
109
+ /** @inheritdoc */
110
+ describe(key: string): string | undefined {
111
+ if (this.describer != null && hasOwn(this.env, key)) return this.describer(key);
112
+ return VM_SHARED_CONTEXT_DESCRIPTIONS[key];
113
+ }
102
114
  constructor(
103
115
  private readonly env: VmContextRecord,
104
- /** @inheritdoc */
105
- readonly describe?: (key: string) => string | undefined,
116
+ private readonly describer?: (key: string) => string | undefined,
106
117
  ) {}
107
118
  }
108
119
 
@@ -124,11 +135,15 @@ class FactoryVmContext implements VmContext {
124
135
  has(key: string): boolean {
125
136
  return this.getter(key) !== undefined || DefaultVmContext.has(key);
126
137
  }
138
+ /** @inheritdoc */
139
+ describe(key: string): string | undefined {
140
+ if (this.describer != null && this.getter(key) !== undefined) return this.describer(key);
141
+ return VM_SHARED_CONTEXT_DESCRIPTIONS[key];
142
+ }
127
143
  constructor(
128
144
  private readonly getter: (key: string) => VmValue | undefined,
129
145
  private readonly enumerator?: () => Iterable<string>,
130
- /** @inheritdoc */
131
- readonly describe?: (key: string) => string | undefined,
146
+ private readonly describer?: (key: string) => string | undefined,
132
147
  ) {}
133
148
  }
134
149
 
@@ -115,7 +115,7 @@ export class VmExtern<const T extends object = object> extends VmWrapper<T> {
115
115
  return 'extern';
116
116
  }
117
117
  /** @inheritdoc */
118
- override get describe(): string {
118
+ override get tag(): string {
119
119
  const tag = ObjectToString.call(this.value).slice(8, -1);
120
120
  if (isArray(this.value)) {
121
121
  return `${tag}(${this.value.length})`;
@@ -35,7 +35,7 @@ export class VmModule<const T extends Record<string, VmImmutable> = Record<strin
35
35
  return 'module';
36
36
  }
37
37
  /** @inheritdoc */
38
- override get describe(): string {
38
+ override get tag(): string {
39
39
  return this.name;
40
40
  }
41
41
  }
@@ -16,15 +16,19 @@ export abstract class VmWrapper<T extends object> {
16
16
  abstract same(other: VmAny): boolean;
17
17
  /** 获取类型 */
18
18
  abstract get type(): TypeName;
19
- /** 获取描述 */
20
- abstract get describe(): string;
19
+ /** 获取当前对象的描述 */
20
+ abstract get tag(): string;
21
+ /** 描述键对应的值 */
22
+ describe(key: string): string | undefined {
23
+ return undefined;
24
+ }
21
25
  /** Convert the object to JSON */
22
26
  toJSON(): undefined {
23
27
  return undefined;
24
28
  }
25
29
  /** 转为字符串 */
26
30
  toString(useBraces: boolean): string {
27
- const { type, describe } = this;
31
+ const { type, tag: describe } = this;
28
32
  if (!describe) return `<${type}>`;
29
33
  return `<${type} ${describe}>`;
30
34
  }