@mirascript/mirascript 0.1.1 → 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-2PWHYDNX.js → chunk-MVHCSH3E.js} +239 -122
- 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/helpers/utils.d.ts +1 -0
- package/dist/helpers/utils.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/subtle.js +1 -1
- package/dist/vm/error.d.ts.map +1 -1
- package/dist/vm/lib/_helpers.d.ts.map +1 -1
- package/dist/vm/lib/global/sequence/find.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 +12 -0
- package/dist/vm/types/boundary.d.ts.map +1 -0
- 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 +7 -7
- package/dist/vm/types/extern.d.ts.map +1 -1
- package/dist/vm/types/function.d.ts +0 -4
- package/dist/vm/types/function.d.ts.map +1 -1
- package/dist/vm/types/index.d.ts +9 -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/helpers/utils.ts +1 -0
- package/src/vm/error.ts +15 -3
- package/src/vm/lib/_helpers.ts +4 -13
- package/src/vm/lib/_loader.ts +4 -4
- package/src/vm/lib/global/math.ts +1 -1
- package/src/vm/lib/global/sequence/find.ts +13 -11
- package/src/vm/lib/global/sequence/map-filter.ts +2 -2
- package/src/vm/lib/global/sequence/with.ts +111 -17
- package/src/vm/operations.ts +23 -24
- package/src/vm/types/boundary.ts +95 -0
- package/src/vm/types/checker.ts +8 -15
- package/src/vm/types/context.ts +31 -19
- package/src/vm/types/extern.ts +30 -57
- package/src/vm/types/function.ts +1 -37
- package/src/vm/types/index.ts +12 -24
- package/src/vm/types/module.ts +7 -0
- package/src/vm/types/wrapper.ts +7 -0
- package/dist/chunk-2PWHYDNX.js.map +0 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/vm/types/module.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,oBAAoB;AACpB,qBAAa,QAAQ,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,CAAC;IAErG,WAAW;IACX,QAAQ,CAAC,IAAI,EAAE,MAAM;;IADrB,WAAW;IACF,IAAI,EAAE,MAAM;IACrB,WAAW;IACX,KAAK,EAAE,CAAC;IAIZ,kBAAkB;IACT,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAGlC,kBAAkB;IACT,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK;IAIhC,kBAAkB;IACT,IAAI,IAAI,MAAM,EAAE;IAGzB,kBAAkB;IACT,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;IAGpC,kBAAkB;IAClB,IAAa,IAAI,IAAI,QAAQ,CAE5B;IACD,kBAAkB;IAClB,IAAa,QAAQ,IAAI,MAAM,CAE9B;CACJ"}
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/vm/types/module.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,oBAAoB;AACpB,qBAAa,QAAQ,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,CAAC;IAErG,WAAW;IACX,QAAQ,CAAC,IAAI,EAAE,MAAM;;IADrB,WAAW;IACF,IAAI,EAAE,MAAM;IACrB,WAAW;IACX,KAAK,EAAE,CAAC;IAIZ,kBAAkB;IACT,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAGlC,kBAAkB;IACT,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK;IAIhC,kBAAkB;IACT,IAAI,IAAI,MAAM,EAAE;IAGzB,kBAAkB;IACT,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;IAGpC,kBAAkB;IAClB,IAAa,IAAI,IAAI,QAAQ,CAE5B;IACD,kBAAkB;IAClB,IAAa,QAAQ,IAAI,MAAM,CAE9B;CACJ;AAID,2BAA2B;AAC3B,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,CAElG"}
|
|
@@ -22,4 +22,6 @@ export declare abstract class VmWrapper<T extends object> {
|
|
|
22
22
|
/** 转为字符串 */
|
|
23
23
|
toString(): string;
|
|
24
24
|
}
|
|
25
|
+
/** 检查值是否为 MiraScript 包装器 */
|
|
26
|
+
export declare function isVmWrapper<T extends object>(value: unknown): value is VmWrapper<T>;
|
|
25
27
|
//# sourceMappingURL=wrapper.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../../src/vm/types/wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,8BAAsB,SAAS,CAAC,CAAC,SAAS,MAAM;IAChC,QAAQ,CAAC,KAAK,EAAE,CAAC;gBAAR,KAAK,EAAE,CAAC;IAC7B,cAAc;IACd,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAClC,cAAc;IACd,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK;IAChC,WAAW;IACX,QAAQ,CAAC,IAAI,IAAI,MAAM,EAAE;IACzB,aAAa;IACb,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;IACpC,WAAW;IACX,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC;IAC9B,WAAW;IACX,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC;IAChC,iCAAiC;IACjC,MAAM,IAAI,SAAS;IAGnB,YAAY;IACZ,QAAQ,IAAI,MAAM;CAGrB"}
|
|
1
|
+
{"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../../src/vm/types/wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,8BAAsB,SAAS,CAAC,CAAC,SAAS,MAAM;IAChC,QAAQ,CAAC,KAAK,EAAE,CAAC;gBAAR,KAAK,EAAE,CAAC;IAC7B,cAAc;IACd,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAClC,cAAc;IACd,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK;IAChC,WAAW;IACX,QAAQ,CAAC,IAAI,IAAI,MAAM,EAAE;IACzB,aAAa;IACb,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;IACpC,WAAW;IACX,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC;IAC9B,WAAW;IACX,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC;IAChC,iCAAiC;IACjC,MAAM,IAAI,SAAS;IAGnB,YAAY;IACZ,QAAQ,IAAI,MAAM;CAGrB;AAID,4BAA4B;AAC5B,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,CAEnF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mirascript/mirascript",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "An expression based scripting language.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"ansi-styles": "^6.2.3",
|
|
36
36
|
"commander": "^14.0.2",
|
|
37
37
|
"js-base64": "^3.7.8",
|
|
38
|
-
"@mirascript/napi": "~0.1.
|
|
39
|
-
"@mirascript/wasm": "~0.1.
|
|
38
|
+
"@mirascript/napi": "~0.1.3",
|
|
39
|
+
"@mirascript/wasm": "~0.1.3"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@types/node": "^24.
|
|
42
|
+
"@types/node": "^24.10.0",
|
|
43
43
|
"@types/sinon": "^17.0.4",
|
|
44
44
|
"ava": "^6.4.1",
|
|
45
45
|
"c8": "^10.1.3",
|
|
@@ -60,20 +60,27 @@ export interface SourceReference<T extends DiagnosticCode = DiagnosticCode> exte
|
|
|
60
60
|
readonly diagnostic: SourceDiagnostic<T>;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
diagnostics: Uint32Array,
|
|
67
|
-
): {
|
|
63
|
+
/** 解析后的诊断信息 */
|
|
64
|
+
interface ParsedDiagnostics {
|
|
65
|
+
/** 错误诊断信息 */
|
|
68
66
|
errors: SourceDiagnostic[];
|
|
67
|
+
/** 警告诊断信息 */
|
|
69
68
|
warnings: SourceDiagnostic[];
|
|
69
|
+
/** 信息诊断信息 */
|
|
70
70
|
infos: SourceDiagnostic[];
|
|
71
|
+
/** 提示诊断信息 */
|
|
71
72
|
hints: SourceDiagnostic[];
|
|
73
|
+
/** 标签诊断信息 */
|
|
72
74
|
tags: SourceDiagnostic[];
|
|
73
75
|
|
|
76
|
+
/** 引用诊断信息 */
|
|
74
77
|
references: SourceReference[];
|
|
78
|
+
/** 标签引用诊断信息 */
|
|
75
79
|
tagsReferences: SourceReference[];
|
|
76
|
-
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 分析诊断信息,{@link diagnostic_position_encoding} 不能设为 `None` */
|
|
83
|
+
export function parseDiagnostics(source: ScriptInput, diagnostics: Uint32Array): ParsedDiagnostics {
|
|
77
84
|
const parsed = [];
|
|
78
85
|
const bufLen = diagnostics.length;
|
|
79
86
|
for (let i = 0; i < bufLen; i += 5) {
|
|
@@ -148,12 +155,7 @@ export function parseDiagnostics(
|
|
|
148
155
|
}
|
|
149
156
|
|
|
150
157
|
/** 生成诊断 range 的字符串 */
|
|
151
|
-
function formatRange(range: {
|
|
152
|
-
startLineNumber: number;
|
|
153
|
-
startColumn: number;
|
|
154
|
-
endLineNumber: number;
|
|
155
|
-
endColumn: number;
|
|
156
|
-
}): string {
|
|
158
|
+
function formatRange(range: IRange): string {
|
|
157
159
|
if (range.startLineNumber === range.endLineNumber) {
|
|
158
160
|
if (range.startColumn === range.endColumn) {
|
|
159
161
|
return `${range.startLineNumber}:${range.startColumn}`;
|
package/src/helpers/utils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const { isArray } = Array;
|
|
2
2
|
export const { isFinite, isNaN, isInteger, isSafeInteger } = Number;
|
|
3
3
|
export const { hasOwn, keys, values, entries, create, getPrototypeOf, fromEntries, defineProperty } = Object;
|
|
4
|
+
export const { apply } = Reflect;
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Determines whether an object has an enumerable property with the specified name.
|
package/src/vm/error.ts
CHANGED
|
@@ -14,9 +14,21 @@ export class VmError extends Error {
|
|
|
14
14
|
|
|
15
15
|
/** 从其他错误构造 */
|
|
16
16
|
static from(prefix: string, error: unknown, recovered: VmAny): VmError {
|
|
17
|
-
if (prefix
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
if (prefix) {
|
|
18
|
+
if (prefix.endsWith(':')) {
|
|
19
|
+
prefix += ' ';
|
|
20
|
+
} else if (!prefix.endsWith(': ')) {
|
|
21
|
+
prefix += ': ';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
let vmError: VmError;
|
|
25
|
+
if (error instanceof Error) {
|
|
26
|
+
vmError = new VmError(`${prefix}${error.message}`, recovered);
|
|
27
|
+
vmError.stack = error.stack;
|
|
28
|
+
} else {
|
|
29
|
+
vmError = new VmError(`${prefix}${String(error)}`, recovered);
|
|
30
|
+
}
|
|
31
|
+
vmError.cause = error;
|
|
20
32
|
return vmError;
|
|
21
33
|
}
|
|
22
34
|
}
|
package/src/vm/lib/_helpers.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { VmError } from '../error.js';
|
|
|
3
3
|
import { $ToNumber, $Type } from '../operations.js';
|
|
4
4
|
import {
|
|
5
5
|
isVmArray,
|
|
6
|
-
isVmExtern,
|
|
7
6
|
isVmFunction,
|
|
8
7
|
type VmExtern,
|
|
9
8
|
type VmFunction,
|
|
@@ -17,6 +16,7 @@ import {
|
|
|
17
16
|
type VmConst,
|
|
18
17
|
isVmConst,
|
|
19
18
|
VM_ARRAY_MAX_LENGTH,
|
|
19
|
+
isVmCallable,
|
|
20
20
|
} from '../types/index.js';
|
|
21
21
|
import type { VmFunctionLike, VmFunctionOption } from '../types/function.js';
|
|
22
22
|
import { Cp } from '../helpers.js';
|
|
@@ -127,8 +127,7 @@ export function expectCallable(
|
|
|
127
127
|
recovered: VmAny | (() => VmAny),
|
|
128
128
|
): asserts value is VmFunction | VmExtern {
|
|
129
129
|
required(name, value, recovered);
|
|
130
|
-
|
|
131
|
-
if (!callable) {
|
|
130
|
+
if (!isVmCallable(value)) {
|
|
132
131
|
throwUnexpectedTypeError(name, 'callable', value, recovered);
|
|
133
132
|
}
|
|
134
133
|
}
|
|
@@ -168,11 +167,7 @@ export function map(
|
|
|
168
167
|
Cp();
|
|
169
168
|
const ret = mapper(data[i] ?? null, i, data);
|
|
170
169
|
if (ret === undefined) continue;
|
|
171
|
-
|
|
172
|
-
result.push(ret);
|
|
173
|
-
} else {
|
|
174
|
-
result.push(null);
|
|
175
|
-
}
|
|
170
|
+
result.push(ret);
|
|
176
171
|
}
|
|
177
172
|
return result;
|
|
178
173
|
} else {
|
|
@@ -181,11 +176,7 @@ export function map(
|
|
|
181
176
|
Cp();
|
|
182
177
|
const ret = mapper(value ?? null, key, data);
|
|
183
178
|
if (ret === undefined) continue;
|
|
184
|
-
|
|
185
|
-
e.push([key, ret]);
|
|
186
|
-
} else {
|
|
187
|
-
e.push([key, null]);
|
|
188
|
-
}
|
|
179
|
+
e.push([key, ret]);
|
|
189
180
|
}
|
|
190
181
|
return fromEntries(e);
|
|
191
182
|
}
|
package/src/vm/lib/_loader.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { VmLib, VmLibOption } from './_helpers.js';
|
|
|
6
6
|
import * as global from './global/index.js';
|
|
7
7
|
|
|
8
8
|
for (const [name, value] of entries(global)) {
|
|
9
|
-
VmSharedContext[name] = wrapEntry(name, value as RawValue);
|
|
9
|
+
VmSharedContext[name] = wrapEntry(name, value as RawValue, 'global');
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/** 原始值 */
|
|
@@ -14,7 +14,7 @@ type RawValue = VmLib | VmConst | VmModule;
|
|
|
14
14
|
/** 包装值 */
|
|
15
15
|
type ToWrappedValue<V extends RawValue> = V extends VmFunctionLike ? VmFunction<V> : V;
|
|
16
16
|
/** 包装值 */
|
|
17
|
-
function wrapEntry<const T extends RawValue>(name: string, value: T): ToWrappedValue<T> {
|
|
17
|
+
function wrapEntry<const T extends RawValue>(name: string, value: T, module: string): ToWrappedValue<T> {
|
|
18
18
|
if (typeof value == 'function') {
|
|
19
19
|
if (value.name !== name) {
|
|
20
20
|
// 如果函数名和导出名不一致,则重命名
|
|
@@ -26,7 +26,7 @@ function wrapEntry<const T extends RawValue>(name: string, value: T): ToWrappedV
|
|
|
26
26
|
return VmFunction(value, {
|
|
27
27
|
isLib: true,
|
|
28
28
|
injectCp: true,
|
|
29
|
-
fullName:
|
|
29
|
+
fullName: `${module}.${name}`,
|
|
30
30
|
...(value as VmLibOption),
|
|
31
31
|
}) as ToWrappedValue<T>;
|
|
32
32
|
} else {
|
|
@@ -43,7 +43,7 @@ export type ToWrappedModule<T extends Record<string, RawValue>> = VmModule<{
|
|
|
43
43
|
export function createModule<const T extends Record<string, RawValue>>(name: string, lib: T): ToWrappedModule<T> {
|
|
44
44
|
const mod = create(null) as Record<string, VmImmutable>;
|
|
45
45
|
for (const [key, value] of entries(lib)) {
|
|
46
|
-
mod[key] = wrapEntry(key, value);
|
|
46
|
+
mod[key] = wrapEntry(key, value, name);
|
|
47
47
|
}
|
|
48
48
|
return new VmModule(name, mod) as ToWrappedModule<T>;
|
|
49
49
|
}
|
|
@@ -15,7 +15,7 @@ export const pow = VmLib((x, y) => _pow($ToNumber(x), $ToNumber(y)), {
|
|
|
15
15
|
returnsType: 'number',
|
|
16
16
|
});
|
|
17
17
|
export const random = VmLib(Math.random, {
|
|
18
|
-
summary: '返回 0
|
|
18
|
+
summary: '返回 [0, 1) 之间的伪随机数',
|
|
19
19
|
params: {},
|
|
20
20
|
paramsType: {},
|
|
21
21
|
returnsType: 'number',
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { entries } from '../../../../helpers/utils.js';
|
|
2
2
|
import { Cp } from '../../../helpers.js';
|
|
3
|
-
import { $Call, $ToBoolean } from '../../../operations.js';
|
|
4
|
-
import { type VmValue, isVmArray } from '../../../types/index.js';
|
|
5
|
-
import { VmLib, expectArrayOrRecord,
|
|
3
|
+
import { $Call, $Same, $ToBoolean } from '../../../operations.js';
|
|
4
|
+
import { type VmValue, isVmArray, isVmCallable } from '../../../types/index.js';
|
|
5
|
+
import { VmLib, expectArrayOrRecord, required } from '../../_helpers.js';
|
|
6
6
|
|
|
7
7
|
export const find = VmLib(
|
|
8
8
|
(data, predicate) => {
|
|
9
9
|
expectArrayOrRecord('data', data, null);
|
|
10
|
-
|
|
11
|
-
const p = (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
required('predicate', predicate, null);
|
|
11
|
+
const p = isVmCallable(predicate)
|
|
12
|
+
? (value: VmValue, key: string | number, data: VmValue) => {
|
|
13
|
+
const ret = $Call(predicate, [value, key, data]);
|
|
14
|
+
return $ToBoolean(ret);
|
|
15
|
+
}
|
|
16
|
+
: (value: VmValue) => $Same(predicate, value);
|
|
15
17
|
if (isVmArray(data)) {
|
|
16
18
|
const { length } = data;
|
|
17
19
|
for (let i = 0; i < length; i++) {
|
|
@@ -37,13 +39,13 @@ export const find = VmLib(
|
|
|
37
39
|
summary: '查找数组或记录中的键值对,返回第一个满足条件的键值对',
|
|
38
40
|
params: {
|
|
39
41
|
data: '查的数组或记录',
|
|
40
|
-
predicate: '
|
|
42
|
+
predicate: '用于测试每个键值对的函数,或要查找的值',
|
|
41
43
|
},
|
|
42
44
|
paramsType: {
|
|
43
45
|
data: 'array | record',
|
|
44
|
-
predicate: 'fn(value: any, key: number | string | nil, input: type(data)) -> boolean',
|
|
46
|
+
predicate: '(fn(value: any, key: number | string | nil, input: type(data)) -> boolean) | any',
|
|
45
47
|
},
|
|
46
48
|
returnsType: '(string | number, any) | nil',
|
|
47
|
-
examples: ['find([3, 5, 8], fn (v) { v % 2 == 0 }) // (2, 8)'],
|
|
49
|
+
examples: ['find([3, 5, 8], fn (v) { v % 2 == 0 }) // (2, 8)', `find((x: 1, y: 2, z: 3), 2) // ('y', 2)`],
|
|
48
50
|
},
|
|
49
51
|
);
|
|
@@ -49,7 +49,7 @@ export const filter = VmLib(
|
|
|
49
49
|
summary: '过滤数组或记录中的元素,返回满足条件的元素',
|
|
50
50
|
params: {
|
|
51
51
|
data: '要过滤的数组或记录',
|
|
52
|
-
predicate: '
|
|
52
|
+
predicate: '用于测试每个元素的函数',
|
|
53
53
|
},
|
|
54
54
|
paramsType: {
|
|
55
55
|
data: 'array | record',
|
|
@@ -70,7 +70,7 @@ export const filter_map = VmLib(
|
|
|
70
70
|
summary: '对数组或记录中的每个元素应用函数,并返回非 nil 的结果',
|
|
71
71
|
params: {
|
|
72
72
|
data: '要映射的数组或记录',
|
|
73
|
-
f: '
|
|
73
|
+
f: '应用于每个元素的函数',
|
|
74
74
|
},
|
|
75
75
|
paramsType: {
|
|
76
76
|
data: 'array | record',
|
|
@@ -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]);
|