@mirascript/mirascript 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-AOINGBRS.js → chunk-MVHCSH3E.js} +154 -65
- package/dist/chunk-MVHCSH3E.js.map +6 -0
- package/dist/cli/index.js +3 -3
- package/dist/compiler/diagnostic.d.ts +12 -3
- package/dist/compiler/diagnostic.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/subtle.js +1 -1
- 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 +4 -4
- package/src/compiler/diagnostic.ts +14 -12
- package/src/vm/lib/_helpers.ts +2 -10
- 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 +7 -0
- package/dist/chunk-AOINGBRS.js.map +0 -6
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
|
}
|
|
@@ -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
|
+
}
|
package/src/vm/types/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { isArray } from '../../helpers/utils.js';
|
|
2
|
-
import { VmExtern } from './extern.js';
|
|
3
|
-
import { isVmFunction,
|
|
4
|
-
import { VmModule } from './module.js';
|
|
5
|
-
import {
|
|
2
|
+
import { isVmExtern, VmExtern } from './extern.js';
|
|
3
|
+
import { isVmFunction, VmFunction } from './function.js';
|
|
4
|
+
import { VmModule, isVmModule } from './module.js';
|
|
5
|
+
import { isVmWrapper } from './wrapper.js';
|
|
6
6
|
|
|
7
7
|
/** Mirascript 原始值 */
|
|
8
8
|
export type VmPrimitive = null | string | number | boolean;
|
|
@@ -74,7 +74,7 @@ export function isVmArray(value: VmAny): value is VmArray {
|
|
|
74
74
|
*/
|
|
75
75
|
export function isVmRecord(value: VmAny): value is VmRecord {
|
|
76
76
|
if (value == null || typeof value !== 'object') return false;
|
|
77
|
-
if (value
|
|
77
|
+
if (isVmWrapper(value)) return false;
|
|
78
78
|
if (isVmArray(value)) return false;
|
|
79
79
|
value satisfies VmRecord;
|
|
80
80
|
return true;
|
|
@@ -92,35 +92,16 @@ export function isVmPrimitive(value: unknown): value is VmPrimitive {
|
|
|
92
92
|
return false;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
export { VmExtern
|
|
95
|
+
export { VmFunction, isVmFunction, VmExtern, isVmExtern, VmModule, isVmModule, isVmWrapper };
|
|
96
96
|
export { wrapToVmValue, unwrapFromVmValue, toVmFunctionProxy, fromVmFunctionProxy } from './boundary.js';
|
|
97
97
|
|
|
98
|
-
/** 检查值是否为 Mirascript 外部值 */
|
|
99
|
-
export function isVmExtern(value: unknown): value is VmExtern {
|
|
100
|
-
return value instanceof VmExtern;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
98
|
/** 检查值是否为 Mirascript 可调用值 */
|
|
104
99
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
105
100
|
export function isVmCallable(value: unknown): value is VmFunction | VmExtern<Function> {
|
|
106
101
|
return isVmFunction(value) || (isVmExtern(value) && typeof value.value == 'function');
|
|
107
102
|
}
|
|
108
103
|
|
|
109
|
-
export {
|
|
110
|
-
VmFunction,
|
|
111
|
-
isVmFunction,
|
|
112
|
-
getVmFunctionInfo,
|
|
113
|
-
type VmFunctionInfo,
|
|
114
|
-
type VmFunctionLike,
|
|
115
|
-
type VmFunctionOption,
|
|
116
|
-
} from './function.js';
|
|
117
|
-
|
|
118
|
-
export { VmModule } from './module.js';
|
|
119
|
-
|
|
120
|
-
/** 检查值是否为 Mirascript 模块 */
|
|
121
|
-
export function isVmModule(value: unknown): value is VmModule {
|
|
122
|
-
return value instanceof VmModule;
|
|
123
|
-
}
|
|
104
|
+
export { getVmFunctionInfo, type VmFunctionInfo, type VmFunctionLike, type VmFunctionOption } from './function.js';
|
|
124
105
|
|
|
125
106
|
export { type VmContext, type VmSharedContext, isVmContext, defineVmContextValue, createVmContext } from './context.js';
|
|
126
107
|
|
package/src/vm/types/module.ts
CHANGED
|
@@ -38,3 +38,10 @@ export class VmModule<const T extends Record<string, VmValue> = Record<string, V
|
|
|
38
38
|
return this.name;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
const kVmModule = Symbol.for('mirascript.vm.module');
|
|
43
|
+
Object.defineProperty(VmModule.prototype, kVmModule, { value: true });
|
|
44
|
+
/** 检查值是否为 Mirascript 模块 */
|
|
45
|
+
export function isVmModule<T extends Record<string, VmValue>>(value: unknown): value is VmModule<T> {
|
|
46
|
+
return value != null && typeof value == 'object' && kVmModule in value;
|
|
47
|
+
}
|
package/src/vm/types/wrapper.ts
CHANGED
|
@@ -26,3 +26,10 @@ export abstract class VmWrapper<T extends object> {
|
|
|
26
26
|
return `<${this.type} ${this.describe}>`;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
const kVmWrapper = Symbol.for('mirascript.vm.wrapper');
|
|
31
|
+
Object.defineProperty(VmWrapper.prototype, kVmWrapper, { value: true });
|
|
32
|
+
/** 检查值是否为 MiraScript 包装器 */
|
|
33
|
+
export function isVmWrapper<T extends object>(value: unknown): value is VmWrapper<T> {
|
|
34
|
+
return value != null && typeof value == 'object' && kVmWrapper in value;
|
|
35
|
+
}
|