@mirascript/mirascript 0.1.62-alpha.8 → 0.1.63
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/README.md +46 -0
- package/dist/{chunk-6CAGNGY6.js → chunk-5BBTXV5A.js} +270 -269
- package/dist/chunk-5BBTXV5A.js.map +6 -0
- package/dist/{chunk-VRGQ23AK.js → chunk-HZYARHEQ.js} +30 -38
- package/dist/chunk-HZYARHEQ.js.map +6 -0
- package/dist/compiler/emit/consts.d.ts.map +1 -1
- package/dist/compiler/emit-script.d.ts +7 -2
- package/dist/compiler/emit-script.d.ts.map +1 -1
- package/dist/compiler/index.d.ts +2 -0
- package/dist/compiler/index.d.ts.map +1 -1
- package/dist/compiler/worker.js +1 -1
- package/dist/helpers/constants.d.ts +1 -10
- package/dist/helpers/constants.d.ts.map +1 -1
- package/dist/helpers/serialize.d.ts.map +1 -1
- package/dist/helpers/types.d.ts +5 -1
- package/dist/helpers/types.d.ts.map +1 -1
- package/dist/helpers/utils.d.ts +7 -1
- package/dist/helpers/utils.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/subtle.js +3 -3
- package/dist/subtle.js.map +1 -1
- package/dist/vm/checkpoint.d.ts +3 -3
- package/dist/vm/checkpoint.d.ts.map +1 -1
- package/dist/vm/lib/global/debug.d.ts.map +1 -1
- package/dist/vm/lib/global/sequence/entries.d.ts.map +1 -1
- package/dist/vm/lib/global/sequence/with.d.ts.map +1 -1
- package/dist/vm/operations/array-range.d.ts +6 -0
- package/dist/vm/operations/array-range.d.ts.map +1 -0
- package/dist/vm/operations/call.d.ts.map +1 -1
- package/dist/vm/operations/common.d.ts +1 -1
- package/dist/vm/operations/common.d.ts.map +1 -1
- package/dist/vm/operations/compound.d.ts +10 -10
- package/dist/vm/operations/compound.d.ts.map +1 -1
- package/dist/vm/operations/convert.d.ts +4 -4
- package/dist/vm/operations/convert.d.ts.map +1 -1
- package/dist/vm/operations/helpers.d.ts +7 -13
- package/dist/vm/operations/helpers.d.ts.map +1 -1
- package/dist/vm/operations/index.d.ts +1 -0
- package/dist/vm/operations/index.d.ts.map +1 -1
- package/dist/vm/operations/operator.d.ts +22 -22
- package/dist/vm/operations/operator.d.ts.map +1 -1
- package/dist/vm/operations/slice.d.ts +2 -2
- package/dist/vm/operations/slice.d.ts.map +1 -1
- package/dist/vm/operations/type-check.d.ts +7 -7
- package/dist/vm/operations/type-check.d.ts.map +1 -1
- package/dist/vm/operations/utils.d.ts +2 -2
- package/dist/vm/operations/utils.d.ts.map +1 -1
- package/dist/vm/types/boundary.d.ts +1 -1
- package/dist/vm/types/boundary.d.ts.map +1 -1
- package/dist/vm/types/context.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 +1 -1
- package/dist/vm/types/index.d.ts.map +1 -1
- package/package.json +21 -8
- package/src/compiler/diagnostic.ts +2 -2
- package/src/compiler/emit/consts.ts +1 -0
- package/src/compiler/emit/index.ts +5 -5
- package/src/compiler/emit-script.ts +15 -6
- package/src/compiler/index.ts +3 -2
- package/src/helpers/analyze.ts +1 -1
- package/src/helpers/constants.ts +1 -11
- package/src/helpers/serialize.ts +1 -1
- package/src/helpers/types.ts +16 -0
- package/src/helpers/utils.ts +2 -1
- package/src/vm/checkpoint.ts +6 -6
- package/src/vm/lib/global/debug.ts +0 -1
- package/src/vm/lib/global/sequence/entries.ts +2 -0
- package/src/vm/lib/global/sequence/map-filter.ts +4 -4
- package/src/vm/lib/global/sequence/with.ts +3 -2
- package/src/vm/lib/loader.ts +2 -2
- package/src/vm/operations/array-range.ts +37 -0
- package/src/vm/operations/call.ts +8 -7
- package/src/vm/operations/common.ts +2 -2
- package/src/vm/operations/compound.ts +24 -25
- package/src/vm/operations/convert.ts +8 -8
- package/src/vm/operations/helpers.ts +17 -51
- package/src/vm/operations/index.ts +1 -0
- package/src/vm/operations/operator.ts +64 -54
- package/src/vm/operations/slice.ts +5 -4
- package/src/vm/operations/type-check.ts +16 -19
- package/src/vm/operations/utils.ts +4 -4
- package/src/vm/types/boundary.ts +2 -2
- package/src/vm/types/context.ts +1 -2
- package/src/vm/types/function.ts +2 -21
- package/src/vm/types/index.ts +1 -0
- package/bench/index.ts +0 -45
- package/dist/chunk-6CAGNGY6.js.map +0 -6
- package/dist/chunk-VRGQ23AK.js.map +0 -6
package/package.json
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mirascript/mirascript",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.63",
|
|
4
4
|
"author": "CloudPSS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "An expression based scripting language.",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"mirascript",
|
|
9
|
+
"language",
|
|
10
|
+
"compiler",
|
|
11
|
+
"runtime",
|
|
12
|
+
"vm"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://mira.cloudpss.net",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/CloudPSS/MiraScript.git",
|
|
18
|
+
"directory": "packages/mirascript"
|
|
19
|
+
},
|
|
7
20
|
"type": "module",
|
|
8
21
|
"main": "./dist/index.js",
|
|
9
22
|
"types": "./dist/index.d.ts",
|
|
@@ -29,15 +42,15 @@
|
|
|
29
42
|
"@cloudpss/worker": "^0.1.11",
|
|
30
43
|
"source-map-js": "^1.2.1",
|
|
31
44
|
"supports-color": "^10.2.2",
|
|
32
|
-
"@mirascript/bindings": "~0.1.
|
|
33
|
-
"@mirascript/constants": "~0.1.
|
|
45
|
+
"@mirascript/bindings": "~0.1.63",
|
|
46
|
+
"@mirascript/constants": "~0.1.63"
|
|
34
47
|
},
|
|
35
48
|
"devDependencies": {
|
|
36
|
-
"@types/node": "^
|
|
37
|
-
"ava": "^
|
|
49
|
+
"@types/node": "^26.0.1",
|
|
50
|
+
"ava": "^8.0.1",
|
|
38
51
|
"c8": "^11.0.0",
|
|
39
|
-
"tinybench": "^6.0.
|
|
40
|
-
"type-fest": "^5.
|
|
52
|
+
"tinybench": "^6.0.2",
|
|
53
|
+
"type-fest": "^5.7.0"
|
|
41
54
|
},
|
|
42
55
|
"scripts": {
|
|
43
56
|
"watch": "pnpm build:ts --watch",
|
|
@@ -45,7 +58,7 @@
|
|
|
45
58
|
"build": "pnpm build:ts --emitDeclarationOnly && node ./esbuild.config.js",
|
|
46
59
|
"deps": "pnpm dlx madge --circular --warning ./src",
|
|
47
60
|
"clean": "rimraf dist",
|
|
48
|
-
"test": "c8 ava",
|
|
61
|
+
"test": "FORCE_COLOR=1 c8 ava --no-worker-threads",
|
|
49
62
|
"bench": "node ./bench/index.ts"
|
|
50
63
|
}
|
|
51
64
|
}
|
|
@@ -116,10 +116,10 @@ export function parseDiagnostics(
|
|
|
116
116
|
diagnostics: Uint32Array,
|
|
117
117
|
filter?: (code: DiagnosticCode) => boolean,
|
|
118
118
|
): ParsedDiagnostics {
|
|
119
|
-
const parsed = [];
|
|
119
|
+
const parsed: Array<Writable<SourceDiagnostic & SourceReference>> = [];
|
|
120
120
|
const bufLen = diagnostics.length;
|
|
121
121
|
for (let i = 0; i < bufLen; i += 5) {
|
|
122
|
-
const code = diagnostics[i + 4]
|
|
122
|
+
const code: DiagnosticCode = diagnostics[i + 4]!;
|
|
123
123
|
if (filter && !filter(code)) {
|
|
124
124
|
continue;
|
|
125
125
|
}
|
|
@@ -193,7 +193,7 @@ export class Emitter {
|
|
|
193
193
|
const opcode = opcode_raw & 0x7f;
|
|
194
194
|
const wide = opcode_raw >= 0x80;
|
|
195
195
|
const read = () => this.readParam(wide);
|
|
196
|
-
let code
|
|
196
|
+
let code: string;
|
|
197
197
|
switch (opcode) {
|
|
198
198
|
case OpCode.Field: {
|
|
199
199
|
const field = read();
|
|
@@ -232,7 +232,7 @@ export class Emitter {
|
|
|
232
232
|
const opcode = opcode_raw & 0x7f;
|
|
233
233
|
const wide = opcode_raw >= 0x80;
|
|
234
234
|
const read = () => this.readParam(wide);
|
|
235
|
-
let code
|
|
235
|
+
let code: string;
|
|
236
236
|
switch (opcode) {
|
|
237
237
|
case OpCode.FieldOpt:
|
|
238
238
|
case OpCode.Field: {
|
|
@@ -299,7 +299,7 @@ export class Emitter {
|
|
|
299
299
|
const opcode = opcode_raw & 0x7f;
|
|
300
300
|
const wide = opcode_raw >= 0x80;
|
|
301
301
|
const read = () => this.readParam(wide);
|
|
302
|
-
let code
|
|
302
|
+
let code: string;
|
|
303
303
|
switch (opcode) {
|
|
304
304
|
case OpCode.Item: {
|
|
305
305
|
const value = read();
|
|
@@ -355,7 +355,7 @@ export class Emitter {
|
|
|
355
355
|
const read = () => this.readParam(wide);
|
|
356
356
|
const readIndex = () => this.readIndex(wide);
|
|
357
357
|
const ident = this.ident();
|
|
358
|
-
let code
|
|
358
|
+
let code: string;
|
|
359
359
|
let reg = 0;
|
|
360
360
|
switch (opcode) {
|
|
361
361
|
case OpCode.FuncVarg:
|
|
@@ -641,7 +641,7 @@ export class Emitter {
|
|
|
641
641
|
}
|
|
642
642
|
case OpCode.Module: {
|
|
643
643
|
reg = read();
|
|
644
|
-
const nameIdx =
|
|
644
|
+
const nameIdx = readIndex();
|
|
645
645
|
const name = this.constLits[nameIdx];
|
|
646
646
|
code = `${this.wv(reg)} = $Module(${name}, {`;
|
|
647
647
|
break;
|
|
@@ -4,11 +4,20 @@ import { emit } from './emit/index.js';
|
|
|
4
4
|
import { createScript, type VmScript } from './create-script.js';
|
|
5
5
|
import { DiagnosticCode, formatDiagnostics, parseDiagnostics } from './diagnostic.js';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
/** 编译错误 */
|
|
8
|
+
export class CompileError extends Error {
|
|
9
|
+
constructor(
|
|
10
|
+
source: ScriptInput,
|
|
11
|
+
diagnostics: Uint32Array,
|
|
12
|
+
readonly fileName: string | undefined,
|
|
13
|
+
) {
|
|
14
|
+
const parsed = parseDiagnostics(source, diagnostics);
|
|
15
|
+
const messages = formatDiagnostics(parsed.errors, source, fileName);
|
|
16
|
+
super(`Failed to compile:\n${messages.join('\n')}`);
|
|
17
|
+
this.diagnostics = messages;
|
|
18
|
+
}
|
|
19
|
+
readonly diagnostics: readonly string[];
|
|
20
|
+
override readonly name = 'CompileError';
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
/**
|
|
@@ -20,7 +29,7 @@ export function emitScript(
|
|
|
20
29
|
options: TranspileOptions,
|
|
21
30
|
): VmScript {
|
|
22
31
|
if (!code) {
|
|
23
|
-
|
|
32
|
+
throw new CompileError(source, diagnostics, options.fileName);
|
|
24
33
|
}
|
|
25
34
|
const sourcemaps = options.sourceMap
|
|
26
35
|
? parseDiagnostics(source, diagnostics, (c) => c === DiagnosticCode.SourceMap).sourcemaps
|
package/src/compiler/index.ts
CHANGED
|
@@ -4,10 +4,11 @@ import { createScript, type VmScript } from './create-script.js';
|
|
|
4
4
|
import { compileFast } from './compile-fast.js';
|
|
5
5
|
import { generateBytecode, generateBytecodeSync } from './generate-bytecode.js';
|
|
6
6
|
import { compileWorker } from './worker-manager.js';
|
|
7
|
-
import { emitScript,
|
|
7
|
+
import { emitScript, CompileError } from './emit-script.js';
|
|
8
8
|
|
|
9
9
|
export * from './types.js';
|
|
10
10
|
export type { VmScript };
|
|
11
|
+
export { CompileError };
|
|
11
12
|
|
|
12
13
|
// 目前编译速度约 2000kB/s
|
|
13
14
|
const WORKER_MIN_LEN = typeof Worker != 'function' ? Number.MAX_VALUE : 1024;
|
|
@@ -39,7 +40,7 @@ export async function compile(this: void, source: ScriptInput, options?: Transpi
|
|
|
39
40
|
}
|
|
40
41
|
const [target, diagnostics] = await compileWorker(source, options);
|
|
41
42
|
if (target == null) {
|
|
42
|
-
|
|
43
|
+
throw new CompileError(source, diagnostics, options.fileName);
|
|
43
44
|
}
|
|
44
45
|
return createScript(source, options.input_mode ?? 'Script', target);
|
|
45
46
|
}
|
package/src/helpers/analyze.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { REG_IDENTIFIER } from '@mirascript/constants';
|
|
1
2
|
import { generateBytecodeSync } from '../compiler/generate-bytecode.js';
|
|
2
3
|
import { DiagnosticCode, parseDiagnostics } from '../compiler/diagnostic.js';
|
|
3
|
-
import { REG_IDENTIFIER } from './constants.js';
|
|
4
4
|
import type { InputMode } from '../compiler/types.js';
|
|
5
5
|
|
|
6
6
|
/** 一个访问链,第一个元素为全局变量名称 */
|
package/src/helpers/constants.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
export const
|
|
2
|
-
export const REG_ORDINAL =
|
|
3
|
-
/(?:214748364[0-7]|21474836[0-3]\d|2147483[0-5]\d{2}|214748[0-2]\d{3}|21474[0-7]\d{4}|2147[0-3]\d{5}|214[0-6]\d{6}|21[0-3]\d{7}|20\d{8}|1\d{9}|[1-9]\d{0,8}|0)/;
|
|
4
|
-
export const REG_IDENTIFIER_FULL = new RegExp(`^${REG_IDENTIFIER.source}$`, REG_IDENTIFIER.flags);
|
|
5
|
-
export const REG_ORDINAL_FULL = new RegExp(`^${REG_ORDINAL.source}$`, REG_ORDINAL.flags);
|
|
6
|
-
export const REG_WHITESPACE = /[ \t\v\f\r\n]/u;
|
|
7
|
-
export const REG_HEX = /0[xX][a-fA-F0-9_]*[a-fA-F0-9]/;
|
|
8
|
-
export const REG_OCT = /0[oO][0-7_]*[0-7]/;
|
|
9
|
-
export const REG_BIN = /0[bB][01_]*[01]/;
|
|
10
|
-
export const REG_NUMBER = /\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?[\d_]*\d)?/u;
|
|
11
|
-
export const VM_ARRAY_MAX_LENGTH = 0x800_0000; // 128 MB, v8's max array length
|
|
1
|
+
export const VM_ARRAY_MAX_LENGTH = 0x100_0000; // 16 M
|
|
12
2
|
|
|
13
3
|
export const kVmScript = Symbol.for('mirascript.vm.script');
|
|
14
4
|
export const kVmFunction = Symbol.for('mirascript.vm.function');
|
package/src/helpers/serialize.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { REG_IDENTIFIER_FULL, REG_ORDINAL_FULL } from '@mirascript/constants';
|
|
1
2
|
import type { VmArray, VmExtern, VmFunction, VmModule, VmAny, VmRecord } from '../vm/index.js';
|
|
2
|
-
import { REG_IDENTIFIER_FULL, REG_ORDINAL_FULL } from './constants.js';
|
|
3
3
|
import { entries, hasOwn, isFinite, isNaN } from '../helpers/utils.js';
|
|
4
4
|
import {
|
|
5
5
|
getVmFunctionInfo,
|
package/src/helpers/types.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
VmModule,
|
|
13
13
|
VmPrimitive,
|
|
14
14
|
VmRecord,
|
|
15
|
+
VmTypeName,
|
|
15
16
|
VmValue,
|
|
16
17
|
} from '../vm/types/index.js';
|
|
17
18
|
import type { VmWrapper } from '../vm/types/wrapper.js';
|
|
@@ -68,6 +69,7 @@ export function isVmCallable<E extends Function, F extends VmFunctionLike>(
|
|
|
68
69
|
/** 检查值是否为 Mirascript 原始值 */
|
|
69
70
|
export function isVmPrimitive(value: unknown): value is VmPrimitive {
|
|
70
71
|
if (value === null || typeof value == 'number' || typeof value == 'string' || typeof value == 'boolean') {
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
71
73
|
value as VmPrimitive satisfies typeof value;
|
|
72
74
|
value satisfies VmPrimitive;
|
|
73
75
|
return true;
|
|
@@ -265,3 +267,17 @@ export function isVmValue(value: unknown, checkDeep = false): value is VmValue {
|
|
|
265
267
|
if (value === undefined) return false;
|
|
266
268
|
return isVmAny(value, checkDeep);
|
|
267
269
|
}
|
|
270
|
+
|
|
271
|
+
/** 获取 MiraScript 类型 */
|
|
272
|
+
export function getVmType(value: VmValue): VmTypeName;
|
|
273
|
+
/** 获取 MiraScript 类型 */
|
|
274
|
+
export function getVmType(value: VmAny): VmTypeName | 'uninitialized';
|
|
275
|
+
/** 获取 MiraScript 类型 */
|
|
276
|
+
export function getVmType(value: VmAny): VmTypeName | 'uninitialized' {
|
|
277
|
+
if (value === undefined) return 'uninitialized';
|
|
278
|
+
if (value === null) return 'nil';
|
|
279
|
+
if (isVmWrapper(value)) return value.type;
|
|
280
|
+
if (isVmArray(value)) return 'array';
|
|
281
|
+
if (typeof value == 'object') return 'record';
|
|
282
|
+
return typeof value as VmTypeName;
|
|
283
|
+
}
|
package/src/helpers/utils.ts
CHANGED
|
@@ -8,7 +8,8 @@ export const {
|
|
|
8
8
|
POSITIVE_INFINITY: PositiveInfinity,
|
|
9
9
|
NEGATIVE_INFINITY: NegativeInfinity,
|
|
10
10
|
} = Number;
|
|
11
|
-
export const { hasOwn, keys, values, entries, create, fromEntries, defineProperty, getOwnPropertyNames } =
|
|
11
|
+
export const { hasOwn, keys, values, entries, create, fromEntries, defineProperty, getOwnPropertyNames, freeze } =
|
|
12
|
+
Object;
|
|
12
13
|
export const { apply, getPrototypeOf } = Reflect;
|
|
13
14
|
|
|
14
15
|
/**
|
package/src/vm/checkpoint.ts
CHANGED
|
@@ -18,7 +18,7 @@ let cpDepth = 0;
|
|
|
18
18
|
let cp = CP_UNSET | 0;
|
|
19
19
|
let cpCounter = 0;
|
|
20
20
|
/** 检查点 */
|
|
21
|
-
export
|
|
21
|
+
export const Cp = (): void => {
|
|
22
22
|
// 不是每次都查时间
|
|
23
23
|
if ((cpCounter = (cpCounter + 1) | 0) % (cpInterval | 0) !== 0) {
|
|
24
24
|
return;
|
|
@@ -29,9 +29,9 @@ export function Cp(): void {
|
|
|
29
29
|
} else if ((timestamp() | 0) - (cp | 0) >= (cpTimeout | 0)) {
|
|
30
30
|
throw new RangeError('Execution timed out');
|
|
31
31
|
}
|
|
32
|
-
}
|
|
32
|
+
};
|
|
33
33
|
/** 检查点 */
|
|
34
|
-
export
|
|
34
|
+
export const CpEnter = (): void => {
|
|
35
35
|
cpDepth = (cpDepth | 0) + 1;
|
|
36
36
|
if ((cpDepth | 0) <= 1) {
|
|
37
37
|
cp = CP_UNSET | 0;
|
|
@@ -42,9 +42,9 @@ export function CpEnter(): void {
|
|
|
42
42
|
} else {
|
|
43
43
|
Cp();
|
|
44
44
|
}
|
|
45
|
-
}
|
|
45
|
+
};
|
|
46
46
|
/** 检查点 */
|
|
47
|
-
export
|
|
47
|
+
export const CpExit = (): void => {
|
|
48
48
|
cpDepth = (cpDepth | 0) - 1;
|
|
49
49
|
if ((cpDepth | 0) < 1) {
|
|
50
50
|
cp = CP_UNSET | 0;
|
|
@@ -53,7 +53,7 @@ export function CpExit(): void {
|
|
|
53
53
|
} else {
|
|
54
54
|
Cp();
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
};
|
|
57
57
|
|
|
58
58
|
const INT_MAX = 0x7fff_ffff;
|
|
59
59
|
/** 设置检查点超时时间 */
|
|
@@ -36,7 +36,7 @@ export const map = VmLib(
|
|
|
36
36
|
f: 'fn(value: any, key: number | string, input: type(data)) -> any',
|
|
37
37
|
},
|
|
38
38
|
returnsType: 'type(data)',
|
|
39
|
-
examples: ['map([1, 2, 3], fn
|
|
39
|
+
examples: ['map([1, 2, 3], fn { it * it }) // [1, 4, 9]'],
|
|
40
40
|
},
|
|
41
41
|
);
|
|
42
42
|
|
|
@@ -57,7 +57,7 @@ export const filter = VmLib(
|
|
|
57
57
|
predicate: 'fn(value: any, key: number | string, input: type(data)) -> boolean',
|
|
58
58
|
},
|
|
59
59
|
returnsType: 'type(data)',
|
|
60
|
-
examples: ['filter([1, 2, 3, 4], fn
|
|
60
|
+
examples: ['filter([1, 2, 3, 4], fn { it % 2 == 0 }) // [2, 4]'],
|
|
61
61
|
},
|
|
62
62
|
);
|
|
63
63
|
|
|
@@ -68,7 +68,7 @@ export const filter_map = VmLib(
|
|
|
68
68
|
return ret ?? undefined;
|
|
69
69
|
}),
|
|
70
70
|
{
|
|
71
|
-
summary: '对数组或记录中的每个元素应用函数,并返回非 nil 的结果',
|
|
71
|
+
summary: '对数组或记录中的每个元素应用函数,并返回非 `nil` 的结果',
|
|
72
72
|
params: {
|
|
73
73
|
data: '要映射的数组或记录',
|
|
74
74
|
f: '应用于每个元素的函数',
|
|
@@ -78,6 +78,6 @@ export const filter_map = VmLib(
|
|
|
78
78
|
f: 'fn(value: any, key: number | string, input: type(data)) -> any | nil',
|
|
79
79
|
},
|
|
80
80
|
returnsType: 'type(data)',
|
|
81
|
-
examples: ['filter_map([1, 2, 3], fn
|
|
81
|
+
examples: ['filter_map([1, 2, 3], fn {\n if it % 2 == 0 { it * it } else { nil } \n}) // [4]'],
|
|
82
82
|
},
|
|
83
83
|
);
|
|
@@ -39,7 +39,7 @@ const withInner = (obj: VmConst | undefined, key: VmArray, keyIndex: number, val
|
|
|
39
39
|
}
|
|
40
40
|
if (isArray(result)) {
|
|
41
41
|
const index = arrIndex(k);
|
|
42
|
-
while (index
|
|
42
|
+
while (index >= result.length) {
|
|
43
43
|
result.push(null);
|
|
44
44
|
}
|
|
45
45
|
result[index] = withInner(result[index], key, keyIndex + 1, value);
|
|
@@ -64,7 +64,8 @@ const normalizeEntries = (data: VmConst, entries: Array<VmValue | undefined>): M
|
|
|
64
64
|
if (isVmArray(key)) {
|
|
65
65
|
if (key.length === 0 || key.includes(null) || key.includes(undefined)) {
|
|
66
66
|
continue;
|
|
67
|
-
}
|
|
67
|
+
}
|
|
68
|
+
if (key.length === 1) {
|
|
68
69
|
key = key[0]!;
|
|
69
70
|
}
|
|
70
71
|
}
|
package/src/vm/lib/loader.ts
CHANGED
|
@@ -59,8 +59,8 @@ export type ToWrappedModule<T extends Record<string, RawValue>> = VmBuiltinModul
|
|
|
59
59
|
|
|
60
60
|
/** 创建模块 */
|
|
61
61
|
export function createModule<const T extends Record<string, RawValue>>(name: string, lib: T): ToWrappedModule<T> {
|
|
62
|
-
const mod = create(null)
|
|
63
|
-
const descriptions
|
|
62
|
+
const mod: Record<string, VmImmutable> = create(null);
|
|
63
|
+
const descriptions: Record<string, string | undefined> = create(null);
|
|
64
64
|
for (const [key, value] of entries(lib)) {
|
|
65
65
|
const [wrappedValue, description] = wrapEntry(key, value, name);
|
|
66
66
|
mod[key] = wrappedValue;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isFinite } from '../../helpers/utils.js';
|
|
2
|
+
import { VM_ARRAY_MAX_LENGTH } from '../../helpers/constants.js';
|
|
3
|
+
import type { VmAny, VmArray } from '../types/index.js';
|
|
4
|
+
import { $ToNumber } from './convert.js';
|
|
5
|
+
|
|
6
|
+
const assertArrayLength = (len: number) => {
|
|
7
|
+
if (len > VM_ARRAY_MAX_LENGTH) {
|
|
8
|
+
throw new RangeError(`Array length exceeds maximum limit of ${VM_ARRAY_MAX_LENGTH}`);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const isEmptyRange = (start: number, end: number) => {
|
|
12
|
+
return !isFinite(start) || !isFinite(end) || start > end;
|
|
13
|
+
};
|
|
14
|
+
/** 构造范围数组 */
|
|
15
|
+
export const $ArrayRange = (start: VmAny, end: VmAny): VmArray => {
|
|
16
|
+
const s = $ToNumber(start);
|
|
17
|
+
const e = $ToNumber(end);
|
|
18
|
+
if (isEmptyRange(s, e)) return [];
|
|
19
|
+
assertArrayLength(e - s + 1);
|
|
20
|
+
const arr = [];
|
|
21
|
+
for (let i = s; i <= e; i++) {
|
|
22
|
+
arr.push(i);
|
|
23
|
+
}
|
|
24
|
+
return arr;
|
|
25
|
+
};
|
|
26
|
+
/** 构造范围数组(不包含结束值) */
|
|
27
|
+
export const $ArrayRangeExclusive = (start: VmAny, end: VmAny): VmArray => {
|
|
28
|
+
const s = $ToNumber(start);
|
|
29
|
+
const e = $ToNumber(end);
|
|
30
|
+
if (isEmptyRange(s, e)) return [];
|
|
31
|
+
assertArrayLength(e - s);
|
|
32
|
+
const arr = [];
|
|
33
|
+
for (let i = s; i < e; i++) {
|
|
34
|
+
arr.push(i);
|
|
35
|
+
}
|
|
36
|
+
return arr;
|
|
37
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VmError } from '../../helpers/error.js';
|
|
2
2
|
import { display } from '../../helpers/serialize.js';
|
|
3
|
-
import {
|
|
3
|
+
import { isVmExtern, isVmConst } from '../../helpers/types.js';
|
|
4
4
|
import type { VmExtern, VmFunction, VmAny, VmArray, VmValue } from '../types/index.js';
|
|
5
5
|
import { $AssertInit } from './common.js';
|
|
6
6
|
|
|
@@ -28,14 +28,15 @@ type CallReturn<T extends VmValue> =
|
|
|
28
28
|
|
|
29
29
|
/** 调用函数 */
|
|
30
30
|
export function $Call<T extends VmValue, A extends readonly VmValue[]>(func: T, args: A): CallReturn<T> {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const argsLen = args.length;
|
|
32
|
+
for (let i = 0; i < argsLen; i++) {
|
|
33
|
+
$AssertInit(args[i]);
|
|
33
34
|
}
|
|
34
|
-
if (
|
|
35
|
-
return (func
|
|
35
|
+
if (typeof func == 'function') {
|
|
36
|
+
return (func(...args) ?? null) as CallReturn<T>;
|
|
36
37
|
}
|
|
37
|
-
if (
|
|
38
|
-
return (func(
|
|
38
|
+
if (isVmExtern(func)) {
|
|
39
|
+
return (func.call(args) ?? null) as CallReturn<T>;
|
|
39
40
|
}
|
|
40
41
|
throw new VmError(`Value is not callable: ${display(func)}`, null);
|
|
41
42
|
}
|
|
@@ -2,6 +2,6 @@ import { VmError } from '../../helpers/error.js';
|
|
|
2
2
|
import type { VmValue } from '../types/index.js';
|
|
3
3
|
|
|
4
4
|
/** 断言值已初始化 */
|
|
5
|
-
export
|
|
5
|
+
export const $AssertInit: <T extends VmValue>(value: T | undefined) => asserts value is T = (value) => {
|
|
6
6
|
if (value === undefined) throw new VmError(`Uninitialized value`, null);
|
|
7
|
-
}
|
|
7
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VmError } from '../../helpers/error.js';
|
|
2
|
-
import { hasOwnEnumerable,
|
|
2
|
+
import { hasOwnEnumerable, NotNumber, keys, create, isFinite } from '../../helpers/utils.js';
|
|
3
3
|
import { toNumber, toString } from '../../helpers/convert/index.js';
|
|
4
4
|
import { display } from '../../helpers/serialize.js';
|
|
5
5
|
import {
|
|
@@ -22,7 +22,7 @@ const { trunc } = Math;
|
|
|
22
22
|
const { at } = Array.prototype;
|
|
23
23
|
|
|
24
24
|
/** 检查值是否在可迭代对象中 */
|
|
25
|
-
export
|
|
25
|
+
export const $In = (value: VmAny, iterable: VmAny): boolean => {
|
|
26
26
|
$AssertInit(value);
|
|
27
27
|
$AssertInit(iterable);
|
|
28
28
|
if (iterable == null) return false;
|
|
@@ -42,22 +42,22 @@ export function $In(value: VmAny, iterable: VmAny): boolean {
|
|
|
42
42
|
const key = toString(value, undefined);
|
|
43
43
|
if (isVmWrapper(iterable)) return iterable.has(key);
|
|
44
44
|
return hasOwnEnumerable(iterable satisfies VmRecord, key);
|
|
45
|
-
}
|
|
45
|
+
};
|
|
46
46
|
|
|
47
47
|
/** 获取值的长度 */
|
|
48
|
-
export
|
|
48
|
+
export const $Length = (value: VmAny): number => {
|
|
49
49
|
$AssertInit(value);
|
|
50
50
|
if (isVmArray(value)) return value.length;
|
|
51
51
|
if (isVmRecord(value)) return keys(value).length;
|
|
52
52
|
if (isVmWrapper(value)) return value.keys().length;
|
|
53
53
|
|
|
54
54
|
throw new VmError(`Value has no length: ${display(value)}`, 0);
|
|
55
|
-
}
|
|
55
|
+
};
|
|
56
56
|
|
|
57
57
|
/** 删除记录中的指定字段 */
|
|
58
|
-
export
|
|
58
|
+
export const $Omit = (value: VmAny, omitted: ReadonlyArray<number | string>): VmRecord => {
|
|
59
59
|
$AssertInit(value);
|
|
60
|
-
if (
|
|
60
|
+
if (!isVmRecord(value)) return {};
|
|
61
61
|
const result: Record<string, VmConst> = {};
|
|
62
62
|
const valueKeys = keys(value);
|
|
63
63
|
const omittedSet = new Set(omitted.map($ToString));
|
|
@@ -68,12 +68,12 @@ export function $Omit(value: VmAny, omitted: ReadonlyArray<number | string>): Vm
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
return result;
|
|
71
|
-
}
|
|
71
|
+
};
|
|
72
72
|
|
|
73
73
|
/** 选择记录中的指定字段 */
|
|
74
|
-
export
|
|
74
|
+
export const $Pick = (value: VmAny, picked: ReadonlyArray<number | string>): VmRecord => {
|
|
75
75
|
$AssertInit(value);
|
|
76
|
-
if (
|
|
76
|
+
if (!isVmRecord(value)) return {};
|
|
77
77
|
const result: Record<string, VmConst> = {};
|
|
78
78
|
for (const key of picked) {
|
|
79
79
|
const k = $ToString(key);
|
|
@@ -82,23 +82,23 @@ export function $Pick(value: VmAny, picked: ReadonlyArray<number | string>): VmR
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
return result;
|
|
85
|
-
}
|
|
85
|
+
};
|
|
86
86
|
/** 检查是否拥有字段 */
|
|
87
|
-
export
|
|
87
|
+
export const $Has = (obj: VmAny, key: VmAny): boolean => {
|
|
88
88
|
$AssertInit(obj);
|
|
89
89
|
const pk = $ToString(key);
|
|
90
90
|
if (obj == null || typeof obj != 'object') return false;
|
|
91
91
|
if (isVmWrapper(obj)) return obj.has(pk);
|
|
92
92
|
return hasOwnEnumerable(obj, pk);
|
|
93
|
-
}
|
|
93
|
+
};
|
|
94
94
|
|
|
95
95
|
/** 获取字段 */
|
|
96
|
-
export
|
|
96
|
+
export const $Get = (obj: VmAny, key: VmAny): VmValue => {
|
|
97
97
|
$AssertInit(obj);
|
|
98
98
|
if (isVmArray(obj)) {
|
|
99
99
|
$AssertInit(key);
|
|
100
100
|
const index = toNumber(key, NotNumber);
|
|
101
|
-
if (
|
|
101
|
+
if (!isFinite(index)) return null;
|
|
102
102
|
return $El((at.call(obj, trunc(index)) ?? null) as VmAny);
|
|
103
103
|
}
|
|
104
104
|
const pk = $ToString(key);
|
|
@@ -116,26 +116,25 @@ export function $Get(obj: VmAny, key: VmAny): VmValue {
|
|
|
116
116
|
}
|
|
117
117
|
if (!hasOwnEnumerable(obj, pk)) return null;
|
|
118
118
|
return $El(obj[pk] ?? null);
|
|
119
|
-
}
|
|
119
|
+
};
|
|
120
120
|
/** 设置字段 */
|
|
121
|
-
export
|
|
121
|
+
export const $Set = (obj: VmAny, key: VmAny, value: VmAny): void => {
|
|
122
122
|
$AssertInit(obj);
|
|
123
123
|
$AssertInit(value);
|
|
124
124
|
const pk = $ToString(key);
|
|
125
|
-
if (obj == null) return;
|
|
126
125
|
if (!isVmExtern(obj)) throw new VmError(`Expected extern, got ${display(obj)}`, undefined);
|
|
127
126
|
obj.set(pk, value);
|
|
128
|
-
}
|
|
127
|
+
};
|
|
129
128
|
/** 获取可迭代对象 */
|
|
130
|
-
export
|
|
129
|
+
export const $Iterable = (value: VmAny): Iterable<VmValue | undefined> => {
|
|
131
130
|
$AssertInit(value);
|
|
132
131
|
if (isVmWrapper(value)) return value.keys();
|
|
133
132
|
if (isVmArray(value)) return value;
|
|
134
133
|
if (value != null && typeof value == 'object') return keys(value);
|
|
135
134
|
throw new VmError(`Value is not iterable: ${display(value)}`, isVmFunction(value) ? [] : [value]);
|
|
136
|
-
}
|
|
135
|
+
};
|
|
137
136
|
/** 展开记录 */
|
|
138
|
-
export
|
|
137
|
+
export const $RecordSpread = (record: VmAny): VmRecord | null => {
|
|
139
138
|
$AssertInit(record);
|
|
140
139
|
if (record == null || isVmRecord(record)) return record;
|
|
141
140
|
if (isVmArray(record)) {
|
|
@@ -158,10 +157,10 @@ export function $RecordSpread(record: VmAny): VmRecord | null {
|
|
|
158
157
|
return result;
|
|
159
158
|
}
|
|
160
159
|
throw new VmError(`Expected record, array, extern or nil, got ${display(record)}`, null);
|
|
161
|
-
}
|
|
160
|
+
};
|
|
162
161
|
|
|
163
162
|
/** 展开数组 */
|
|
164
|
-
export
|
|
163
|
+
export const $ArraySpread = (array: VmAny): Iterable<VmConst | undefined> => {
|
|
165
164
|
$AssertInit(array);
|
|
166
165
|
if (array == null) return [];
|
|
167
166
|
if (isVmArray(array)) return array;
|
|
@@ -182,4 +181,4 @@ export function $ArraySpread(array: VmAny): Iterable<VmConst | undefined> {
|
|
|
182
181
|
}
|
|
183
182
|
}
|
|
184
183
|
throw new VmError(`Expected array, iterable extern or nil, got ${display(array)}`, []);
|
|
185
|
-
}
|
|
184
|
+
};
|
|
@@ -3,25 +3,25 @@ import type { VmAny } from '../types/index.js';
|
|
|
3
3
|
import { $AssertInit } from './common.js';
|
|
4
4
|
|
|
5
5
|
/** 转换为布尔值 */
|
|
6
|
-
export
|
|
6
|
+
export const $ToBoolean = (value: VmAny): boolean => {
|
|
7
7
|
if (typeof value == 'boolean') return value;
|
|
8
8
|
$AssertInit(value);
|
|
9
9
|
return toBoolean(value, undefined);
|
|
10
|
-
}
|
|
10
|
+
};
|
|
11
11
|
/** 转换为字符串 */
|
|
12
|
-
export
|
|
12
|
+
export const $ToString = (value: VmAny): string => {
|
|
13
13
|
if (typeof value == 'string') return value;
|
|
14
14
|
$AssertInit(value);
|
|
15
15
|
return toString(value, undefined);
|
|
16
|
-
}
|
|
16
|
+
};
|
|
17
17
|
/** 转换为数字 */
|
|
18
|
-
export
|
|
18
|
+
export const $ToNumber = (value: VmAny): number => {
|
|
19
19
|
if (typeof value == 'number') return value;
|
|
20
20
|
$AssertInit(value);
|
|
21
21
|
return toNumber(value, undefined);
|
|
22
|
-
}
|
|
22
|
+
};
|
|
23
23
|
/** 格式化值 */
|
|
24
|
-
export
|
|
24
|
+
export const $Format = (value: VmAny, format: string | null): string => {
|
|
25
25
|
$AssertInit(value);
|
|
26
26
|
return toFormat(value, format);
|
|
27
|
-
}
|
|
27
|
+
};
|