@mirascript/mirascript 0.1.21 → 0.1.23
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/bench/index.ts +45 -0
- package/dist/{chunk-RY7PYMXX.js → chunk-CHS52AEC.js} +35 -9
- package/dist/chunk-CHS52AEC.js.map +6 -0
- package/dist/{chunk-4T5YE7LC.js → chunk-K7JDJG3G.js} +26 -31
- package/dist/chunk-K7JDJG3G.js.map +6 -0
- package/dist/chunk-X6LYEGOK.js +69 -0
- package/dist/chunk-X6LYEGOK.js.map +6 -0
- package/dist/{chunk-XNGYORPT.js → chunk-XWQWKD67.js} +581 -531
- package/dist/chunk-XWQWKD67.js.map +6 -0
- package/dist/cli/index.js +4 -4
- package/dist/compiler/compile-fast.d.ts.map +1 -1
- package/dist/compiler/create-script.d.ts +6 -2
- package/dist/compiler/create-script.d.ts.map +1 -1
- package/dist/compiler/emit/constants.d.ts +1 -1
- package/dist/compiler/emit/constants.d.ts.map +1 -1
- package/dist/compiler/emit/index.d.ts.map +1 -1
- package/dist/compiler/worker.js +1 -1
- package/dist/helpers/analyze.d.ts +8 -0
- package/dist/helpers/analyze.d.ts.map +1 -0
- package/dist/index.js +3 -3
- package/dist/subtle.d.ts +3 -2
- package/dist/subtle.d.ts.map +1 -1
- package/dist/subtle.js +12 -7
- package/dist/vm/operations/call.d.ts +3 -0
- package/dist/vm/operations/call.d.ts.map +1 -0
- package/dist/vm/operations/common.d.ts +3 -0
- package/dist/vm/operations/common.d.ts.map +1 -0
- package/dist/vm/operations/compound.d.ts +12 -0
- package/dist/vm/operations/compound.d.ts.map +1 -0
- package/dist/vm/operations/convert.d.ts +6 -0
- package/dist/vm/operations/convert.d.ts.map +1 -0
- package/dist/vm/operations/cp.d.ts +2 -0
- package/dist/vm/operations/cp.d.ts.map +1 -0
- package/dist/vm/operations/helpers.d.ts +19 -0
- package/dist/vm/operations/helpers.d.ts.map +1 -0
- package/dist/vm/operations/index.d.ts +10 -0
- package/dist/vm/operations/index.d.ts.map +1 -0
- package/dist/vm/operations/operator.d.ts +24 -0
- package/dist/vm/operations/operator.d.ts.map +1 -0
- package/dist/vm/operations/slice.d.ts +4 -0
- package/dist/vm/operations/slice.d.ts.map +1 -0
- package/dist/vm/operations/type-check.d.ts +9 -0
- package/dist/vm/operations/type-check.d.ts.map +1 -0
- package/dist/vm/operations/utils.d.ts +11 -0
- package/dist/vm/operations/utils.d.ts.map +1 -0
- package/dist/vm/types/boundary.d.ts +2 -3
- package/dist/vm/types/boundary.d.ts.map +1 -1
- package/dist/vm/types/context.d.ts +2 -4
- package/dist/vm/types/context.d.ts.map +1 -1
- package/dist/vm/types/extern.d.ts +2 -2
- package/dist/vm/types/extern.d.ts.map +1 -1
- package/dist/vm/types/index.d.ts +1 -1
- package/dist/vm/types/index.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/compiler/compile-fast.ts +32 -12
- package/src/compiler/create-script.ts +19 -5
- package/src/compiler/emit/constants.ts +1 -1
- package/src/compiler/emit/index.ts +18 -25
- package/src/compiler/emit/sourcemap.ts +8 -8
- package/src/helpers/analyze.ts +70 -0
- package/src/subtle.ts +3 -2
- package/src/vm/lib/global/math.ts +2 -2
- package/src/vm/lib/global/sequence/all-any.ts +2 -2
- package/src/vm/lib/global/sequence/find.ts +2 -2
- package/src/vm/lib/global/sequence/map-filter.ts +1 -1
- package/src/vm/lib/global/sequence/sort.ts +1 -1
- package/src/vm/lib/global/sequence/with.ts +2 -2
- package/src/vm/lib/global/sequence/zip.ts +1 -1
- package/src/vm/lib/helpers.ts +1 -1
- package/src/vm/lib/mod/matrix.ts +2 -2
- package/src/vm/operations/call.ts +18 -0
- package/src/vm/operations/common.ts +6 -0
- package/src/vm/operations/compound.ts +148 -0
- package/src/vm/operations/convert.ts +24 -0
- package/src/vm/operations/cp.ts +1 -0
- package/src/vm/{helpers.ts → operations/helpers.ts} +17 -18
- package/src/vm/operations/index.ts +9 -0
- package/src/vm/operations/operator.ts +131 -0
- package/src/vm/operations/slice.ts +41 -0
- package/src/vm/operations/type-check.ts +38 -0
- package/src/vm/operations/utils.ts +66 -0
- package/src/vm/types/boundary.ts +18 -13
- package/src/vm/types/context.ts +72 -38
- package/src/vm/types/extern.ts +5 -4
- package/src/vm/types/index.ts +1 -1
- package/dist/chunk-4T5YE7LC.js.map +0 -6
- package/dist/chunk-RNLB52WP.js +0 -1
- package/dist/chunk-RNLB52WP.js.map +0 -6
- package/dist/chunk-RY7PYMXX.js.map +0 -6
- package/dist/chunk-XNGYORPT.js.map +0 -6
- package/dist/vm/env.d.ts +0 -3
- package/dist/vm/env.d.ts.map +0 -1
- package/dist/vm/helpers.d.ts +0 -20
- package/dist/vm/helpers.d.ts.map +0 -1
- package/dist/vm/operations.d.ts +0 -49
- package/dist/vm/operations.d.ts.map +0 -1
- package/src/vm/env.ts +0 -16
- package/src/vm/operations.ts +0 -412
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { VmError } from '../../helpers/error.js';
|
|
2
|
+
import { hasOwnEnumerable, isNaN, keys, create } from '../../helpers/utils.js';
|
|
3
|
+
import { toString } from '../../helpers/convert/index.js';
|
|
4
|
+
import { display } from '../../helpers/serialize.js';
|
|
5
|
+
import { isVmPrimitive, isVmArray, isVmRecord, isVmFunction, isVmExtern, isVmWrapper } from '../../helpers/types.js';
|
|
6
|
+
import type { VmAny, VmImmutable, VmRecord, VmValue, VmConst } from '../types/index.js';
|
|
7
|
+
import { isSame } from './utils.js';
|
|
8
|
+
import { $AssertInit } from './common.js';
|
|
9
|
+
import { $ToNumber, $ToString } from './convert.js';
|
|
10
|
+
|
|
11
|
+
const { trunc } = Math;
|
|
12
|
+
const { at } = Array.prototype;
|
|
13
|
+
|
|
14
|
+
export const $In = (value: VmAny, iterable: VmAny): boolean => {
|
|
15
|
+
$AssertInit(value);
|
|
16
|
+
$AssertInit(iterable);
|
|
17
|
+
if (iterable == null) return false;
|
|
18
|
+
if (typeof iterable != 'object') return false;
|
|
19
|
+
if (isVmArray(iterable)) {
|
|
20
|
+
if (value == null) {
|
|
21
|
+
// array may have empty slots
|
|
22
|
+
for (const item of iterable) if (item == null) return true;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
// JS %SameValueZero is same with `isSame` in this context
|
|
26
|
+
if (isVmPrimitive(value)) return iterable.includes(value);
|
|
27
|
+
// value is not null here, so it's ok to skip empty slots, since `isSame(null, something)` is always false
|
|
28
|
+
return iterable.some((item = null) => isSame(item, value satisfies NonNullable<VmValue>));
|
|
29
|
+
}
|
|
30
|
+
// iterable is a record or an extern here, value should be a string
|
|
31
|
+
const key = toString(value, undefined);
|
|
32
|
+
if (isVmWrapper(iterable)) return iterable.has(key);
|
|
33
|
+
return hasOwnEnumerable(iterable satisfies VmRecord, key);
|
|
34
|
+
};
|
|
35
|
+
export const $Length = (value: VmAny): number => {
|
|
36
|
+
$AssertInit(value);
|
|
37
|
+
if (isVmArray(value)) return value.length;
|
|
38
|
+
if (isVmRecord(value)) return keys(value).length;
|
|
39
|
+
if (isVmWrapper(value)) return value.keys().length;
|
|
40
|
+
|
|
41
|
+
throw new VmError(`Value has no length: ${display(value)}`, 0);
|
|
42
|
+
};
|
|
43
|
+
export const $Omit = (value: VmAny, omitted: ReadonlyArray<number | string>): VmRecord => {
|
|
44
|
+
$AssertInit(value);
|
|
45
|
+
if (value == null || !isVmRecord(value)) return {};
|
|
46
|
+
const result: Record<string, VmConst> = {};
|
|
47
|
+
const valueKeys = keys(value);
|
|
48
|
+
const omittedSet = new Set(omitted.map($ToString));
|
|
49
|
+
for (const key of valueKeys) {
|
|
50
|
+
if (!omittedSet.has(key)) {
|
|
51
|
+
/* c8 ignore next */
|
|
52
|
+
result[key] = value[key] ?? null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
export const $Pick = (value: VmAny, picked: ReadonlyArray<number | string>): VmRecord => {
|
|
58
|
+
$AssertInit(value);
|
|
59
|
+
if (value == null || !isVmRecord(value)) return {};
|
|
60
|
+
const result: Record<string, VmConst> = {};
|
|
61
|
+
for (const key of picked) {
|
|
62
|
+
const k = $ToString(key);
|
|
63
|
+
if (hasOwnEnumerable(value, k)) {
|
|
64
|
+
result[k] = value[k] ?? null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
};
|
|
69
|
+
export const $Has = (obj: VmAny, key: VmAny): boolean => {
|
|
70
|
+
$AssertInit(obj);
|
|
71
|
+
const pk = $ToString(key);
|
|
72
|
+
if (obj == null || typeof obj != 'object') return false;
|
|
73
|
+
if (isVmWrapper(obj)) return obj.has(pk);
|
|
74
|
+
return hasOwnEnumerable(obj, pk);
|
|
75
|
+
};
|
|
76
|
+
export const $Get = (obj: VmAny, key: VmAny): VmValue => {
|
|
77
|
+
$AssertInit(obj);
|
|
78
|
+
if (isVmArray(obj)) {
|
|
79
|
+
const index = $ToNumber(key);
|
|
80
|
+
if (isNaN(index)) return null;
|
|
81
|
+
return (at.call(obj, trunc(index)) as VmConst | undefined) ?? null;
|
|
82
|
+
}
|
|
83
|
+
const pk = $ToString(key);
|
|
84
|
+
if (obj == null || typeof obj != 'object') return null;
|
|
85
|
+
if (isVmWrapper(obj)) return obj.get(pk) ?? null;
|
|
86
|
+
if (!hasOwnEnumerable(obj, pk)) return null;
|
|
87
|
+
return (obj as Record<string, VmImmutable>)[pk] ?? null;
|
|
88
|
+
};
|
|
89
|
+
export const $Set = (obj: VmAny, key: VmAny, value: VmAny): void => {
|
|
90
|
+
$AssertInit(obj);
|
|
91
|
+
$AssertInit(value);
|
|
92
|
+
const pk = $ToString(key);
|
|
93
|
+
if (obj == null) return;
|
|
94
|
+
if (!isVmExtern(obj)) throw new VmError(`Expected extern, got ${display(obj)}`, undefined);
|
|
95
|
+
obj.set(pk, value);
|
|
96
|
+
};
|
|
97
|
+
export const $Iterable = (value: VmAny): Iterable<VmValue | undefined> => {
|
|
98
|
+
$AssertInit(value);
|
|
99
|
+
if (isVmWrapper(value)) return value.keys();
|
|
100
|
+
if (isVmArray(value)) return value;
|
|
101
|
+
if (value != null && typeof value == 'object') return keys(value);
|
|
102
|
+
throw new VmError(`Value is not iterable: ${display(value)}`, isVmFunction(value) ? [] : [value]);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const $RecordSpread = (record: VmAny): VmRecord | null => {
|
|
106
|
+
$AssertInit(record);
|
|
107
|
+
if (record == null || isVmRecord(record)) return record;
|
|
108
|
+
if (isVmArray(record)) {
|
|
109
|
+
const result: Record<string, VmConst> = {};
|
|
110
|
+
const len = record.length;
|
|
111
|
+
for (let i = 0; i < len; i++) {
|
|
112
|
+
const item = record[i];
|
|
113
|
+
result[i] = item ?? null;
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
if (isVmExtern(record)) {
|
|
118
|
+
const result: Record<string, VmConst> = create(null);
|
|
119
|
+
for (const key of record.keys()) {
|
|
120
|
+
const value = record.get(key) ?? null;
|
|
121
|
+
// 当前只有 Primitive 不会进行二次包装
|
|
122
|
+
if (isVmPrimitive(value)) {
|
|
123
|
+
result[key] = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
throw new VmError(`Expected record, array, extern or nil, got ${display(record)}`, null);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const $ArraySpread = (array: VmAny): Iterable<VmConst | undefined> => {
|
|
132
|
+
$AssertInit(array);
|
|
133
|
+
if (array == null) return [];
|
|
134
|
+
if (isVmArray(array)) return array;
|
|
135
|
+
if (isVmExtern(array) && typeof (array.value as Iterable<unknown>)[Symbol.iterator] == 'function') {
|
|
136
|
+
const result: VmConst[] = [];
|
|
137
|
+
for (const item of array.value as Iterable<unknown>) {
|
|
138
|
+
// 当前只有 Primitive 不会进行二次包装
|
|
139
|
+
if (isVmPrimitive(item)) {
|
|
140
|
+
result.push(item);
|
|
141
|
+
} else {
|
|
142
|
+
result.push(null);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
throw new VmError(`Expected array, iterable extern or nil, got ${display(array)}`, []);
|
|
148
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { toNumber, toString, toBoolean, toFormat } from '../../helpers/convert/index.js';
|
|
2
|
+
import type { VmAny } from '../types/index.js';
|
|
3
|
+
import { $AssertInit } from './common.js';
|
|
4
|
+
|
|
5
|
+
export const $ToBoolean = (value: VmAny): boolean => {
|
|
6
|
+
if (typeof value == 'boolean') return value;
|
|
7
|
+
$AssertInit(value);
|
|
8
|
+
return toBoolean(value, undefined);
|
|
9
|
+
};
|
|
10
|
+
export const $ToString = (value: VmAny): string => {
|
|
11
|
+
if (typeof value == 'string') return value;
|
|
12
|
+
$AssertInit(value);
|
|
13
|
+
return toString(value, undefined);
|
|
14
|
+
};
|
|
15
|
+
export const $ToNumber = (value: VmAny): number => {
|
|
16
|
+
if (typeof value == 'number') return value;
|
|
17
|
+
$AssertInit(value);
|
|
18
|
+
return toNumber(value, undefined);
|
|
19
|
+
};
|
|
20
|
+
export const $Format = (value: VmAny, format: VmAny): string => {
|
|
21
|
+
$AssertInit(value);
|
|
22
|
+
const f = format == null ? '' : $ToString(format);
|
|
23
|
+
return toFormat(value, f);
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Cp as $Cp, CpEnter as $CpEnter, CpExit as $CpExit } from '../checkpoint.js';
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { create, defineProperty, isFinite } from '
|
|
2
|
-
import { VM_ARRAY_MAX_LENGTH, VM_FUNCTION_ANONYMOUS_NAME } from '
|
|
3
|
-
import { isVmConst } from '
|
|
4
|
-
import {
|
|
5
|
-
import type
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
export { Cp, CpEnter, CpExit } from './checkpoint.js';
|
|
1
|
+
import { create, defineProperty, isFinite } from '../../helpers/utils.js';
|
|
2
|
+
import { VM_ARRAY_MAX_LENGTH, VM_FUNCTION_ANONYMOUS_NAME } from '../../helpers/constants.js';
|
|
3
|
+
import { isVmConst } from '../../helpers/types.js';
|
|
4
|
+
import type { VmFunctionLike } from '../types/function.js';
|
|
5
|
+
import { DefaultVmContext, type VmContext } from '../types/context.js';
|
|
6
|
+
import type { VmConst, VmAny, VmArray, VmValue } from '../types/index.js';
|
|
7
|
+
import { VmFunction } from '../types/function.js';
|
|
8
|
+
import { $AssertInit } from './common.js';
|
|
9
|
+
import { $ToNumber } from './convert.js';
|
|
11
10
|
|
|
12
11
|
/** 过滤剩余参数数组 */
|
|
13
|
-
export const
|
|
12
|
+
export const $VArgs = (varags: VmAny[]): VmArray => {
|
|
14
13
|
for (let i = 0, l = varags.length; i < l; i++) {
|
|
15
14
|
const el = varags[i];
|
|
16
15
|
if (!isVmConst(el)) {
|
|
@@ -21,7 +20,7 @@ export const Vargs = (varags: VmAny[]): VmArray => {
|
|
|
21
20
|
};
|
|
22
21
|
|
|
23
22
|
/** 构造 record | array 元素 */
|
|
24
|
-
export const
|
|
23
|
+
export const $El = (value: VmAny): VmConst => {
|
|
25
24
|
$AssertInit(value);
|
|
26
25
|
if (!isVmConst(value)) return null;
|
|
27
26
|
return value;
|
|
@@ -29,20 +28,20 @@ export const Element = (value: VmAny): VmConst => {
|
|
|
29
28
|
|
|
30
29
|
const EMPTY = create(null);
|
|
31
30
|
/** 构造 record 可选元素 */
|
|
32
|
-
export const
|
|
31
|
+
export const $ElOpt = (key: string, value: VmAny): VmConst => {
|
|
33
32
|
$AssertInit(value);
|
|
34
33
|
if (value == null || !isVmConst(value)) return EMPTY;
|
|
35
34
|
return { __proto__: null, [key]: value };
|
|
36
35
|
};
|
|
37
36
|
|
|
38
37
|
/** 构造函数和函数表达式 */
|
|
39
|
-
export const
|
|
38
|
+
export const $Fn = (name: string | null | undefined, fn: VmFunctionLike): VmFunction => {
|
|
40
39
|
defineProperty(fn, 'name', { value: name || VM_FUNCTION_ANONYMOUS_NAME });
|
|
41
40
|
return VmFunction(fn, { isLib: false, injectCp: false });
|
|
42
41
|
};
|
|
43
42
|
|
|
44
43
|
/** 读取闭包上值 */
|
|
45
|
-
export const Upvalue = (value: VmAny): VmValue => {
|
|
44
|
+
export const $Upvalue = (value: VmAny): VmValue => {
|
|
46
45
|
$AssertInit(value);
|
|
47
46
|
return value;
|
|
48
47
|
};
|
|
@@ -55,7 +54,7 @@ const assertArrayLength = (start: number, end: number) => {
|
|
|
55
54
|
const isEmptyRange = (start: number, end: number) => {
|
|
56
55
|
return !isFinite(start) || !isFinite(end) || start > end;
|
|
57
56
|
};
|
|
58
|
-
export const ArrayRange = (start: VmAny, end: VmAny): VmArray => {
|
|
57
|
+
export const $ArrayRange = (start: VmAny, end: VmAny): VmArray => {
|
|
59
58
|
const s = $ToNumber(start);
|
|
60
59
|
const e = $ToNumber(end);
|
|
61
60
|
if (isEmptyRange(s, e)) return [];
|
|
@@ -66,7 +65,7 @@ export const ArrayRange = (start: VmAny, end: VmAny): VmArray => {
|
|
|
66
65
|
}
|
|
67
66
|
return arr;
|
|
68
67
|
};
|
|
69
|
-
export const ArrayRangeExclusive = (start: VmAny, end: VmAny): VmArray => {
|
|
68
|
+
export const $ArrayRangeExclusive = (start: VmAny, end: VmAny): VmArray => {
|
|
70
69
|
const s = $ToNumber(start);
|
|
71
70
|
const e = $ToNumber(end);
|
|
72
71
|
if (isEmptyRange(s, e)) return [];
|
|
@@ -79,6 +78,6 @@ export const ArrayRangeExclusive = (start: VmAny, end: VmAny): VmArray => {
|
|
|
79
78
|
};
|
|
80
79
|
|
|
81
80
|
/** 默认执行上下文 */
|
|
82
|
-
export function GlobalFallback(): VmContext {
|
|
81
|
+
export function $GlobalFallback(): VmContext {
|
|
83
82
|
return DefaultVmContext;
|
|
84
83
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './common.js';
|
|
2
|
+
export * from './convert.js';
|
|
3
|
+
export * from './type-check.js';
|
|
4
|
+
export * from './operator.js';
|
|
5
|
+
export * from './slice.js';
|
|
6
|
+
export * from './helpers.js';
|
|
7
|
+
export * from './compound.js';
|
|
8
|
+
export * from './cp.js';
|
|
9
|
+
export * from './call.js';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { toFormat } from '../../helpers/convert/index.js';
|
|
2
|
+
import { isNaN } from '../../helpers/utils.js';
|
|
3
|
+
import type { VmAny } from '../types/index.js';
|
|
4
|
+
import { $AssertInit } from './common.js';
|
|
5
|
+
import { $ToBoolean, $ToNumber, $ToString } from './convert.js';
|
|
6
|
+
import { isSame, overloadNumberString } from './utils.js';
|
|
7
|
+
|
|
8
|
+
// String operations
|
|
9
|
+
export const $Concat = (...args: readonly string[]): string => {
|
|
10
|
+
return args.map((a) => toFormat(a, null)).join('');
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Unary operations
|
|
14
|
+
export const $Pos = (a: VmAny): number => {
|
|
15
|
+
return $ToNumber(a);
|
|
16
|
+
};
|
|
17
|
+
export const $Neg = (a: VmAny): number => {
|
|
18
|
+
return -$ToNumber(a);
|
|
19
|
+
};
|
|
20
|
+
export const $Not = (a: VmAny): boolean => {
|
|
21
|
+
return !$ToBoolean(a);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Math operations
|
|
25
|
+
export const $Add = (a: VmAny, b: VmAny): number => {
|
|
26
|
+
return $ToNumber(a) + $ToNumber(b);
|
|
27
|
+
};
|
|
28
|
+
export const $Sub = (a: VmAny, b: VmAny): number => {
|
|
29
|
+
return $ToNumber(a) - $ToNumber(b);
|
|
30
|
+
};
|
|
31
|
+
export const $Mul = (a: VmAny, b: VmAny): number => {
|
|
32
|
+
return $ToNumber(a) * $ToNumber(b);
|
|
33
|
+
};
|
|
34
|
+
export const $Div = (a: VmAny, b: VmAny): number => {
|
|
35
|
+
return $ToNumber(a) / $ToNumber(b);
|
|
36
|
+
};
|
|
37
|
+
export const $Mod = (a: VmAny, b: VmAny): number => {
|
|
38
|
+
return $ToNumber(a) % $ToNumber(b);
|
|
39
|
+
};
|
|
40
|
+
export const $Pow = (a: VmAny, b: VmAny): number => {
|
|
41
|
+
return $ToNumber(a) ** $ToNumber(b);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Logical operations without short-circuiting
|
|
45
|
+
export const $And = (a: VmAny, b: VmAny): boolean => {
|
|
46
|
+
return $ToBoolean(a) && $ToBoolean(b);
|
|
47
|
+
};
|
|
48
|
+
export const $Or = (a: VmAny, b: VmAny): boolean => {
|
|
49
|
+
return $ToBoolean(a) || $ToBoolean(b);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Comparison operations
|
|
53
|
+
export const $Gt = (a: VmAny, b: VmAny): boolean => {
|
|
54
|
+
if (overloadNumberString(a, b)) {
|
|
55
|
+
return $ToNumber(a) > $ToNumber(b);
|
|
56
|
+
} else {
|
|
57
|
+
return $ToString(a) > $ToString(b);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
export const $Gte = (a: VmAny, b: VmAny): boolean => {
|
|
61
|
+
if (overloadNumberString(a, b)) {
|
|
62
|
+
return $ToNumber(a) >= $ToNumber(b);
|
|
63
|
+
} else {
|
|
64
|
+
return $ToString(a) >= $ToString(b);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
export const $Lt = (a: VmAny, b: VmAny): boolean => {
|
|
68
|
+
if (overloadNumberString(a, b)) {
|
|
69
|
+
return $ToNumber(a) < $ToNumber(b);
|
|
70
|
+
} else {
|
|
71
|
+
return $ToString(a) < $ToString(b);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
export const $Lte = (a: VmAny, b: VmAny): boolean => {
|
|
75
|
+
if (overloadNumberString(a, b)) {
|
|
76
|
+
return $ToNumber(a) <= $ToNumber(b);
|
|
77
|
+
} else {
|
|
78
|
+
return $ToString(a) <= $ToString(b);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Equality operations
|
|
83
|
+
export const $Eq = (a: VmAny, b: VmAny): boolean => {
|
|
84
|
+
$AssertInit(a);
|
|
85
|
+
$AssertInit(b);
|
|
86
|
+
// Number comparison is a special case to handle NaN correctly
|
|
87
|
+
if (typeof a == 'number' && typeof b == 'number') return a === b;
|
|
88
|
+
return isSame(a, b);
|
|
89
|
+
};
|
|
90
|
+
export const $Neq = (a: VmAny, b: VmAny): boolean => {
|
|
91
|
+
return !$Eq(a, b);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const { abs, min } = Math;
|
|
95
|
+
export const $Aeq = (a: VmAny, b: VmAny): boolean => {
|
|
96
|
+
if (overloadNumberString(a, b)) {
|
|
97
|
+
const an = $ToNumber(a);
|
|
98
|
+
const bn = $ToNumber(b);
|
|
99
|
+
const EPS = 1e-15;
|
|
100
|
+
if (isNaN(an) || isNaN(bn)) return false;
|
|
101
|
+
// Since Inf - Inf is NaN, we must check for equality first
|
|
102
|
+
if (an === bn) return true;
|
|
103
|
+
const absoluteDifference = abs(an - bn);
|
|
104
|
+
if (absoluteDifference < EPS) return true;
|
|
105
|
+
const base = min(abs(an), abs(bn));
|
|
106
|
+
return absoluteDifference < base * EPS;
|
|
107
|
+
} else {
|
|
108
|
+
// For strings, we use normalized case-insensitive comparison
|
|
109
|
+
const as = $ToString(a);
|
|
110
|
+
const bs = $ToString(b);
|
|
111
|
+
if (as === bs) return true;
|
|
112
|
+
const ai = as.toLowerCase();
|
|
113
|
+
const bi = bs.toLowerCase();
|
|
114
|
+
if (ai === bi) return true;
|
|
115
|
+
const an = ai.normalize('NFC');
|
|
116
|
+
const bn = bi.normalize('NFC');
|
|
117
|
+
return an === bn;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
export const $Naeq = (a: VmAny, b: VmAny): boolean => {
|
|
121
|
+
return !$Aeq(a, b);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const $Same = (a: VmAny, b: VmAny): boolean => {
|
|
125
|
+
$AssertInit(a);
|
|
126
|
+
$AssertInit(b);
|
|
127
|
+
return isSame(a, b);
|
|
128
|
+
};
|
|
129
|
+
export const $Nsame = (a: VmAny, b: VmAny): boolean => {
|
|
130
|
+
return !$Same(a, b);
|
|
131
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { VmError } from '../../helpers/error.js';
|
|
2
|
+
import { display } from '../../helpers/serialize.js';
|
|
3
|
+
import { isNaN, isSafeInteger } from '../../helpers/utils.js';
|
|
4
|
+
import { isVmArray } from '../../helpers/types.js';
|
|
5
|
+
import type { VmAny, VmArray } from '../types/index.js';
|
|
6
|
+
import { $AssertInit } from './common.js';
|
|
7
|
+
import { $ToNumber } from './convert.js';
|
|
8
|
+
const { ceil } = Math;
|
|
9
|
+
const { slice } = Array.prototype;
|
|
10
|
+
|
|
11
|
+
const sliceCore = (value: VmArray, start: number, end: number, exclusive: boolean): VmArray => {
|
|
12
|
+
const { length } = value;
|
|
13
|
+
|
|
14
|
+
if (isNaN(start)) start = 0;
|
|
15
|
+
else if (start < 0) start = length + start;
|
|
16
|
+
if (isNaN(end)) end = exclusive ? length : length - 1;
|
|
17
|
+
else if (end < 0) end = length + end;
|
|
18
|
+
|
|
19
|
+
start = ceil(start);
|
|
20
|
+
if (exclusive || !isSafeInteger(end)) {
|
|
21
|
+
end = ceil(end);
|
|
22
|
+
} else {
|
|
23
|
+
end = end + 1;
|
|
24
|
+
}
|
|
25
|
+
return slice.call(value, start, end) satisfies unknown[] as VmArray;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const $Slice = (value: VmAny, start: VmAny, end: VmAny): VmArray => {
|
|
29
|
+
$AssertInit(value);
|
|
30
|
+
if (!isVmArray(value)) throw new VmError(`Expected array, got ${display(value)}`, []);
|
|
31
|
+
const s = start != null ? $ToNumber(start) : 0;
|
|
32
|
+
const e = end != null ? $ToNumber(end) : value.length - 1;
|
|
33
|
+
return sliceCore(value, s, e, false);
|
|
34
|
+
};
|
|
35
|
+
export const $SliceExclusive = (value: VmAny, start: VmAny, end: VmAny): VmArray => {
|
|
36
|
+
$AssertInit(value);
|
|
37
|
+
if (!isVmArray(value)) throw new VmError(`Expected array, got ${display(value)}`, []);
|
|
38
|
+
const s = start != null ? $ToNumber(start) : 0;
|
|
39
|
+
const e = end != null ? $ToNumber(end) : value.length;
|
|
40
|
+
return sliceCore(value, s, e, true);
|
|
41
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { VmError } from '../../helpers/error.js';
|
|
2
|
+
import { isVmArray, isVmRecord, isVmWrapper } from '../../helpers/types.js';
|
|
3
|
+
import type { TypeName, VmAny, VmArray, VmRecord, VmValue } from '../types/index.js';
|
|
4
|
+
import { $AssertInit } from './common.js';
|
|
5
|
+
|
|
6
|
+
export const $Type = (value: VmAny): TypeName => {
|
|
7
|
+
// 允许未初始化值通过
|
|
8
|
+
if (value == null) return 'nil';
|
|
9
|
+
if (isVmWrapper(value)) return value.type;
|
|
10
|
+
if (isVmArray(value)) return 'array';
|
|
11
|
+
if (typeof value == 'object') return 'record';
|
|
12
|
+
return typeof value as TypeName;
|
|
13
|
+
};
|
|
14
|
+
export const $IsBoolean = (value: VmAny): value is boolean => {
|
|
15
|
+
$AssertInit(value);
|
|
16
|
+
return typeof value == 'boolean';
|
|
17
|
+
};
|
|
18
|
+
export const $IsNumber = (value: VmAny): value is number => {
|
|
19
|
+
$AssertInit(value);
|
|
20
|
+
return typeof value == 'number';
|
|
21
|
+
};
|
|
22
|
+
export const $IsString = (value: VmAny): value is string => {
|
|
23
|
+
$AssertInit(value);
|
|
24
|
+
return typeof value == 'string';
|
|
25
|
+
};
|
|
26
|
+
export const $IsRecord = (value: VmAny): value is VmRecord => {
|
|
27
|
+
$AssertInit(value);
|
|
28
|
+
return isVmRecord(value);
|
|
29
|
+
};
|
|
30
|
+
export const $IsArray = (value: VmAny): value is VmArray => {
|
|
31
|
+
$AssertInit(value);
|
|
32
|
+
return isVmArray(value);
|
|
33
|
+
};
|
|
34
|
+
export const $AssertNonNil = (value: VmAny): asserts value is NonNullable<VmValue> => {
|
|
35
|
+
$AssertInit(value);
|
|
36
|
+
if (value !== null) return;
|
|
37
|
+
throw new VmError(`Expected non-nil value`, null);
|
|
38
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { hasOwnEnumerable, isNaN, keys } from '../../helpers/utils.js';
|
|
2
|
+
import { isVmArray, isVmWrapper } from '../../helpers/types.js';
|
|
3
|
+
import type { VmAny, VmValue } from '../types/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 确定操作的重载
|
|
7
|
+
* @returns 如果应按数字处理则返回 true;如果应按字符串处理则返回 false
|
|
8
|
+
*/
|
|
9
|
+
export function overloadNumberString(a: VmAny, b: VmAny): boolean {
|
|
10
|
+
if (typeof a == 'number' || typeof b == 'number') return true;
|
|
11
|
+
if (typeof a == 'string' || typeof b == 'string') return false;
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 检查两个 VmValue 是否相同
|
|
17
|
+
*/
|
|
18
|
+
export function isSame(a: VmValue, b: VmValue): boolean {
|
|
19
|
+
// Check for NaN
|
|
20
|
+
if (typeof a == 'number' && typeof b == 'number') {
|
|
21
|
+
return a === b || (isNaN(a) && isNaN(b));
|
|
22
|
+
}
|
|
23
|
+
// Check all primitive types, and fast path for reference equality
|
|
24
|
+
if (a === b) return true;
|
|
25
|
+
// Any primitives and functions arrive here are not equal
|
|
26
|
+
if (a == null || typeof a != 'object' || b == null || typeof b != 'object') return false;
|
|
27
|
+
// Handle wrapper values
|
|
28
|
+
if (isVmWrapper(a)) return a.same(b);
|
|
29
|
+
if (isVmWrapper(b)) return b.same(a);
|
|
30
|
+
// Handle array values
|
|
31
|
+
if (isVmArray(a) && isVmArray(b)) {
|
|
32
|
+
const len = a.length;
|
|
33
|
+
if (len !== b.length) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
// Compare array items
|
|
37
|
+
for (let i = 0; i < len; i++) {
|
|
38
|
+
if (!isSame(a[i] ?? null, b[i] ?? null)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
// Handle record values
|
|
45
|
+
if (!isVmArray(a) && !isVmArray(b)) {
|
|
46
|
+
// Compare record fields
|
|
47
|
+
const aKeys = keys(a);
|
|
48
|
+
const bKeys = keys(b);
|
|
49
|
+
if (aKeys.length !== bKeys.length) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
for (const key of aKeys) {
|
|
53
|
+
if (!hasOwnEnumerable(b, key)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
/* c8 ignore next 2 */
|
|
57
|
+
const av = a[key] ?? null;
|
|
58
|
+
const bv = b[key] ?? null;
|
|
59
|
+
if (!isSame(av, bv)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
package/src/vm/types/boundary.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { kVmFunctionProxy } from '../../helpers/constants.js';
|
|
|
4
4
|
import type { VmFunctionLike, VmFunction } from './function.js';
|
|
5
5
|
import { VmExtern } from './extern.js';
|
|
6
6
|
import type { VmAny, VmConst, VmModule, VmPrimitive, VmValue } from './index.js';
|
|
7
|
-
import { $Call } from '../operations.js';
|
|
7
|
+
import { $Call } from '../operations/call.js';
|
|
8
8
|
|
|
9
9
|
/** 创建 Mirascript 函数在宿主语言运行的代理 */
|
|
10
10
|
export function toVmFunctionProxy<T extends VmFunctionLike>(fn: VmFunction<T>): T {
|
|
@@ -15,7 +15,7 @@ export function toVmFunctionProxy<T extends VmFunctionLike>(fn: VmFunction<T>):
|
|
|
15
15
|
const ret = $Call(
|
|
16
16
|
fn,
|
|
17
17
|
// 作为函数参数传入的值一定丢失了它的 this
|
|
18
|
-
args.map((v) => wrapToVmValue(v, null)),
|
|
18
|
+
args.map((v) => wrapToVmValue(v, null, null)),
|
|
19
19
|
);
|
|
20
20
|
return unwrapFromVmValue(ret);
|
|
21
21
|
};
|
|
@@ -29,26 +29,26 @@ export function toVmFunctionProxy<T extends VmFunctionLike>(fn: VmFunction<T>):
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** 解开 Mirascript 函数在宿主语言运行的代理 */
|
|
32
|
-
export function fromVmFunctionProxy<T extends VmFunctionLike>(fn: T): VmFunction<T> |
|
|
32
|
+
export function fromVmFunctionProxy<T extends VmFunctionLike>(fn: T): VmFunction<T> | null {
|
|
33
33
|
if (isVmFunction(fn)) return fn;
|
|
34
34
|
|
|
35
35
|
const original = (fn as unknown as { [kVmFunctionProxy]?: VmFunction<T> })[kVmFunctionProxy];
|
|
36
36
|
if (original && isVmFunction(original)) return original;
|
|
37
37
|
|
|
38
|
-
return
|
|
38
|
+
return null;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/** 将宿主语言的值包装为 Mirascript 类型 */
|
|
42
42
|
export function wrapToVmValue(
|
|
43
43
|
value: unknown,
|
|
44
|
-
thisArg:
|
|
45
|
-
assumeVmValue
|
|
44
|
+
thisArg: unknown = null,
|
|
45
|
+
assumeVmValue: ((obj: object) => obj is Exclude<VmConst, VmPrimitive>) | null = null,
|
|
46
46
|
): VmValue {
|
|
47
47
|
if (value == null) return null;
|
|
48
48
|
switch (typeof value) {
|
|
49
49
|
case 'function': {
|
|
50
50
|
const unwrapped = fromVmFunctionProxy(value as VmFunctionLike);
|
|
51
|
-
if (unwrapped) return unwrapped;
|
|
51
|
+
if (unwrapped != null) return unwrapped;
|
|
52
52
|
return new VmExtern(value as () => never, thisArg);
|
|
53
53
|
}
|
|
54
54
|
case 'object': {
|
|
@@ -71,6 +71,16 @@ export function wrapToVmValue(
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/** 创建绑定 */
|
|
75
|
+
function bindThis<T extends (...args: unknown[]) => unknown>(fn: T, thisArg: unknown): T {
|
|
76
|
+
if (thisArg == null) return fn;
|
|
77
|
+
return new Proxy(fn, {
|
|
78
|
+
apply(target, _thisArg, args) {
|
|
79
|
+
return apply(target, thisArg, args);
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
/** 取消宿主语言的值的 Mirascript 包装 */
|
|
75
85
|
export function unwrapFromVmValue(value: VmAny): unknown {
|
|
76
86
|
if (isVmFunction(value)) {
|
|
@@ -83,10 +93,5 @@ export function unwrapFromVmValue(value: VmAny): unknown {
|
|
|
83
93
|
return value.value;
|
|
84
94
|
}
|
|
85
95
|
const f = value as VmExtern<(...args: unknown[]) => unknown>;
|
|
86
|
-
|
|
87
|
-
return new Proxy(f.value, {
|
|
88
|
-
apply(target, thisArg, args): unknown {
|
|
89
|
-
return apply(target, caller, args);
|
|
90
|
-
},
|
|
91
|
-
});
|
|
96
|
+
return bindThis(f.value, f.thisArg);
|
|
92
97
|
}
|