@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.
- package/dist/{chunk-JG3D67GF.js → chunk-5AEWM4W6.js} +30 -12
- package/dist/{chunk-JG3D67GF.js.map → chunk-5AEWM4W6.js.map} +1 -1
- package/dist/{chunk-W2I5XPIE.js → chunk-BFSB6SQK.js} +204 -61
- package/dist/chunk-BFSB6SQK.js.map +6 -0
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/index.js +11 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/compiler/diagnostic.d.ts.map +1 -1
- package/dist/compiler/worker.js +1 -1
- package/dist/index.js +2 -2
- package/dist/subtle.js +2 -2
- package/dist/vm/checkpoint.d.ts.map +1 -1
- package/dist/vm/lib/global/debug.d.ts +17 -1
- package/dist/vm/lib/global/debug.d.ts.map +1 -1
- package/dist/vm/lib/global/json.d.ts.map +1 -1
- package/dist/vm/lib/global/math-const.d.ts +24 -2
- package/dist/vm/lib/global/math-const.d.ts.map +1 -1
- package/dist/vm/lib/helpers.d.ts +7 -3
- package/dist/vm/lib/helpers.d.ts.map +1 -1
- package/dist/vm/lib/index.d.ts.map +1 -1
- package/dist/vm/lib/loader.d.ts +12 -5
- package/dist/vm/lib/loader.d.ts.map +1 -1
- package/dist/vm/types/context.d.ts +3 -2
- package/dist/vm/types/context.d.ts.map +1 -1
- package/dist/vm/types/extern.d.ts +1 -1
- package/dist/vm/types/extern.d.ts.map +1 -1
- package/dist/vm/types/module.d.ts +1 -1
- package/dist/vm/types/module.d.ts.map +1 -1
- package/dist/vm/types/wrapper.d.ts +4 -2
- package/dist/vm/types/wrapper.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/cli/execute.ts +10 -10
- package/src/compiler/diagnostic.ts +29 -11
- package/src/vm/checkpoint.ts +19 -10
- package/src/vm/lib/global/debug.ts +84 -7
- package/src/vm/lib/global/json.ts +3 -4
- package/src/vm/lib/global/math-const.ts +34 -2
- package/src/vm/lib/helpers.ts +9 -10
- package/src/vm/lib/index.ts +7 -3
- package/src/vm/lib/loader.ts +56 -19
- package/src/vm/types/context.ts +21 -6
- package/src/vm/types/extern.ts +1 -1
- package/src/vm/types/module.ts +1 -1
- package/src/vm/types/wrapper.ts +7 -3
- package/dist/chunk-W2I5XPIE.js.map +0 -6
package/src/cli/execute.ts
CHANGED
|
@@ -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,
|
|
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 (
|
|
11
|
+
if (code < 0 || code >= 0xffff || !isSafeInteger(code)) {
|
|
12
12
|
throw new RangeError(`Invalid DiagnosticCode: ${code}`);
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
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 >
|
|
143
|
+
if (code > ErrorStart && code < ErrorEnd) {
|
|
126
144
|
_errors.push(diagnostic);
|
|
127
|
-
} else if (code >
|
|
145
|
+
} else if (code > WarningStart && code < WarningEnd) {
|
|
128
146
|
_warnings.push(diagnostic);
|
|
129
|
-
} else if (code >
|
|
147
|
+
} else if (code > InfoStart && code < InfoEnd) {
|
|
130
148
|
_infos.push(diagnostic);
|
|
131
|
-
} else if (code >
|
|
149
|
+
} else if (code > HintStart && code < HintEnd) {
|
|
132
150
|
_hints.push(diagnostic);
|
|
133
|
-
} else if (code >
|
|
151
|
+
} else if (code > TagStart && code < TagEnd) {
|
|
134
152
|
_tags.push(diagnostic);
|
|
135
|
-
} else if (code ===
|
|
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 >
|
|
164
|
+
if (ref.code > TagRefStart && ref.code < TagRefEnd) {
|
|
147
165
|
isRef = true;
|
|
148
166
|
_tagsReferences.push(ref);
|
|
149
167
|
}
|
|
150
|
-
if (ref.code >
|
|
168
|
+
if (ref.code > ReferenceStart && ref.code < ReferenceEnd) {
|
|
151
169
|
isRef = true;
|
|
152
170
|
_references.push(ref);
|
|
153
171
|
}
|
package/src/vm/checkpoint.ts
CHANGED
|
@@ -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 =
|
|
5
|
-
let cpTimeout =
|
|
13
|
+
let cp = CP_UNSET;
|
|
14
|
+
let cpTimeout = CP_DEFAULT_TIMEOUT;
|
|
6
15
|
/** 检查点 */
|
|
7
16
|
export function Cp(): void {
|
|
8
|
-
if (
|
|
9
|
-
cp =
|
|
10
|
-
} else if (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
38
|
-
if (typeof timeout !== 'number' || timeout <= 0 ||
|
|
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/
|
|
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(
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
30
|
-
if (typeof json != 'string') return json;
|
|
29
|
+
const j = expectString('json', json);
|
|
31
30
|
try {
|
|
32
|
-
return parse(
|
|
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
|
-
|
|
2
|
-
|
|
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
|
+
});
|
package/src/vm/lib/helpers.ts
CHANGED
|
@@ -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 &
|
|
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<
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
)
|
|
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 =
|
|
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;
|
package/src/vm/lib/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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));
|
package/src/vm/lib/loader.ts
CHANGED
|
@@ -1,43 +1,80 @@
|
|
|
1
|
-
import {
|
|
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 |
|
|
29
|
+
export type RawValue = VmLib<VmFunctionLike | VmConst> | VmBuiltinModule;
|
|
8
30
|
/** 包装值 */
|
|
9
|
-
type ToWrappedValue<V extends RawValue> = V extends VmFunctionLike ? VmFunction<
|
|
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>(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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
|
-
|
|
27
|
-
|
|
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>> =
|
|
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
|
-
|
|
75
|
+
const [wrappedValue, description] = wrapEntry(key, value, name);
|
|
76
|
+
mod[key] = wrappedValue;
|
|
77
|
+
descriptions[key] = description;
|
|
41
78
|
}
|
|
42
|
-
return new
|
|
79
|
+
return new VmBuiltinModule(name, mod, descriptions) as ToWrappedModule<T>;
|
|
43
80
|
}
|
package/src/vm/types/context.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
readonly describe?: (key: string) => string | undefined,
|
|
146
|
+
private readonly describer?: (key: string) => string | undefined,
|
|
132
147
|
) {}
|
|
133
148
|
}
|
|
134
149
|
|
package/src/vm/types/extern.ts
CHANGED
|
@@ -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
|
|
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})`;
|
package/src/vm/types/module.ts
CHANGED
package/src/vm/types/wrapper.ts
CHANGED
|
@@ -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
|
|
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
|
}
|