@aster-cloud/aster-lang-ts 1.0.28 → 1.0.30
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/scripts/generate_error_codes.js +49 -6
- package/dist/scripts/generate_error_codes.js.map +1 -1
- package/dist/src/ai/providers/openai.d.ts +10 -0
- package/dist/src/ai/providers/openai.d.ts.map +1 -1
- package/dist/src/ai/providers/openai.js +4 -0
- package/dist/src/ai/providers/openai.js.map +1 -1
- package/dist/src/config/lexicons/regex-guard.d.ts.map +1 -1
- package/dist/src/config/lexicons/regex-guard.js +128 -0
- package/dist/src/config/lexicons/regex-guard.js.map +1 -1
- package/dist/src/core/interpreter.d.ts.map +1 -1
- package/dist/src/core/interpreter.js +221 -27
- package/dist/src/core/interpreter.js.map +1 -1
- package/dist/src/cst/cst_builder.js +0 -14
- package/dist/src/cst/cst_builder.js.map +1 -1
- package/dist/src/cst/cst_printer.d.ts.map +1 -1
- package/dist/src/cst/cst_printer.js +20 -3
- package/dist/src/cst/cst_printer.js.map +1 -1
- package/dist/src/diagnostics/error_codes.d.ts +3 -3
- package/dist/src/diagnostics/error_codes.d.ts.map +1 -1
- package/dist/src/diagnostics/error_codes.js +509 -78
- package/dist/src/diagnostics/error_codes.js.map +1 -1
- package/dist/src/formatter.d.ts +7 -0
- package/dist/src/formatter.d.ts.map +1 -1
- package/dist/src/formatter.js +175 -5
- package/dist/src/formatter.js.map +1 -1
- package/dist/src/frontend/canonicalizer.d.ts.map +1 -1
- package/dist/src/frontend/canonicalizer.js +47 -6
- package/dist/src/frontend/canonicalizer.js.map +1 -1
- package/dist/src/frontend/lexer.d.ts +2 -1
- package/dist/src/frontend/lexer.d.ts.map +1 -1
- package/dist/src/frontend/lexer.js +10 -3
- package/dist/src/frontend/lexer.js.map +1 -1
- package/dist/src/frontend/transformers.d.ts.map +1 -1
- package/dist/src/frontend/transformers.js +50 -3
- package/dist/src/frontend/transformers.js.map +1 -1
- package/dist/src/lower_to_core.js +101 -4
- package/dist/src/lower_to_core.js.map +1 -1
- package/dist/src/lsp/canonicalize-options.d.ts +26 -0
- package/dist/src/lsp/canonicalize-options.d.ts.map +1 -0
- package/dist/src/lsp/canonicalize-options.js +14 -0
- package/dist/src/lsp/canonicalize-options.js.map +1 -0
- package/dist/src/lsp/completion.d.ts.map +1 -1
- package/dist/src/lsp/completion.js +22 -4
- package/dist/src/lsp/completion.js.map +1 -1
- package/dist/src/lsp/server.js +29 -1
- package/dist/src/lsp/server.js.map +1 -1
- package/dist/src/lsp/tenant-init.d.ts +55 -0
- package/dist/src/lsp/tenant-init.d.ts.map +1 -0
- package/dist/src/lsp/tenant-init.js +88 -0
- package/dist/src/lsp/tenant-init.js.map +1 -0
- package/dist/src/mapping/candidate-generator.d.ts +84 -0
- package/dist/src/mapping/candidate-generator.d.ts.map +1 -0
- package/dist/src/mapping/candidate-generator.js +114 -0
- package/dist/src/mapping/candidate-generator.js.map +1 -0
- package/dist/src/mapping/entity-proposer.d.ts +85 -0
- package/dist/src/mapping/entity-proposer.d.ts.map +1 -0
- package/dist/src/mapping/entity-proposer.js +162 -0
- package/dist/src/mapping/entity-proposer.js.map +1 -0
- package/dist/src/mapping/mapping-ir.d.ts +85 -0
- package/dist/src/mapping/mapping-ir.d.ts.map +1 -0
- package/dist/src/mapping/mapping-ir.js +195 -0
- package/dist/src/mapping/mapping-ir.js.map +1 -0
- package/dist/src/mapping/proof-ir.d.ts +125 -0
- package/dist/src/mapping/proof-ir.d.ts.map +1 -0
- package/dist/src/mapping/proof-ir.js +48 -0
- package/dist/src/mapping/proof-ir.js.map +1 -0
- package/dist/src/mapping/quantity-ir.d.ts +93 -0
- package/dist/src/mapping/quantity-ir.d.ts.map +1 -0
- package/dist/src/mapping/quantity-ir.js +151 -0
- package/dist/src/mapping/quantity-ir.js.map +1 -0
- package/dist/src/mapping/source-ir.d.ts +82 -0
- package/dist/src/mapping/source-ir.d.ts.map +1 -0
- package/dist/src/mapping/source-ir.js +231 -0
- package/dist/src/mapping/source-ir.js.map +1 -0
- package/dist/src/nodeid/change-impact.d.ts +77 -0
- package/dist/src/nodeid/change-impact.d.ts.map +1 -0
- package/dist/src/nodeid/change-impact.js +132 -0
- package/dist/src/nodeid/change-impact.js.map +1 -0
- package/dist/src/nodeid/node-id-map.d.ts +69 -0
- package/dist/src/nodeid/node-id-map.d.ts.map +1 -0
- package/dist/src/nodeid/node-id-map.js +193 -0
- package/dist/src/nodeid/node-id-map.js.map +1 -0
- package/dist/src/parser/decl-parser.d.ts.map +1 -1
- package/dist/src/parser/decl-parser.js +10 -4
- package/dist/src/parser/decl-parser.js.map +1 -1
- package/dist/src/parser/expr-stmt-parser.d.ts.map +1 -1
- package/dist/src/parser/expr-stmt-parser.js +36 -24
- package/dist/src/parser/expr-stmt-parser.js.map +1 -1
- package/dist/src/parser/span-utils.d.ts +25 -0
- package/dist/src/parser/span-utils.d.ts.map +1 -1
- package/dist/src/parser/span-utils.js +43 -0
- package/dist/src/parser/span-utils.js.map +1 -1
- package/dist/src/parser/type-parser.d.ts.map +1 -1
- package/dist/src/parser/type-parser.js +21 -0
- package/dist/src/parser/type-parser.js.map +1 -1
- package/dist/src/typecheck/expression.d.ts.map +1 -1
- package/dist/src/typecheck/expression.js +26 -0
- package/dist/src/typecheck/expression.js.map +1 -1
- package/dist/test/fuzz/fuzz.test.js +62 -2
- package/dist/test/fuzz/fuzz.test.js.map +1 -1
- package/dist/test/unit/call-arity.test.d.ts +2 -0
- package/dist/test/unit/call-arity.test.d.ts.map +1 -0
- package/dist/test/unit/call-arity.test.js +71 -0
- package/dist/test/unit/call-arity.test.js.map +1 -0
- package/dist/test/unit/canonicalizer/canonicalizer.test.js +42 -4
- package/dist/test/unit/canonicalizer/canonicalizer.test.js.map +1 -1
- package/dist/test/unit/canonicalizer/line-preservation.test.d.ts +2 -0
- package/dist/test/unit/canonicalizer/line-preservation.test.d.ts.map +1 -0
- package/dist/test/unit/canonicalizer/line-preservation.test.js +71 -0
- package/dist/test/unit/canonicalizer/line-preservation.test.js.map +1 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.d.ts +2 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.d.ts.map +1 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.js +98 -0
- package/dist/test/unit/canonicalizer/marker-restore-linear.test.js.map +1 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.d.ts +2 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.d.ts.map +1 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.js +101 -0
- package/dist/test/unit/canonicalizer/possessive-anchor.test.js.map +1 -0
- package/dist/test/unit/formatter-redos.test.d.ts +2 -0
- package/dist/test/unit/formatter-redos.test.d.ts.map +1 -0
- package/dist/test/unit/formatter-redos.test.js +196 -0
- package/dist/test/unit/formatter-redos.test.js.map +1 -0
- package/dist/test/unit/interpreter/interpreter.test.js +7 -2
- package/dist/test/unit/interpreter/interpreter.test.js.map +1 -1
- package/dist/test/unit/lexer/bom.test.d.ts +2 -0
- package/dist/test/unit/lexer/bom.test.d.ts.map +1 -0
- package/dist/test/unit/lexer/bom.test.js +49 -0
- package/dist/test/unit/lexer/bom.test.js.map +1 -0
- package/dist/test/unit/list-get-bounds.test.d.ts +2 -0
- package/dist/test/unit/list-get-bounds.test.d.ts.map +1 -0
- package/dist/test/unit/list-get-bounds.test.js +66 -0
- package/dist/test/unit/list-get-bounds.test.js.map +1 -0
- package/dist/test/unit/long-bigint-exact.test.d.ts +2 -0
- package/dist/test/unit/long-bigint-exact.test.d.ts.map +1 -0
- package/dist/test/unit/long-bigint-exact.test.js +79 -0
- package/dist/test/unit/long-bigint-exact.test.js.map +1 -0
- package/dist/test/unit/lsp/completion-lexicon.test.d.ts +2 -0
- package/dist/test/unit/lsp/completion-lexicon.test.d.ts.map +1 -0
- package/dist/test/unit/lsp/completion-lexicon.test.js +83 -0
- package/dist/test/unit/lsp/completion-lexicon.test.js.map +1 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.d.ts +2 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.d.ts.map +1 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.js +187 -0
- package/dist/test/unit/lsp/tenant-vocabulary.test.js.map +1 -0
- package/dist/test/unit/map-builtins.test.js +163 -3
- package/dist/test/unit/map-builtins.test.js.map +1 -1
- package/dist/test/unit/map-structural-equality.test.d.ts +2 -0
- package/dist/test/unit/map-structural-equality.test.d.ts.map +1 -0
- package/dist/test/unit/map-structural-equality.test.js +64 -0
- package/dist/test/unit/map-structural-equality.test.js.map +1 -0
- package/dist/test/unit/map-type-guard.test.js +8 -5
- package/dist/test/unit/map-type-guard.test.js.map +1 -1
- package/dist/test/unit/mapping/candidate-generator.test.d.ts +2 -0
- package/dist/test/unit/mapping/candidate-generator.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/candidate-generator.test.js +177 -0
- package/dist/test/unit/mapping/candidate-generator.test.js.map +1 -0
- package/dist/test/unit/mapping/entity-proposer.test.d.ts +2 -0
- package/dist/test/unit/mapping/entity-proposer.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/entity-proposer.test.js +168 -0
- package/dist/test/unit/mapping/entity-proposer.test.js.map +1 -0
- package/dist/test/unit/mapping/mapping-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/mapping-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/mapping-ir.test.js +144 -0
- package/dist/test/unit/mapping/mapping-ir.test.js.map +1 -0
- package/dist/test/unit/mapping/proof-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/proof-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/proof-ir.test.js +119 -0
- package/dist/test/unit/mapping/proof-ir.test.js.map +1 -0
- package/dist/test/unit/mapping/quantity-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/quantity-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/quantity-ir.test.js +144 -0
- package/dist/test/unit/mapping/quantity-ir.test.js.map +1 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.d.ts +2 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.js +182 -0
- package/dist/test/unit/mapping/quantity-redos-property.test.js.map +1 -0
- package/dist/test/unit/mapping/redos-budget.test.d.ts +2 -0
- package/dist/test/unit/mapping/redos-budget.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/redos-budget.test.js +168 -0
- package/dist/test/unit/mapping/redos-budget.test.js.map +1 -0
- package/dist/test/unit/mapping/source-ir.test.d.ts +2 -0
- package/dist/test/unit/mapping/source-ir.test.d.ts.map +1 -0
- package/dist/test/unit/mapping/source-ir.test.js +113 -0
- package/dist/test/unit/mapping/source-ir.test.js.map +1 -0
- package/dist/test/unit/nodeid/change-impact.test.d.ts +2 -0
- package/dist/test/unit/nodeid/change-impact.test.d.ts.map +1 -0
- package/dist/test/unit/nodeid/change-impact.test.js +152 -0
- package/dist/test/unit/nodeid/change-impact.test.js.map +1 -0
- package/dist/test/unit/nodeid/rename-declaration.test.d.ts +2 -0
- package/dist/test/unit/nodeid/rename-declaration.test.d.ts.map +1 -0
- package/dist/test/unit/nodeid/rename-declaration.test.js +75 -0
- package/dist/test/unit/nodeid/rename-declaration.test.js.map +1 -0
- package/dist/test/unit/parse-type-recursion.test.d.ts +2 -0
- package/dist/test/unit/parse-type-recursion.test.d.ts.map +1 -0
- package/dist/test/unit/parse-type-recursion.test.js +58 -0
- package/dist/test/unit/parse-type-recursion.test.js.map +1 -0
- package/dist/test/unit/regex-guard.test.js +64 -0
- package/dist/test/unit/regex-guard.test.js.map +1 -1
- package/dist/test/unit/strict-int-coercion.test.d.ts +2 -0
- package/dist/test/unit/strict-int-coercion.test.d.ts.map +1 -0
- package/dist/test/unit/strict-int-coercion.test.js +96 -0
- package/dist/test/unit/strict-int-coercion.test.js.map +1 -0
- package/dist/test/unit/ufcs-receiver.test.d.ts +2 -0
- package/dist/test/unit/ufcs-receiver.test.d.ts.map +1 -0
- package/dist/test/unit/ufcs-receiver.test.js +75 -0
- package/dist/test/unit/ufcs-receiver.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -69,6 +69,56 @@ function decimalScale(scale) {
|
|
|
69
69
|
}
|
|
70
70
|
return n;
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* 严格整型换算——与 truffle `Builtins.toInt` 等价(issue #144)。
|
|
74
|
+
*
|
|
75
|
+
* ★为什么不能用裸 `Number()`:`Number("x")` 得 `NaN` 后**所有防线同时失效**——
|
|
76
|
+
* `NaN < MIN || NaN > MAX` 两侧都是 false 于是放行;`size > MAX_RANGE_SIZE` 同理,
|
|
77
|
+
* 然后循环零次返回 `[]`。非法输入不报错,反而产出「看起来正常」的决策值。
|
|
78
|
+
*
|
|
79
|
+
* 实测(修复前):
|
|
80
|
+
*
|
|
81
|
+
* Date.daysBetween("x","b") → success:true, value:NaN
|
|
82
|
+
* Date.addDays("x",5) greater than 0 → success:true, value:false
|
|
83
|
+
* List.length(List.range("a",5)) → success:true, value:0
|
|
84
|
+
*
|
|
85
|
+
* 而 truffle 同输入抛 `NumberFormatException`——**同一条规则两引擎一个静默出错答案、
|
|
86
|
+
* 一个响亮失败**。对合规引擎,静默的那个远更危险。
|
|
87
|
+
*
|
|
88
|
+
* 语义逐条对齐 truffle `toInt`:
|
|
89
|
+
* - number → 截断取整(对齐 Java `Number.intValue()`),但拒绝 NaN/Infinity;
|
|
90
|
+
* - bigint → 精确转换,超出安全整数范围拒绝(Long 已是 BigInt 表示,见 issue #142);
|
|
91
|
+
* - string → 严格整数解析(对齐 `Integer.parseInt`:不接受小数、十六进制、空串);
|
|
92
|
+
* - 其他 → 抛错。
|
|
93
|
+
*/
|
|
94
|
+
function toInt(v, ctx) {
|
|
95
|
+
const bad = () => {
|
|
96
|
+
throw new InterpreterError(`${ctx}: expected Int, got ${JSON.stringify(v) ?? String(v)}`);
|
|
97
|
+
};
|
|
98
|
+
if (typeof v === 'number') {
|
|
99
|
+
if (!Number.isFinite(v))
|
|
100
|
+
return bad();
|
|
101
|
+
return Math.trunc(v);
|
|
102
|
+
}
|
|
103
|
+
if (typeof v === 'bigint') {
|
|
104
|
+
if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER))
|
|
105
|
+
return bad();
|
|
106
|
+
return Number(v);
|
|
107
|
+
}
|
|
108
|
+
if (typeof v === 'string') {
|
|
109
|
+
// ★只接受**整数**字面量,与 Integer.parseInt 一致:
|
|
110
|
+
// "5" 收;"5.0"/"0x10"/""/" "/"5x" 拒。前后空白由 trim 容忍(parseInt 亦然)。
|
|
111
|
+
// 正则无歧义分支,线性匹配(避免 decimalScale 注释里记录过的 ReDoS)。
|
|
112
|
+
const t = v.trim();
|
|
113
|
+
if (!/^[+-]?\d+$/.test(t))
|
|
114
|
+
return bad();
|
|
115
|
+
const n = Number(t);
|
|
116
|
+
if (!Number.isSafeInteger(n))
|
|
117
|
+
return bad();
|
|
118
|
+
return n;
|
|
119
|
+
}
|
|
120
|
+
return bad();
|
|
121
|
+
}
|
|
72
122
|
/** 把任意操作数转成 Decimal(Int/Long 精确提升;Double 禁;已是 Decimal 原样)。 */
|
|
73
123
|
function toDecimalArg(v, ctx) {
|
|
74
124
|
if (v instanceof Decimal)
|
|
@@ -201,6 +251,12 @@ export function serializeGuestValue(v, depth = 0) {
|
|
|
201
251
|
if (depth > SERIALIZE_MAX_DEPTH) {
|
|
202
252
|
throw new InterpreterError('serializeGuestValue: value nesting exceeds max depth (cycle?)');
|
|
203
253
|
}
|
|
254
|
+
// ★BigInt(Long 的运行时表示)必须在委托 JSON.stringify 之前拦下——
|
|
255
|
+
// JSON.stringify(1n) 直接抛错。输出为无引号十进制字面量,与 JVM 侧
|
|
256
|
+
// Jackson 序列化 `long` 的形态一致(9223372036854775807,非 "…" 字符串)。
|
|
257
|
+
if (typeof v === 'bigint') {
|
|
258
|
+
return v.toString();
|
|
259
|
+
}
|
|
204
260
|
if (v instanceof GuestMap) {
|
|
205
261
|
const parts = [];
|
|
206
262
|
for (const [k, val] of v) {
|
|
@@ -305,8 +361,42 @@ function mapKey(k) {
|
|
|
305
361
|
throw new InterpreterError('Map key must not be null: null and the string "null" would collapse to the same key, '
|
|
306
362
|
+ 'silently overwriting each other. Use a non-null key, or Maybe/Option to express absence.');
|
|
307
363
|
}
|
|
364
|
+
// ★变体(Some/None/Ok/Err)作键时必须按**内容**归一,不能用 `String(obj)`
|
|
365
|
+
// ——JS 里它恒为 `"[object Object]"`,于是 `Some("null")` 与 `None`
|
|
366
|
+
// 塌陷到同一个槽位(`List.groupBy` 上实测两桶并成一桶)。这正是本函数
|
|
367
|
+
// 开头的 null 守卫要消灭的那类**静默丢数据**,只是发生在变体上。
|
|
368
|
+
//
|
|
369
|
+
// truffle 侧 `String.valueOf(map)` 天然渲染内容(`{__type=None}` /
|
|
370
|
+
// `{__type=Some, value=null}`),两键本就不同——故此前 TS 得 1 桶、
|
|
371
|
+
// Java 得 2 桶,是双引擎分叉(aster-lang-ts#137 统一 None 表示后暴露)。
|
|
372
|
+
if (isVariantValue(k)) {
|
|
373
|
+
const v = k;
|
|
374
|
+
return 'value' in v ? `{__type=${v.__type}, value=${mapKeyPart(v.value)}}` : `{__type=${v.__type}}`;
|
|
375
|
+
}
|
|
308
376
|
return String(k);
|
|
309
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* 变体载荷的字符串化:载荷本身可能又是变体,递归走同一条归一。
|
|
380
|
+
*
|
|
381
|
+
* ★已知缺口(两引擎**一致地**存在,非本次引入,故不在此修):
|
|
382
|
+
* 载荷为真 null 时渲染成 `null`,与载荷为字符串 `"null"` 撞键——
|
|
383
|
+
* `Map.put(m, Some(null), 1)` 与 `Map.put(m, Some("null"), 2)` 塌陷成同一槽位。
|
|
384
|
+
* 实测两引擎同为 1 桶(truffle 走 `String.valueOf`,同样得 `value=null`),
|
|
385
|
+
* 故不是分叉,而是 mapKey 开头那条 null 守卫**没有下沉到载荷层**。
|
|
386
|
+
* 修它需先与 truffle 商定载荷 null 的规范渲染并同步改两侧,属独立议题。
|
|
387
|
+
*/
|
|
388
|
+
function mapKeyPart(v) {
|
|
389
|
+
if (v === null || v === undefined)
|
|
390
|
+
return 'null';
|
|
391
|
+
return isVariantValue(v) ? mapKey(v) : String(v);
|
|
392
|
+
}
|
|
393
|
+
/** 是否为 Maybe/Result 变体(带 `__type` 标签且标签值属四者之一)。 */
|
|
394
|
+
function isVariantValue(v) {
|
|
395
|
+
if (v === null || typeof v !== 'object')
|
|
396
|
+
return false;
|
|
397
|
+
const t = v.__type;
|
|
398
|
+
return t === 'Some' || t === 'None' || t === 'Ok' || t === 'Err';
|
|
399
|
+
}
|
|
310
400
|
/**
|
|
311
401
|
* 值相等(value equality),与 JVM `equals` 契约对齐——**不是** JS 引用相等。
|
|
312
402
|
* - Decimal↔Decimal 用 `.equals`(`1.5m === 1.5m` 引用不等但值相等)。
|
|
@@ -338,9 +428,35 @@ function valueEquals(x, y, depth = 0) {
|
|
|
338
428
|
return false;
|
|
339
429
|
return true;
|
|
340
430
|
}
|
|
431
|
+
// ★Map 结构相等(issue #143)。此前这里把 Map **排除**在结构比较之外
|
|
432
|
+
// (下面的条件带 `&& !(x instanceof Map)`),GuestMap 落回引用相等:
|
|
433
|
+
// Map.put(Map.empty(),"a",1) == Map.put(Map.empty(),"a",1) → false
|
|
434
|
+
// 而同样内容的列表 [1,2] == [1,2] → true——**TS 自身就不自洽**;
|
|
435
|
+
// truffle 侧 valueEquals 对映射逐键递归 → 内容相同的 map JVM 判 true,
|
|
436
|
+
// 同一规则跨引擎决策翻转,且不报错。
|
|
437
|
+
//
|
|
438
|
+
// 镜像 truffle Builtins.valueEquals 的语义:先比 size,再逐键递归;
|
|
439
|
+
// 一侧是 Map 另一侧不是 → false(载体不同即不等)。
|
|
440
|
+
// GuestMap extends Map,故 `instanceof Map` 同时覆盖两者。
|
|
441
|
+
const xm = x instanceof Map;
|
|
442
|
+
const ym = y instanceof Map;
|
|
443
|
+
if (xm && ym) {
|
|
444
|
+
const mx = x;
|
|
445
|
+
const my = y;
|
|
446
|
+
if (mx.size !== my.size)
|
|
447
|
+
return false;
|
|
448
|
+
for (const [k, v] of mx) {
|
|
449
|
+
if (!my.has(k))
|
|
450
|
+
return false;
|
|
451
|
+
if (!valueEquals(v, my.get(k), depth + 1))
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
if (xm || ym)
|
|
457
|
+
return false;
|
|
341
458
|
if (x && y && typeof x === 'object' && typeof y === 'object'
|
|
342
|
-
&& !Array.isArray(x) && !Array.isArray(y)
|
|
343
|
-
&& !(x instanceof Map) && !(y instanceof Map)) {
|
|
459
|
+
&& !Array.isArray(x) && !Array.isArray(y)) {
|
|
344
460
|
const kx = Object.keys(x);
|
|
345
461
|
const ky = Object.keys(y);
|
|
346
462
|
if (kx.length !== ky.length)
|
|
@@ -742,12 +858,25 @@ class Interpreter {
|
|
|
742
858
|
case 'String':
|
|
743
859
|
return expr.value;
|
|
744
860
|
case 'Long':
|
|
745
|
-
// Long
|
|
746
|
-
|
|
861
|
+
// ★Long 运行时表示 = BigInt(issue #142)。此前是 `Number(expr.value)`,
|
|
862
|
+
// 把词法层专门用 BigInt 保住的精度又丢了回去:
|
|
863
|
+
// 9223372036854775807L → 9223372036854776000(差 193,静默错答案)
|
|
864
|
+
// 而 JVM 侧 CoreModel.LongE 是精确 `long` → 同一源码两引擎结果不同。
|
|
865
|
+
// 改用 BigInt 后与 JVM 逐位一致。
|
|
866
|
+
//
|
|
867
|
+
// ★BigInt 的三个坑(已实测,见 evalBinaryOp / serializeGuestValue):
|
|
868
|
+
// · JSON.stringify(1n) 抛 "Do not know how to serialize a BigInt"
|
|
869
|
+
// · 1n + 1 抛 "Cannot mix BigInt and other types"
|
|
870
|
+
// · 1n === 1 为 false(== 才为 true)
|
|
871
|
+
return BigInt(expr.value);
|
|
747
872
|
case 'Null':
|
|
748
873
|
return null;
|
|
874
|
+
// ★None 的运行期表示是 `{__type:'None'}`,与 truffle 的 `maybeNone()` 对齐
|
|
875
|
+
// (aster-lang-ts#137)。此前返回裸 `null`:同一份 IR 两引擎产出不同 JSON,
|
|
876
|
+
// 宿主一边拿到 `null`、一边拿到 `{"__type":"None"}`,等价性按值比较判为不等;
|
|
877
|
+
// 且 `null` 无法与「真的没有值」区分——None 是**有值的变体**,只是不含载荷。
|
|
749
878
|
case 'None':
|
|
750
|
-
return
|
|
879
|
+
return { __type: 'None' };
|
|
751
880
|
case 'Name':
|
|
752
881
|
return this.resolveName(expr.name, env);
|
|
753
882
|
case 'Call':
|
|
@@ -834,7 +963,8 @@ class Interpreter {
|
|
|
834
963
|
return { __type: ctor, value: this.evalExpr(call.args[0], env) };
|
|
835
964
|
}
|
|
836
965
|
if (ctor === 'None' && call.args.length === 0) {
|
|
837
|
-
|
|
966
|
+
// 与 evalExpr 的 case 'None' 同一表示(aster-lang-ts#137)。
|
|
967
|
+
return { __type: 'None' };
|
|
838
968
|
}
|
|
839
969
|
}
|
|
840
970
|
// stdlib namespaced builtin(Text.* 等),与 Java Builtins 对齐。
|
|
@@ -1006,11 +1136,11 @@ class Interpreter {
|
|
|
1006
1136
|
case 'Text.substring': {
|
|
1007
1137
|
const args = a();
|
|
1008
1138
|
const s = text(args[0]);
|
|
1009
|
-
const start =
|
|
1139
|
+
const start = toInt(args[1], 'Text.substring');
|
|
1010
1140
|
if (start < 0)
|
|
1011
1141
|
throw new InterpreterError(`Text.substring: index out of range: ${start}`);
|
|
1012
1142
|
if (args.length >= 3) {
|
|
1013
|
-
const end =
|
|
1143
|
+
const end = toInt(args[2], 'Text.substring');
|
|
1014
1144
|
if (end < 0)
|
|
1015
1145
|
throw new InterpreterError(`Text.substring: index out of range: ${end}`);
|
|
1016
1146
|
// Java String.substring(start,end) 对 start>end 或 end>length 抛异常。
|
|
@@ -1033,8 +1163,8 @@ class Interpreter {
|
|
|
1033
1163
|
case 'List.slice': {
|
|
1034
1164
|
const args = a();
|
|
1035
1165
|
const l = reqList('List.slice', args[0]);
|
|
1036
|
-
const start =
|
|
1037
|
-
const end = args.length >= 3 ?
|
|
1166
|
+
const start = toInt(args[1], 'List.slice');
|
|
1167
|
+
const end = args.length >= 3 ? toInt(args[2], 'List.slice') : l.length;
|
|
1038
1168
|
// 镜像 Java List.subList 的越界语义。
|
|
1039
1169
|
if (start < 0 || end > l.length || start > end) {
|
|
1040
1170
|
throw new InterpreterError(`List.slice: index out of range: begin ${start}, end ${end}, size ${l.length}`);
|
|
@@ -1061,8 +1191,19 @@ class Interpreter {
|
|
|
1061
1191
|
return reqList('List.isEmpty', l).length === 0;
|
|
1062
1192
|
}
|
|
1063
1193
|
case 'List.get': {
|
|
1194
|
+
// ★越界必须抛错,不得静默返回 undefined(issue aster-dev#32)。
|
|
1195
|
+
// JS 的 `arr[99]` 是 undefined,而 truffle Builtins.List.get 有显式
|
|
1196
|
+
// 边界检查并抛 BuiltinException——同一段源码两引擎一个静默产
|
|
1197
|
+
// undefined、一个响亮失败,且文档声称「越界报错且双引擎一致」。
|
|
1198
|
+
// undefined 随后会一路流进裁决结果,是最危险的一类静默错答案。
|
|
1199
|
+
// 消息对齐 truffle ErrorMessages.collectionIndexOutOfBounds 的英文形式。
|
|
1064
1200
|
const [l, i] = a();
|
|
1065
|
-
|
|
1201
|
+
const list = reqList('List.get', l);
|
|
1202
|
+
const idx = toInt(i, 'List.get');
|
|
1203
|
+
if (idx < 0 || idx >= list.length) {
|
|
1204
|
+
throw new InterpreterError(`List.get: index out of bounds: ${idx} (size=${list.length})`);
|
|
1205
|
+
}
|
|
1206
|
+
return list[idx];
|
|
1066
1207
|
}
|
|
1067
1208
|
case 'List.contains': {
|
|
1068
1209
|
const [l, x] = a();
|
|
@@ -1084,16 +1225,16 @@ class Interpreter {
|
|
|
1084
1225
|
// 此前返回裸值/null,于是 `Map.get(m,k) plus 1` 能**编译通过、运行才炸**。
|
|
1085
1226
|
// ★这是对 Stable 集的语义变更,明知破坏 spec-1.0-freeze 承诺,由用户拍板接受。
|
|
1086
1227
|
//
|
|
1087
|
-
// ★
|
|
1088
|
-
//
|
|
1089
|
-
//
|
|
1228
|
+
// ★None 的运行期表示是 `{__type:'None'}`(见 evalExpr 的 case 'None',
|
|
1229
|
+
// aster-lang-ts#137 起与 truffle 的 maybeNone() 对齐);缺键分支返回它。
|
|
1230
|
+
// 命中分支包一层 Some,与 Java 的 {__type:"Some"} 对齐。
|
|
1090
1231
|
// 命中值本身为 null 时仍返回 Some(null):「键存在但值为 null」与「键不存在」
|
|
1091
1232
|
// 是两件事,不能塌陷。
|
|
1092
1233
|
case 'Map.get': {
|
|
1093
1234
|
const [m, k] = a();
|
|
1094
1235
|
const g = asGuestMap('Map.get', m);
|
|
1095
1236
|
const key = mapKey(k);
|
|
1096
|
-
return g.has(key) ? { __type: 'Some', value: g.get(key) } :
|
|
1237
|
+
return g.has(key) ? { __type: 'Some', value: g.get(key) } : { __type: 'None' };
|
|
1097
1238
|
}
|
|
1098
1239
|
case 'Map.contains': {
|
|
1099
1240
|
const [m, k] = a();
|
|
@@ -1122,11 +1263,11 @@ class Interpreter {
|
|
|
1122
1263
|
case 'Date.fromISO': return dateFromISO(a()[0]);
|
|
1123
1264
|
case 'Date.daysBetween': {
|
|
1124
1265
|
const [d1, d2] = a();
|
|
1125
|
-
return
|
|
1266
|
+
return toInt(d2, 'Date.daysBetween') - toInt(d1, 'Date.daysBetween');
|
|
1126
1267
|
}
|
|
1127
1268
|
case 'Date.addDays': {
|
|
1128
1269
|
const [d, n] = a();
|
|
1129
|
-
const r =
|
|
1270
|
+
const r = toInt(d, 'Date.addDays') + toInt(n, 'Date.addDays');
|
|
1130
1271
|
if (r < DATE_MIN_EPOCH || r > DATE_MAX_EPOCH)
|
|
1131
1272
|
throw new InterpreterError(`Date.OutOfRange: epoch-day ${r}`);
|
|
1132
1273
|
return r;
|
|
@@ -1179,13 +1320,13 @@ class Interpreter {
|
|
|
1179
1320
|
// Ok/Err/Int/Text 四格分叉(共 8 格),isSome/isOk/isErr 全对齐。
|
|
1180
1321
|
// JVM 那侧是对的——isNone 不该对 `Ok(1)`、`42` 返回 true。
|
|
1181
1322
|
//
|
|
1182
|
-
//
|
|
1183
|
-
//
|
|
1323
|
+
// ★None 只有一种运行期表示 `{__type:'None'}`(aster-lang-ts#137 起统一)。
|
|
1324
|
+
// 此前 `None` 字面量求值为裸 `null`,故这里额外把 `null`/`undefined` 判为 None;
|
|
1325
|
+
// 现已不需要——truffle 侧 `"None".equals(m.get("__type"))` 对 null 返回 false,
|
|
1326
|
+
// 继续放行会让 `Maybe.isNone(null)` 两引擎分叉(TS true / JVM false)。
|
|
1184
1327
|
case 'Maybe.isNone':
|
|
1185
1328
|
case 'Option.isNone': {
|
|
1186
1329
|
const [x] = a();
|
|
1187
|
-
if (x === null || x === undefined)
|
|
1188
|
-
return true;
|
|
1189
1330
|
return x?.__type === 'None';
|
|
1190
1331
|
}
|
|
1191
1332
|
case 'Result.isOk': {
|
|
@@ -1296,9 +1437,11 @@ class Interpreter {
|
|
|
1296
1437
|
}
|
|
1297
1438
|
case 'List.range': {
|
|
1298
1439
|
const [s, e] = a();
|
|
1299
|
-
const start =
|
|
1300
|
-
const end =
|
|
1440
|
+
const start = toInt(s, 'List.range');
|
|
1441
|
+
const end = toInt(e, 'List.range');
|
|
1301
1442
|
// 红队 P0-B:先按长度判上限,超限即抛(不先生成,防 DoS)。
|
|
1443
|
+
// ★start/end 走 toInt 后不可能是 NaN——此前裸 Number() 下 NaN 会让
|
|
1444
|
+
// `size > MAX_RANGE_SIZE` 为 false 直接放行,循环零次静默返回 []。
|
|
1302
1445
|
const size = end - start;
|
|
1303
1446
|
if (size > MAX_RANGE_SIZE) {
|
|
1304
1447
|
throw new InterpreterError(`List.range: 长度过大(${size} > ${MAX_RANGE_SIZE}),拒绝以防内存耗尽 DoS`);
|
|
@@ -1426,9 +1569,10 @@ class Interpreter {
|
|
|
1426
1569
|
// `operationExpectedType("Maybe.map", "Maybe (Some or None)", …)`)。
|
|
1427
1570
|
// 既是双引擎分叉,也是静默错答案,与 #123 的 arity 缺口同一模式。
|
|
1428
1571
|
//
|
|
1429
|
-
//
|
|
1430
|
-
//
|
|
1431
|
-
//
|
|
1572
|
+
// ★None 只有一种运行期表示 `{__type:'None'}`(aster-lang-ts#137 起统一)。
|
|
1573
|
+
// 此前 `None` 字面量求值为裸 `null`,故这里额外放行 `null`;现已不需要——
|
|
1574
|
+
// 且 truffle 的 `Maybe.map` 要求带标签的 map、对裸 null 直接报错,
|
|
1575
|
+
// 继续放行 `null` 本身就是新的分叉。
|
|
1432
1576
|
//
|
|
1433
1577
|
// ★不在放行之列(有意为之):`undefined`、以及 JVM 形的 `{_type:'None'}`
|
|
1434
1578
|
// (单下划线——truffle 用 `_type`,TS 全仓用 `__type`)。后者若从跨引擎
|
|
@@ -1442,7 +1586,7 @@ class Interpreter {
|
|
|
1442
1586
|
const fn = callableArg(1);
|
|
1443
1587
|
return { __type: 'Some', value: this.applyCallable(fn, [o.value]) };
|
|
1444
1588
|
}
|
|
1445
|
-
if (o
|
|
1589
|
+
if (o?.__type !== 'None') {
|
|
1446
1590
|
// 消息**主体**与 truffle 的 operationExpectedType 对齐
|
|
1447
1591
|
// (`Maybe.map: expected Maybe (Some or None), got X`),但**类型名词汇表
|
|
1448
1592
|
// 两侧不同**,不止大小写:本引擎用 JS `typeof`(7 值域),truffle 用
|
|
@@ -1559,6 +1703,56 @@ class Interpreter {
|
|
|
1559
1703
|
throw new InterpreterError(`Decimal: unsupported operator '${op}'`);
|
|
1560
1704
|
}
|
|
1561
1705
|
}
|
|
1706
|
+
// ★BigInt(Long)分派——必须在下面的原生算术之前。
|
|
1707
|
+
// JS 不允许 BigInt 与 number 混算(`1n + 1` 抛错),故:
|
|
1708
|
+
// · 两侧都是 BigInt → 用 BigInt 运算,精确 64 位,与 JVM long 一致
|
|
1709
|
+
// · 一侧 BigInt 一侧 Int → 把整数值 number 提升为 BigInt(精确,无损)
|
|
1710
|
+
// · 一侧 BigInt 一侧非整数 Double → 响亮失败,不静默降级
|
|
1711
|
+
// 与 Decimal 分派同构(见上),理由相同:静默错答案比响亮失败危险得多。
|
|
1712
|
+
if (typeof left === 'bigint' || typeof right === 'bigint') {
|
|
1713
|
+
const toBig = (v, side) => {
|
|
1714
|
+
if (typeof v === 'bigint')
|
|
1715
|
+
return v;
|
|
1716
|
+
if (typeof v === 'number') {
|
|
1717
|
+
if (!Number.isInteger(v)) {
|
|
1718
|
+
throw new InterpreterError(`Cannot combine Long and non-integer Double (${side}). Long arithmetic is exact 64-bit.`);
|
|
1719
|
+
}
|
|
1720
|
+
if (!Number.isSafeInteger(v)) {
|
|
1721
|
+
// >2^53 的 number 本身已不精确,提升过去只会把错误带进精确域
|
|
1722
|
+
throw new InterpreterError(`Cannot combine Long and unsafe integer ${v} (${side}); value already lost precision as a JS number.`);
|
|
1723
|
+
}
|
|
1724
|
+
return BigInt(v);
|
|
1725
|
+
}
|
|
1726
|
+
throw new InterpreterError(`Long ${op}: expected Long/Int operand, got ${typeof v}`);
|
|
1727
|
+
};
|
|
1728
|
+
const lb = toBig(left, 'left'), rb = toBig(right, 'right');
|
|
1729
|
+
switch (op) {
|
|
1730
|
+
case '+': return lb + rb;
|
|
1731
|
+
case '-': return lb - rb;
|
|
1732
|
+
case '*': return lb * rb;
|
|
1733
|
+
case '/':
|
|
1734
|
+
if (rb === 0n)
|
|
1735
|
+
throw new InterpreterError('Division by zero');
|
|
1736
|
+
// Long 除法与 JVM 一致:向零截断的整数除法
|
|
1737
|
+
return lb / rb;
|
|
1738
|
+
case '//':
|
|
1739
|
+
if (rb === 0n)
|
|
1740
|
+
throw new InterpreterError('Division by zero');
|
|
1741
|
+
return lb / rb;
|
|
1742
|
+
case '%':
|
|
1743
|
+
if (rb === 0n)
|
|
1744
|
+
throw new InterpreterError('Division by zero');
|
|
1745
|
+
return lb % rb;
|
|
1746
|
+
case '==': return lb === rb;
|
|
1747
|
+
case '!=': return lb !== rb;
|
|
1748
|
+
case '<': return lb < rb;
|
|
1749
|
+
case '<=': return lb <= rb;
|
|
1750
|
+
case '>': return lb > rb;
|
|
1751
|
+
case '>=': return lb >= rb;
|
|
1752
|
+
default:
|
|
1753
|
+
throw new InterpreterError(`Long: unsupported operator '${op}'`);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1562
1756
|
// 算术运算
|
|
1563
1757
|
switch (op) {
|
|
1564
1758
|
case '+': {
|