@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.
- package/dist/{chunk-3RUWGMBP.js → chunk-JB6LPPFJ.js} +190 -24
- package/dist/chunk-JB6LPPFJ.js.map +6 -0
- package/dist/{chunk-AOINGBRS.js → chunk-RYSPVMVZ.js} +189 -191
- package/dist/chunk-RYSPVMVZ.js.map +6 -0
- package/dist/cli/execute.d.ts +1 -1
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/index.js +42 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/print.d.ts.map +1 -1
- package/dist/compiler/diagnostic.d.ts +14 -3
- package/dist/compiler/diagnostic.d.ts.map +1 -1
- package/dist/compiler/emit.d.ts +2 -1
- package/dist/compiler/emit.d.ts.map +1 -1
- package/dist/compiler/index.d.ts.map +1 -1
- package/dist/compiler/worker.d.ts.map +1 -1
- package/dist/compiler/worker.js +6 -3
- package/dist/compiler/worker.js.map +1 -1
- package/dist/helpers/serialize.d.ts +10 -2
- package/dist/helpers/serialize.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/subtle.d.ts +1 -1
- package/dist/subtle.d.ts.map +1 -1
- package/dist/subtle.js +17 -7
- package/dist/vm/lib/_helpers.d.ts.map +1 -1
- package/dist/vm/lib/global/sequence/with.d.ts +2 -2
- package/dist/vm/lib/global/sequence/with.d.ts.map +1 -1
- package/dist/vm/operations.d.ts.map +1 -1
- package/dist/vm/types/boundary.d.ts.map +1 -1
- package/dist/vm/types/checker.d.ts +1 -1
- package/dist/vm/types/checker.d.ts.map +1 -1
- package/dist/vm/types/context.d.ts +14 -6
- package/dist/vm/types/context.d.ts.map +1 -1
- package/dist/vm/types/extern.d.ts +2 -0
- package/dist/vm/types/extern.d.ts.map +1 -1
- package/dist/vm/types/index.d.ts +6 -10
- package/dist/vm/types/index.d.ts.map +1 -1
- package/dist/vm/types/module.d.ts +2 -0
- package/dist/vm/types/module.d.ts.map +1 -1
- package/dist/vm/types/wrapper.d.ts +2 -0
- package/dist/vm/types/wrapper.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/cli/execute.ts +8 -3
- package/src/cli/index.ts +22 -5
- package/src/cli/print.ts +13 -9
- package/src/compiler/diagnostic.ts +32 -14
- package/src/compiler/emit.ts +45 -19
- package/src/compiler/index.ts +10 -2
- package/src/compiler/worker.ts +5 -1
- package/src/helpers/serialize.ts +6 -6
- package/src/subtle.ts +11 -1
- package/src/vm/lib/_helpers.ts +2 -10
- package/src/vm/lib/global/math-arr.ts +1 -1
- package/src/vm/lib/global/sequence/with.ts +111 -17
- package/src/vm/operations.ts +23 -24
- package/src/vm/types/boundary.ts +4 -4
- package/src/vm/types/checker.ts +8 -15
- package/src/vm/types/context.ts +28 -16
- package/src/vm/types/extern.ts +11 -2
- package/src/vm/types/index.ts +7 -26
- package/src/vm/types/module.ts +7 -0
- package/src/vm/types/wrapper.ts +10 -1
- package/dist/chunk-3RUWGMBP.js.map +0 -6
- package/dist/chunk-AOINGBRS.js.map +0 -6
package/src/compiler/worker.ts
CHANGED
|
@@ -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
|
|
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
|
|
package/src/helpers/serialize.ts
CHANGED
|
@@ -67,7 +67,7 @@ const DEFAULT_OPTIONS = Object.freeze({
|
|
|
67
67
|
} satisfies SerializeOptions);
|
|
68
68
|
|
|
69
69
|
/** 获取选项 */
|
|
70
|
-
|
|
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 {
|
|
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';
|
package/src/vm/lib/_helpers.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 {
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
11
|
-
|
|
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 (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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] =
|
|
99
|
+
result[index] = val;
|
|
23
100
|
}
|
|
24
101
|
return result;
|
|
25
102
|
} else {
|
|
26
103
|
const result: Record<string, VmConst | undefined> = { ...data };
|
|
27
|
-
for (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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: {
|
|
38
|
-
|
|
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: [
|
|
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' };
|
package/src/vm/operations.ts
CHANGED
|
@@ -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
|
|
37
|
-
if (b
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
299
|
-
if (value
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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]);
|
package/src/vm/types/boundary.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isVmFunction, type VmFunctionLike, type VmFunction } from './function.js';
|
|
2
|
-
import { VmExtern } from './extern.js';
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
83
|
+
if (!isVmExtern(value)) return value;
|
|
84
84
|
|
|
85
85
|
if (value.thisArg == null || typeof value.value != 'function') {
|
|
86
86
|
return value.value;
|
package/src/vm/types/checker.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { getPrototypeOf, isArray, values } from '../../helpers/utils.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
148
|
+
if (isVmWrapper(value)) return true;
|
|
156
149
|
return isVmConst(value, checkDeep);
|
|
157
150
|
case 'function':
|
|
158
151
|
return isVmFunction(value);
|
package/src/vm/types/context.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
|
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
|
/** 检查是否为执行上下文 */
|
package/src/vm/types/extern.ts
CHANGED
|
@@ -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
|
|
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
|
+
}
|