@scriptc/compiler 0.1.4 → 0.1.6
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/ambient/scriptc-node-fallback.d.ts +75 -11
- package/dist/backend/c/c-emitter.d.ts +5 -1
- package/dist/backend/c/c-emitter.js +22 -9
- package/dist/backend/c/c-emitter.js.map +1 -1
- package/dist/backend/c/exprs.d.ts +2 -2
- package/dist/backend/c/exprs.js +298 -27
- package/dist/backend/c/exprs.js.map +1 -1
- package/dist/backend/c/stmts.js +5 -3
- package/dist/backend/c/stmts.js.map +1 -1
- package/dist/backend/c/types.d.ts +2 -3
- package/dist/backend/c/types.js +8 -1
- package/dist/backend/c/types.js.map +1 -1
- package/dist/backend/c/walkers.js +1 -1
- package/dist/backend/c/walkers.js.map +1 -1
- package/dist/backend/link-plan.d.ts +1 -0
- package/dist/backend/link-plan.js +2 -1
- package/dist/backend/link-plan.js.map +1 -1
- package/dist/backend/llvm/dyn.js +1 -1
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +25 -9
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.d.ts +1 -2
- package/dist/backend/llvm/expr-bytes.js +22 -17
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-containers.d.ts +1 -0
- package/dist/backend/llvm/expr-containers.js +58 -4
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +6 -2
- package/dist/backend/llvm/expr-primitives.js +22 -7
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +20 -0
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-network.js +53 -10
- package/dist/backend/llvm/lib-network.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +4 -3
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +65 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/native-toolchain.d.ts +2 -0
- package/dist/backend/native-toolchain.js +2 -1
- package/dist/backend/native-toolchain.js.map +1 -1
- package/dist/backend/targets.d.ts +3 -0
- package/dist/backend/targets.js +13 -0
- package/dist/backend/targets.js.map +1 -1
- package/dist/coverage/surface-manifest.js +12 -5
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/executable/executable-cache.d.ts +2 -0
- package/dist/executable/executable-cache.js +3 -1
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +3 -3
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/frontend/lowering/array-indexed-mutation.d.ts +8 -0
- package/dist/frontend/lowering/array-indexed-mutation.js +176 -0
- package/dist/frontend/lowering/array-indexed-mutation.js.map +1 -0
- package/dist/frontend/lowering/containers/bytes.d.ts +1 -1
- package/dist/frontend/lowering/containers/bytes.js +6 -5
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/containers/string-and-regexp.d.ts +14 -6
- package/dist/frontend/lowering/containers/string-and-regexp.js +420 -34
- package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
- package/dist/frontend/lowering/expressions/object-literals.js +8 -0
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
- package/dist/frontend/lowering/indexed-comparison.d.ts +6 -0
- package/dist/frontend/lowering/indexed-comparison.js +30 -0
- package/dist/frontend/lowering/indexed-comparison.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +5 -6
- package/dist/frontend/lowering/lower-builtins.js +24 -16
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +3 -2
- package/dist/frontend/lowering/lower-calls.js +522 -89
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +5 -29
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +15 -11
- package/dist/frontend/lowering/lower-containers.js +588 -146
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +12 -0
- package/dist/frontend/lowering/lower-exprs.js +145 -11
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-island.js +17 -6
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-server.d.ts +2 -4
- package/dist/frontend/lowering/lower-server.js +288 -32
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +4 -4
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.js +37 -2
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/optional-arguments.d.ts +9 -1
- package/dist/frontend/lowering/optional-arguments.js +67 -1
- package/dist/frontend/lowering/optional-arguments.js.map +1 -1
- package/dist/frontend/lowering/pairs-snapshot.d.ts +3 -1
- package/dist/frontend/lowering/pairs-snapshot.js +12 -4
- package/dist/frontend/lowering/pairs-snapshot.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +10 -14
- package/dist/frontend/lowering/surfaces.js +50 -28
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm-static-prune.d.ts +6 -0
- package/dist/frontend/npm-static-prune.js +203 -0
- package/dist/frontend/npm-static-prune.js.map +1 -0
- package/dist/frontend/npm-static-prune.test.d.ts +1 -0
- package/dist/frontend/npm-static-prune.test.js +48 -0
- package/dist/frontend/npm-static-prune.test.js.map +1 -0
- package/dist/frontend/npm.js +7 -6
- package/dist/frontend/npm.js.map +1 -1
- package/dist/frontend/program.js +15 -4
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/provenance.js +8 -3
- package/dist/frontend/provenance.js.map +1 -1
- package/dist/frontend/type-mapper.js +16 -5
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +13 -41
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.d.ts +5 -3
- package/dist/ir/analysis.js +25 -11
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/analysis.test.js +50 -2
- package/dist/ir/analysis.test.js.map +1 -1
- package/dist/ir/constant-tables.d.ts +17 -0
- package/dist/ir/constant-tables.js +99 -0
- package/dist/ir/constant-tables.js.map +1 -0
- package/dist/ir/constant-tables.test.d.ts +1 -0
- package/dist/ir/constant-tables.test.js +78 -0
- package/dist/ir/constant-tables.test.js.map +1 -0
- package/dist/ir/integer-ranges.d.ts +13 -0
- package/dist/ir/integer-ranges.js +122 -0
- package/dist/ir/integer-ranges.js.map +1 -0
- package/dist/ir/integer-ranges.test.d.ts +1 -0
- package/dist/ir/integer-ranges.test.js +63 -0
- package/dist/ir/integer-ranges.test.js.map +1 -0
- package/dist/ir/ir.d.ts +56 -35
- package/dist/ir/ir.js +9 -1
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/ir.test.js +6 -0
- package/dist/ir/ir.test.js.map +1 -1
- package/dist/ir/scalar-records.d.ts +8 -0
- package/dist/ir/scalar-records.js +216 -0
- package/dist/ir/scalar-records.js.map +1 -0
- package/dist/ir/scalar-records.test.d.ts +1 -0
- package/dist/ir/scalar-records.test.js +142 -0
- package/dist/ir/scalar-records.test.js.map +1 -0
- package/dist/ir/validate.js +135 -34
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.d.ts +1 -0
- package/dist/ir/validate.test.js +48 -0
- package/dist/ir/validate.test.js.map +1 -0
- package/dist/library/cache-primitives.d.ts +1 -1
- package/dist/library/cache-primitives.js +3 -3
- package/dist/library/cache-primitives.js.map +1 -1
- package/dist/library/fence-eval.js +11 -4
- package/dist/library/fence-eval.js.map +1 -1
- package/dist/library/library-cache.js +0 -1
- package/dist/library/library-cache.js.map +1 -1
- package/dist/library/library-cache.test.js +3 -3
- package/dist/library/library-cache.test.js.map +1 -1
- package/package.json +17 -17
- package/surface-manifest.json +90 -45
|
@@ -1,15 +1,119 @@
|
|
|
1
1
|
import * as ts from "../../ts7/adapter.js";
|
|
2
|
-
import { BOOL, F64, STRING, arrayOf, isUnitType, typeEquals } from "../../../ir/ir.js";
|
|
3
|
-
import { numLit, varRef } from "../../../ir/build.js";
|
|
2
|
+
import { BOOL, DYN, F64, STRING, UNDEFINED_T, arrayOf, isUnitType, typeEquals, typeKey } from "../../../ir/ir.js";
|
|
3
|
+
import { numLit, strLit, varRef } from "../../../ir/build.js";
|
|
4
4
|
import { locOf } from "../../program.js";
|
|
5
|
-
import { own } from "../lowerer.js";
|
|
5
|
+
import { nodeThrowExpr, own } from "../lowerer.js";
|
|
6
6
|
import { isRequireMainFilename } from "../expressions/optional-chains.js";
|
|
7
|
-
import { STR_METHODS } from "../surfaces.js";
|
|
8
|
-
import { lowerOptionalArgument } from "../optional-arguments.js";
|
|
7
|
+
import { STRING_INDEX_METHODS, STR_METHODS } from "../surfaces.js";
|
|
8
|
+
import { coerceStringSearchValue, defaultAfterUndefined, lowerOptionalArgument, lowerPositionArgument, lowerStaticallyUndefinedArgument, lowerStringSearchArgument, positionNumber } from "../optional-arguments.js";
|
|
9
9
|
function lowerSplitLimitArg(lowerer, node, loc) {
|
|
10
10
|
const defaultValue = { kind: "numLit", value: 4294967295, type: F64, loc };
|
|
11
11
|
return node ? lowerOptionalArgument(lowerer, node, F64, defaultValue) : defaultValue;
|
|
12
12
|
}
|
|
13
|
+
/** Split evaluates its arguments before converting the receiver, limit, and
|
|
14
|
+
* separator, in that order. An undefined separator returns the whole string
|
|
15
|
+
* unless ToUint32(limit) is zero; the string-separator intrinsic handles the
|
|
16
|
+
* remaining split loop. */
|
|
17
|
+
export function lowerStringSplitCall(lowerer, call, receiver, receiverNode, argumentNodes) {
|
|
18
|
+
const loc = locOf(call);
|
|
19
|
+
if (argumentNodes.length > 2 || argumentNodes.some(ts.isSpreadElement)) {
|
|
20
|
+
return lowerer.noLowering(`.split with ${argumentNodes.length} arguments`, call);
|
|
21
|
+
}
|
|
22
|
+
const separatorNode = argumentNodes[0];
|
|
23
|
+
const undefinedSeparator = separatorNode ? lowerStaticallyUndefinedArgument(lowerer, separatorNode) : null;
|
|
24
|
+
const absent = !separatorNode || undefinedSeparator !== null;
|
|
25
|
+
let separator = absent
|
|
26
|
+
? undefinedSeparator
|
|
27
|
+
? defaultAfterUndefined(undefinedSeparator, strLit("undefined", loc))
|
|
28
|
+
: strLit("undefined", loc)
|
|
29
|
+
: lowerer.lowerExpr(separatorNode);
|
|
30
|
+
if (separator.type.kind === "nullT")
|
|
31
|
+
separator = defaultAfterUndefined(separator, strLit("null", loc));
|
|
32
|
+
const scalar = separator.type.kind === "string" || separator.type.kind === "f64" ||
|
|
33
|
+
separator.type.kind === "bool" || separator.type.kind === "bigint" ||
|
|
34
|
+
(separator.type.kind === "union" && (lowerer.unions.get(separator.type.unionId)?.arms.every((arm) => arm.kind === "string" || arm.kind === "f64" || arm.kind === "bool" ||
|
|
35
|
+
arm.kind === "bigint" || arm.kind === "nullT" || arm.kind === "undefinedT") ?? false));
|
|
36
|
+
if (!scalar) {
|
|
37
|
+
lowerer.unsupported("SC1090", separatorNode ?? call, `'.split()' on a '${lowerer.fmt(separator.type)}' separator (pass a string, or a regex literal)`);
|
|
38
|
+
}
|
|
39
|
+
const defaultLimit = numLit(4294967295, loc);
|
|
40
|
+
const suppliedLimit = lowerPositionArgument(lowerer, argumentNodes[1], defaultLimit);
|
|
41
|
+
const limit = suppliedLimit.type.kind === "record" && argumentNodes[1]
|
|
42
|
+
? lowerer.coerceInto(argumentNodes[1], suppliedLimit, DYN) : suppliedLimit;
|
|
43
|
+
const resultType = arrayOf(STRING);
|
|
44
|
+
if (receiver.type.kind === "string" && separator.type.kind === "string" && limit.type.kind === "f64" && !absent) {
|
|
45
|
+
return { kind: "strIntrinsic", method: "split", receiver, args: [separator, limit], type: resultType, loc };
|
|
46
|
+
}
|
|
47
|
+
const key = `str.split:${receiver.type.kind === "union" ? typeKey(receiver.type) : receiver.type.kind}:${typeKey(separator.type)}:${typeKey(limit.type)}:${absent}`;
|
|
48
|
+
let helper = lowerer.widthHelpers.get(key);
|
|
49
|
+
if (!helper) {
|
|
50
|
+
helper = `%str.split.${lowerer.widthHelpers.size}`;
|
|
51
|
+
const values = [receiver, separator, limit];
|
|
52
|
+
const params = values.map((value, index) => ({ localId: `arg.${index}`, name: `arg${index}`, type: value.type }));
|
|
53
|
+
const rawReceiver = varRef("arg.0", receiver.type, loc);
|
|
54
|
+
const rawSeparator = varRef("arg.1", separator.type, loc);
|
|
55
|
+
const rawLimit = varRef("arg.2", limit.type, loc);
|
|
56
|
+
const stringReceiver = varRef("receiver.0", STRING, loc);
|
|
57
|
+
const numericLimit = varRef("limit.0", F64, loc);
|
|
58
|
+
const stringSeparator = varRef("separator.0", STRING, loc);
|
|
59
|
+
const undefinedTag = separator.type.kind === "union"
|
|
60
|
+
? lowerer.unions.get(separator.type.unionId).arms.findIndex((arm) => arm.kind === "undefinedT") : -1;
|
|
61
|
+
const separatorIsUndefined = absent
|
|
62
|
+
? { kind: "boolLit", value: true, type: BOOL, loc }
|
|
63
|
+
: undefinedTag >= 0 && separator.type.kind === "union"
|
|
64
|
+
? { kind: "unionIsTag", unionId: separator.type.unionId, tag: undefinedTag, value: rawSeparator, negated: false, type: BOOL, loc }
|
|
65
|
+
: { kind: "boolLit", value: false, type: BOOL, loc };
|
|
66
|
+
const split = {
|
|
67
|
+
kind: "strIntrinsic", method: "split", receiver: stringReceiver,
|
|
68
|
+
args: [stringSeparator, numericLimit], type: resultType, loc,
|
|
69
|
+
};
|
|
70
|
+
const result = {
|
|
71
|
+
kind: "ternary",
|
|
72
|
+
cond: {
|
|
73
|
+
kind: "bin", op: "===",
|
|
74
|
+
left: { kind: "bin", op: ">>>", left: numericLimit, right: numLit(0, loc), type: F64, loc },
|
|
75
|
+
right: numLit(0, loc), type: BOOL, loc,
|
|
76
|
+
},
|
|
77
|
+
then: { kind: "arrayLit", elems: [], type: resultType, loc },
|
|
78
|
+
else_: {
|
|
79
|
+
kind: "ternary", cond: separatorIsUndefined,
|
|
80
|
+
then: { kind: "arrayLit", elems: [stringReceiver], type: resultType, loc },
|
|
81
|
+
else_: split, type: resultType, loc,
|
|
82
|
+
},
|
|
83
|
+
type: resultType, loc,
|
|
84
|
+
};
|
|
85
|
+
lowerer.widthHelpers.set(key, helper);
|
|
86
|
+
lowerer.liftedFns.push({
|
|
87
|
+
name: helper, params, returnType: resultType,
|
|
88
|
+
locals: [
|
|
89
|
+
...params.map(param => ({ id: param.localId, name: param.name, type: param.type, mutable: false })),
|
|
90
|
+
{ id: "receiver.0", name: "receiver", type: STRING, mutable: false },
|
|
91
|
+
{ id: "limit.0", name: "limit", type: F64, mutable: false },
|
|
92
|
+
{ id: "separator.0", name: "separator", type: STRING, mutable: false },
|
|
93
|
+
],
|
|
94
|
+
body: [
|
|
95
|
+
{ kind: "varDecl", localId: "receiver.0", init: lowerer.ensureString(rawReceiver, receiverNode), loc },
|
|
96
|
+
{ kind: "varDecl", localId: "limit.0", init: positionNumber(lowerer, rawLimit, defaultLimit, argumentNodes[1] ?? call, "string split limit"), loc },
|
|
97
|
+
{ kind: "varDecl", localId: "separator.0", init: lowerer.ensureString(rawSeparator, separatorNode ?? call), loc },
|
|
98
|
+
{ kind: "return", value: result, loc },
|
|
99
|
+
],
|
|
100
|
+
loc,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return { kind: "call", callee: helper, args: [receiver, separator, limit], type: resultType, loc };
|
|
104
|
+
}
|
|
105
|
+
function lowerRegexSubject(lowerer, node, loc) {
|
|
106
|
+
const absent = { kind: "strLit", value: "undefined", type: STRING, loc };
|
|
107
|
+
if (!node)
|
|
108
|
+
return absent;
|
|
109
|
+
const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, node);
|
|
110
|
+
if (undefinedArg)
|
|
111
|
+
return defaultAfterUndefined(undefinedArg, absent);
|
|
112
|
+
const value = lowerer.lowerExpr(node);
|
|
113
|
+
if (value.kind === "unitLit")
|
|
114
|
+
return { kind: "strLit", value: value.unit, type: STRING, loc };
|
|
115
|
+
return lowerer.ensureString(value, node);
|
|
116
|
+
}
|
|
13
117
|
/** Extract the checker-proven receiver arm from a runtime-optional property
|
|
14
118
|
* read. The receiver is stabilized once before its tag is tested, and a
|
|
15
119
|
* missing value throws the same property-read TypeError Node throws before
|
|
@@ -23,6 +127,192 @@ function lowerMethodReceiver(lowerer, node, expected, member) {
|
|
|
23
127
|
return lowered;
|
|
24
128
|
return lowerer.coerceInto(node, lowered, expected);
|
|
25
129
|
}
|
|
130
|
+
/** Relative string indexing and code-point decoding over the UTF-16 intrinsics. */
|
|
131
|
+
export function lowerStringIndexCall(lowerer, call, method, receiver, receiverNode, argumentNodes) {
|
|
132
|
+
const loc = locOf(call);
|
|
133
|
+
if (argumentNodes.length > 1 || argumentNodes.some(ts.isSpreadElement)) {
|
|
134
|
+
return lowerer.noLowering(`String.prototype.${method} with ${argumentNodes.length} arguments`, call);
|
|
135
|
+
}
|
|
136
|
+
const indexNode = argumentNodes[0];
|
|
137
|
+
let index = lowerPositionArgument(lowerer, indexNode, numLit(0, loc));
|
|
138
|
+
if (indexNode && (index.type.kind === "record" || index.type.kind === "array" || index.type.kind === "func")) {
|
|
139
|
+
index = lowerer.coerceInto(indexNode, index, DYN);
|
|
140
|
+
}
|
|
141
|
+
const valueType = method === "at" ? STRING : F64;
|
|
142
|
+
const resultType = lowerer.withUndefinedArmOf(valueType);
|
|
143
|
+
if (!resultType || resultType.kind !== "union")
|
|
144
|
+
return lowerer.noLowering(`String.prototype.${method} result`, call);
|
|
145
|
+
const undefinedTag = lowerer.armTag(resultType.unionId, UNDEFINED_T);
|
|
146
|
+
const valueTag = lowerer.armTag(resultType.unionId, valueType);
|
|
147
|
+
const key = `str.index:${method}:${typeKey(receiver.type)}:${typeKey(index.type)}`;
|
|
148
|
+
let helper = lowerer.widthHelpers.get(key);
|
|
149
|
+
if (!helper) {
|
|
150
|
+
helper = `%str.index.${lowerer.widthHelpers.size}`;
|
|
151
|
+
lowerer.widthHelpers.set(key, helper);
|
|
152
|
+
const rawReceiver = varRef("arg.0", receiver.type, loc);
|
|
153
|
+
const rawIndex = varRef("arg.1", index.type, loc);
|
|
154
|
+
const stringReceiver = varRef("receiver.0", STRING, loc);
|
|
155
|
+
const length = varRef("length.0", F64, loc);
|
|
156
|
+
const position = varRef("position.0", F64, loc);
|
|
157
|
+
const first = varRef("first.0", F64, loc);
|
|
158
|
+
const second = varRef("second.0", F64, loc);
|
|
159
|
+
const bin = (op, left, right) => ({ kind: "bin", op, left, right, type: op === "<" || op === ">=" || op === ">" ? BOOL : F64, loc });
|
|
160
|
+
const either = (left, right) => ({ kind: "logical", op: "||", left, right, type: BOOL, loc });
|
|
161
|
+
const wrap = (value) => ({ kind: "unionWrap", unionId: resultType.unionId, tag: valueTag, value, type: resultType, loc });
|
|
162
|
+
const miss = {
|
|
163
|
+
kind: "unionWrap", unionId: resultType.unionId, tag: undefinedTag,
|
|
164
|
+
value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, type: resultType, loc,
|
|
165
|
+
};
|
|
166
|
+
const numeric = positionNumber(lowerer, rawIndex, numLit(0, loc), indexNode ?? call, "string index");
|
|
167
|
+
const numericValue = varRef("numeric.0", F64, loc);
|
|
168
|
+
const body = [
|
|
169
|
+
{ kind: "varDecl", localId: "receiver.0", init: lowerer.ensureString(rawReceiver, receiverNode), loc },
|
|
170
|
+
{ kind: "varDecl", localId: "length.0", init: { kind: "strIntrinsic", method: "length", receiver: stringReceiver, args: [], type: F64, loc }, loc },
|
|
171
|
+
{ kind: "varDecl", localId: "numeric.0", init: numeric, loc },
|
|
172
|
+
{ kind: "varDecl", localId: "position.0", init: {
|
|
173
|
+
kind: "ternary", cond: { kind: "libCall", fn: "num.isNaN", args: [numericValue], type: BOOL, loc },
|
|
174
|
+
then: numLit(0, loc), else_: { kind: "libCall", fn: "math.trunc", args: [numericValue], type: F64, loc }, type: F64, loc,
|
|
175
|
+
}, loc },
|
|
176
|
+
];
|
|
177
|
+
if (method === "at") {
|
|
178
|
+
body.push({ kind: "if", cond: bin("<", position, numLit(0, loc)),
|
|
179
|
+
then: [{ kind: "assign", localId: "position.0", value: bin("+", position, length), loc }], else_: null, loc });
|
|
180
|
+
}
|
|
181
|
+
body.push({ kind: "if", cond: either(bin("<", position, numLit(0, loc)), bin(">=", position, length)),
|
|
182
|
+
then: [{ kind: "return", value: miss, loc }], else_: null, loc });
|
|
183
|
+
if (method === "at") {
|
|
184
|
+
body.push({ kind: "return", value: wrap({ kind: "strIntrinsic", method: "charAt", receiver: stringReceiver, args: [position], type: STRING, loc }), loc });
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
body.push({ kind: "varDecl", localId: "first.0", init: { kind: "strIntrinsic", method: "charCodeAt", receiver: stringReceiver, args: [position], type: F64, loc }, loc });
|
|
188
|
+
body.push({ kind: "if", cond: either(either(bin("<", first, numLit(0xd800, loc)), bin(">", first, numLit(0xdbff, loc))), bin(">=", bin("+", position, numLit(1, loc)), length)),
|
|
189
|
+
then: [{ kind: "return", value: wrap(first), loc }], else_: null, loc });
|
|
190
|
+
body.push({ kind: "varDecl", localId: "second.0", init: { kind: "strIntrinsic", method: "charCodeAt", receiver: stringReceiver, args: [bin("+", position, numLit(1, loc))], type: F64, loc }, loc });
|
|
191
|
+
body.push({ kind: "if", cond: either(bin("<", second, numLit(0xdc00, loc)), bin(">", second, numLit(0xdfff, loc))),
|
|
192
|
+
then: [{ kind: "return", value: wrap(first), loc }], else_: null, loc });
|
|
193
|
+
body.push({ kind: "return", value: wrap(bin("+", bin("+", bin("*", bin("-", first, numLit(0xd800, loc)), numLit(1024, loc)), bin("-", second, numLit(0xdc00, loc))), numLit(0x10000, loc))), loc });
|
|
194
|
+
}
|
|
195
|
+
lowerer.liftedFns.push({
|
|
196
|
+
name: helper,
|
|
197
|
+
params: [{ localId: "arg.0", name: "receiver", type: receiver.type }, { localId: "arg.1", name: "index", type: index.type }],
|
|
198
|
+
returnType: resultType,
|
|
199
|
+
locals: [
|
|
200
|
+
{ id: "arg.0", name: "receiver", type: receiver.type, mutable: false },
|
|
201
|
+
{ id: "arg.1", name: "index", type: index.type, mutable: false },
|
|
202
|
+
{ id: "receiver.0", name: "stringReceiver", type: STRING, mutable: false },
|
|
203
|
+
{ id: "length.0", name: "length", type: F64, mutable: false },
|
|
204
|
+
{ id: "numeric.0", name: "numeric", type: F64, mutable: false },
|
|
205
|
+
{ id: "position.0", name: "position", type: F64, mutable: method === "at" },
|
|
206
|
+
...(method === "codePointAt" ? [
|
|
207
|
+
{ id: "first.0", name: "first", type: F64, mutable: false },
|
|
208
|
+
{ id: "second.0", name: "second", type: F64, mutable: false },
|
|
209
|
+
] : []),
|
|
210
|
+
],
|
|
211
|
+
body, loc,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return { kind: "call", callee: helper, args: [receiver, index], type: resultType, loc };
|
|
215
|
+
}
|
|
216
|
+
function paddingFillString(lowerer, value, node) {
|
|
217
|
+
const loc = value.loc;
|
|
218
|
+
if (value.type.kind === "undefinedT" || value.type.kind === "void")
|
|
219
|
+
return strLit(" ", loc);
|
|
220
|
+
if (value.type.kind === "nullT")
|
|
221
|
+
return strLit("null", loc);
|
|
222
|
+
if (value.type.kind === "dyn") {
|
|
223
|
+
return {
|
|
224
|
+
kind: "ternary",
|
|
225
|
+
cond: { kind: "dynTest", test: "undefined", value, type: BOOL, loc },
|
|
226
|
+
then: strLit(" ", loc),
|
|
227
|
+
else_: { kind: "libCall", fn: "dyn.toStringCoerce", args: [value], type: STRING, loc },
|
|
228
|
+
type: STRING, loc,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (value.type.kind === "union") {
|
|
232
|
+
const unionId = value.type.unionId;
|
|
233
|
+
const arms = lowerer.unions.get(unionId).arms;
|
|
234
|
+
let result = strLit(" ", loc);
|
|
235
|
+
for (let tag = arms.length - 1; tag >= 0; tag--) {
|
|
236
|
+
const narrowed = { kind: "unionNarrow", unionId, tag, value, type: arms[tag], loc };
|
|
237
|
+
const converted = paddingFillString(lowerer, narrowed, node);
|
|
238
|
+
result = tag === arms.length - 1 ? converted : {
|
|
239
|
+
kind: "ternary",
|
|
240
|
+
cond: { kind: "unionIsTag", unionId, tag, value, negated: false, type: BOOL, loc },
|
|
241
|
+
then: converted, else_: result, type: STRING, loc,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
return lowerer.ensureString(value, node);
|
|
247
|
+
}
|
|
248
|
+
/** Pad arguments are evaluated before receiver/length coercion, while fill
|
|
249
|
+
* conversion runs only if padding is needed. One helper keeps that order for
|
|
250
|
+
* member calls and direct String.prototype calls alike. */
|
|
251
|
+
export function lowerStringPaddingCall(lowerer, call, method, receiver, receiverNode, argumentNodes) {
|
|
252
|
+
if (argumentNodes.length > 2) {
|
|
253
|
+
return lowerer.noLowering(`.${method} with ${argumentNodes.length} arguments`, call);
|
|
254
|
+
}
|
|
255
|
+
const loc = locOf(call);
|
|
256
|
+
const maxLength = lowerPositionArgument(lowerer, argumentNodes[0], numLit(0, loc));
|
|
257
|
+
const fillNode = argumentNodes[1];
|
|
258
|
+
const undefinedFill = fillNode ? lowerStaticallyUndefinedArgument(lowerer, fillNode) : null;
|
|
259
|
+
const rawFill = !fillNode ? strLit(" ", loc) : undefinedFill
|
|
260
|
+
? defaultAfterUndefined(undefinedFill, strLit(" ", loc))
|
|
261
|
+
: lowerer.lowerExpr(fillNode);
|
|
262
|
+
const fill = rawFill.type.kind === "nullT"
|
|
263
|
+
? defaultAfterUndefined(rawFill, strLit("null", loc)) : rawFill;
|
|
264
|
+
const values = [receiver, maxLength, fill];
|
|
265
|
+
const key = `str.pad:${method}:${values.map(value => typeKey(value.type)).join(":")}`;
|
|
266
|
+
let helper = lowerer.widthHelpers.get(key);
|
|
267
|
+
if (!helper) {
|
|
268
|
+
helper = `%str.pad.${lowerer.widthHelpers.size}`;
|
|
269
|
+
const params = values.map((value, index) => ({ localId: `arg.${index}`, name: `arg${index}`, type: value.type }));
|
|
270
|
+
const rawReceiver = varRef("arg.0", receiver.type, loc);
|
|
271
|
+
const convertedReceiver = receiver.type.kind === "dyn" ? {
|
|
272
|
+
kind: "ternary",
|
|
273
|
+
cond: { kind: "dynTest", test: "nullish", value: rawReceiver, type: BOOL, loc },
|
|
274
|
+
then: nodeThrowExpr(1, "", `String.prototype.${method} called on null or undefined`, STRING, loc),
|
|
275
|
+
else_: { kind: "libCall", fn: "dyn.toStringCoerce", args: [rawReceiver], type: STRING, loc },
|
|
276
|
+
type: STRING, loc,
|
|
277
|
+
} : lowerer.ensureString(rawReceiver, receiverNode);
|
|
278
|
+
const stringReceiver = varRef("receiver.0", STRING, loc);
|
|
279
|
+
const length = varRef("length.0", F64, loc);
|
|
280
|
+
const fillValue = varRef("arg.2", fill.type, loc);
|
|
281
|
+
const padded = {
|
|
282
|
+
kind: "strIntrinsic", method, receiver: stringReceiver,
|
|
283
|
+
args: [length, paddingFillString(lowerer, fillValue, fillNode ?? call)], type: STRING, loc,
|
|
284
|
+
};
|
|
285
|
+
const result = {
|
|
286
|
+
kind: "ternary",
|
|
287
|
+
cond: {
|
|
288
|
+
kind: "bin", op: ">=", left: length,
|
|
289
|
+
right: {
|
|
290
|
+
kind: "bin", op: "+",
|
|
291
|
+
left: { kind: "strIntrinsic", method: "length", receiver: stringReceiver, args: [], type: F64, loc },
|
|
292
|
+
right: numLit(1, loc), type: F64, loc,
|
|
293
|
+
},
|
|
294
|
+
type: BOOL, loc,
|
|
295
|
+
},
|
|
296
|
+
then: padded, else_: stringReceiver, type: STRING, loc,
|
|
297
|
+
};
|
|
298
|
+
const body = [
|
|
299
|
+
{ kind: "varDecl", localId: "receiver.0", init: convertedReceiver, loc },
|
|
300
|
+
{ kind: "varDecl", localId: "length.0", init: positionNumber(lowerer, varRef("arg.1", maxLength.type, loc), numLit(0, loc), argumentNodes[0] ?? call, "string padding length"), loc },
|
|
301
|
+
{ kind: "return", value: result, loc },
|
|
302
|
+
];
|
|
303
|
+
lowerer.widthHelpers.set(key, helper);
|
|
304
|
+
lowerer.liftedFns.push({
|
|
305
|
+
name: helper, params, returnType: STRING,
|
|
306
|
+
locals: [
|
|
307
|
+
...params.map(param => ({ id: param.localId, name: param.name, type: param.type, mutable: false })),
|
|
308
|
+
{ id: "receiver.0", name: "receiver", type: STRING, mutable: false },
|
|
309
|
+
{ id: "length.0", name: "length", type: F64, mutable: false },
|
|
310
|
+
],
|
|
311
|
+
body, loc,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return { kind: "call", callee: helper, args: values, type: STRING, loc };
|
|
315
|
+
}
|
|
26
316
|
/** Regex method calls, both directions: `re.test(s)` on a regex receiver,
|
|
27
317
|
* and `s.replace(re, tpl)` / `s.replaceAll(re, tpl)` / `s.split(re)` on a
|
|
28
318
|
* string receiver whose FIRST ARGUMENT is a regex (the string-pattern
|
|
@@ -52,6 +342,9 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
|
|
|
52
342
|
};
|
|
53
343
|
const loc = locOf(call);
|
|
54
344
|
if (receiverKind === "regex" && name === "test") {
|
|
345
|
+
if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
|
|
346
|
+
lowerer.noLowering("RegExp.prototype.test with surplus or spread arguments", call);
|
|
347
|
+
}
|
|
55
348
|
// The statefulness fence, at compile time where the flags are
|
|
56
349
|
// visible: a literal receiver (possibly parenthesized). Values that
|
|
57
350
|
// flow through variables hit the same fence at runtime.
|
|
@@ -65,7 +358,7 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
|
|
|
65
358
|
}
|
|
66
359
|
}
|
|
67
360
|
const receiver = lowerReceiver();
|
|
68
|
-
const args = call.arguments
|
|
361
|
+
const args = [lowerRegexSubject(lowerer, call.arguments[0], loc)];
|
|
69
362
|
return { kind: "regexIntrinsic", method: "test", receiver, args, type: BOOL, loc };
|
|
70
363
|
}
|
|
71
364
|
// `re.exec(s)` for non-g/y regexes: spec-identical to `s.match(re)`
|
|
@@ -76,8 +369,8 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
|
|
|
76
369
|
// compile time on literal receivers, exactly test()'s stance; values
|
|
77
370
|
// reaching the runtime with those flags abort there.
|
|
78
371
|
if (receiverKind === "regex" && name === "exec") {
|
|
79
|
-
if (call.arguments.length
|
|
80
|
-
return null;
|
|
372
|
+
if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement))
|
|
373
|
+
return null;
|
|
81
374
|
let recv = access.expression;
|
|
82
375
|
while (ts.isParenthesizedExpression(recv))
|
|
83
376
|
recv = recv.expression;
|
|
@@ -88,9 +381,13 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
|
|
|
88
381
|
}
|
|
89
382
|
}
|
|
90
383
|
const re = lowerReceiver();
|
|
91
|
-
const subject = lowerer
|
|
384
|
+
const subject = lowerRegexSubject(lowerer, call.arguments[0], loc);
|
|
92
385
|
const resultT = { kind: "union", unionId: lowerer.unions.intern([arrayOf(STRING), { kind: "nullT" }]) };
|
|
93
|
-
|
|
386
|
+
// The shared match intrinsic takes the string first; preserve exec's
|
|
387
|
+
// receiver-before-subject evaluation order before swapping operands.
|
|
388
|
+
const saved = lowerer.declareHiddenLocal("%execReceiver", re.type);
|
|
389
|
+
const result = { kind: "regexIntrinsic", method: "match", receiver: subject, args: [varRef(saved.id, re.type, loc)], type: resultT, loc };
|
|
390
|
+
return { kind: "seqExpr", stmts: [{ kind: "varDecl", localId: saved.id, init: re, loc }], result, type: resultT, loc };
|
|
94
391
|
}
|
|
95
392
|
// `s.match(re)` for non-g/y regexes: Node's exec-shaped result reduced
|
|
96
393
|
// to the honest slice — the `string[] | null` union holding
|
|
@@ -212,18 +509,16 @@ export function lowerRegexMethodCall(lowerer, call, access, dynReceiver) {
|
|
|
212
509
|
return null;
|
|
213
510
|
}
|
|
214
511
|
/** `s.slice(1, 4)` and friends → strIntrinsic. Null when this isn't an
|
|
215
|
-
* ambient string method call (caller keeps its generic rejection).
|
|
216
|
-
*
|
|
217
|
-
|
|
218
|
-
* JSON-safe). tsc has already checked arity and argument types against
|
|
219
|
-
* ambient/scriptc.d.ts. */
|
|
220
|
-
export function lowerStringMethodCall(lowerer, call, access, dynReceiver) {
|
|
512
|
+
* ambient string method call (caller keeps its generic rejection).
|
|
513
|
+
* Complete position defaults and conversions here for both backends. */
|
|
514
|
+
export function lowerStringMethodCall(lowerer, call, access, dynReceiver, argumentNodes = call.arguments) {
|
|
221
515
|
if (lowerer.chainBlocked(access, call))
|
|
222
516
|
return null;
|
|
223
517
|
if (dynReceiver === undefined && access.name.text === "localeCompare")
|
|
224
518
|
return lowerLocaleCompareCall(lowerer, call, access);
|
|
225
519
|
const entry = own(STR_METHODS, access.name.text);
|
|
226
|
-
|
|
520
|
+
const indexMethod = STRING_INDEX_METHODS.has(access.name.text) ? access.name.text : null;
|
|
521
|
+
if (!entry && !indexMethod)
|
|
227
522
|
return null;
|
|
228
523
|
// A validated dyn receiver (`pkg.name.replace(...)` on a JSON.parse
|
|
229
524
|
// value) arrives pre-extracted through `dynReceiver`; its checker type
|
|
@@ -244,29 +539,120 @@ export function lowerStringMethodCall(lowerer, call, access, dynReceiver) {
|
|
|
244
539
|
if (!lowerer.isStdlibMember(access))
|
|
245
540
|
return null;
|
|
246
541
|
}
|
|
247
|
-
// The lib declares optional parameters beyond
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
lowerer.noLowering(`.${access.name.text} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"} on strings`, call);
|
|
542
|
+
// The lib declares optional parameters beyond some lowered forms; fence
|
|
543
|
+
// those arities instead of passing arguments the runtime doesn't take.
|
|
544
|
+
if (argumentNodes.length < (entry?.minArgs ?? 0) || argumentNodes.length > (entry?.maxArgs ?? 1)) {
|
|
545
|
+
lowerer.noLowering(`.${access.name.text} with ${argumentNodes.length} argument${argumentNodes.length === 1 ? "" : "s"} on strings`, call);
|
|
252
546
|
}
|
|
253
547
|
const receiver = dynReceiver
|
|
254
548
|
? dynReceiver()
|
|
255
549
|
: lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text);
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
550
|
+
const loc = locOf(call);
|
|
551
|
+
if (indexMethod)
|
|
552
|
+
return lowerStringIndexCall(lowerer, call, indexMethod, receiver, access.expression, argumentNodes);
|
|
553
|
+
if (!entry)
|
|
554
|
+
return null;
|
|
555
|
+
if (entry.method === "split")
|
|
556
|
+
return lowerStringSplitCall(lowerer, call, receiver, access.expression, argumentNodes);
|
|
557
|
+
if (entry.method === "padStart" || entry.method === "padEnd") {
|
|
558
|
+
return lowerStringPaddingCall(lowerer, call, entry.method, receiver, access.expression, argumentNodes);
|
|
264
559
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
|
|
560
|
+
const searchMethod = entry.method === "indexOf" || entry.method === "includes" ||
|
|
561
|
+
entry.method === "startsWith" || entry.method === "endsWith";
|
|
562
|
+
if (searchMethod && argumentNodes.length < 2) {
|
|
563
|
+
const needle = lowerStringSearchArgument(lowerer, argumentNodes[0], loc);
|
|
564
|
+
return { kind: "strIntrinsic", method: entry.method, receiver, args: [needle], type: entry.result, loc };
|
|
565
|
+
}
|
|
566
|
+
if (searchMethod && argumentNodes.length === 2) {
|
|
567
|
+
const needleNode = argumentNodes[0];
|
|
568
|
+
const undefinedArg = lowerStaticallyUndefinedArgument(lowerer, needleNode);
|
|
569
|
+
let needle = undefinedArg
|
|
570
|
+
? defaultAfterUndefined(undefinedArg, strLit("undefined", loc))
|
|
571
|
+
: lowerer.lowerExpr(needleNode);
|
|
572
|
+
if (isUnitType(needle.type))
|
|
573
|
+
needle = coerceStringSearchValue(lowerer, needle, needleNode, loc);
|
|
574
|
+
const scalarUnion = needle.type.kind === "union" &&
|
|
575
|
+
(lowerer.unions.get(needle.type.unionId)?.arms.every((arm) => arm.kind === "string" || arm.kind === "f64" || arm.kind === "bool" || arm.kind === "bigint" || isUnitType(arm)) ?? false);
|
|
576
|
+
const scalarNeedle = needle.type.kind === "f64" || needle.type.kind === "bool" || needle.type.kind === "bigint" || scalarUnion;
|
|
577
|
+
if (needle.type.kind !== "string" && needle.type.kind !== "dyn" && !scalarNeedle) {
|
|
578
|
+
lowerer.noLowering(`.${entry.method} with '${lowerer.fmt(needle.type)}' search values`, call);
|
|
579
|
+
}
|
|
580
|
+
const defaultPosition = entry.method === "endsWith"
|
|
581
|
+
? { kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc }
|
|
582
|
+
: numLit(0, loc);
|
|
583
|
+
const position = lowerPositionArgument(lowerer, argumentNodes[1], defaultPosition);
|
|
584
|
+
if (needle.type.kind === "string" && (position.type.kind === "f64" || position.type.kind === "jsval")) {
|
|
585
|
+
return { kind: "strIntrinsic", method: entry.method, receiver, args: [needle, position], type: entry.result, loc };
|
|
586
|
+
}
|
|
587
|
+
const key = `str.positions:${entry.method}:${typeKey(needle.type)}:${typeKey(position.type)}`;
|
|
588
|
+
let helper = lowerer.widthHelpers.get(key);
|
|
589
|
+
if (!helper) {
|
|
590
|
+
helper = `%str.positions.${lowerer.widthHelpers.size}`;
|
|
591
|
+
const params = [receiver, needle, position].map((arg, index) => ({ localId: `arg.${index}`, name: `arg${index}`, type: arg.type }));
|
|
592
|
+
const coerceNeedle = needle.type.kind !== "string";
|
|
593
|
+
const search = coerceNeedle ? varRef("search.0", STRING, loc) : varRef("arg.1", STRING, loc);
|
|
594
|
+
const result = {
|
|
595
|
+
kind: "strIntrinsic", method: entry.method, receiver: varRef("arg.0", STRING, loc),
|
|
596
|
+
args: [
|
|
597
|
+
search,
|
|
598
|
+
positionNumber(lowerer, varRef("arg.2", position.type, loc), defaultPosition, argumentNodes[1], "string position"),
|
|
599
|
+
],
|
|
600
|
+
type: entry.result, loc,
|
|
601
|
+
};
|
|
602
|
+
const locals = params.map(param => ({ id: param.localId, name: param.name, type: param.type, mutable: false }));
|
|
603
|
+
const body = [];
|
|
604
|
+
if (coerceNeedle) {
|
|
605
|
+
const value = varRef("arg.1", needle.type, loc);
|
|
606
|
+
const init = needle.type.kind === "dyn"
|
|
607
|
+
? { kind: "libCall", fn: "dyn.toStringCoerce", args: [value], type: STRING, loc }
|
|
608
|
+
: coerceStringSearchValue(lowerer, value, needleNode, loc);
|
|
609
|
+
locals.push({ id: "search.0", name: "search", type: STRING, mutable: false });
|
|
610
|
+
body.push({ kind: "varDecl", localId: "search.0", init, loc });
|
|
611
|
+
}
|
|
612
|
+
body.push({ kind: "return", value: result, loc });
|
|
613
|
+
lowerer.widthHelpers.set(key, helper);
|
|
614
|
+
lowerer.liftedFns.push({
|
|
615
|
+
name: helper, params, returnType: entry.result,
|
|
616
|
+
locals, body, loc,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
return { kind: "call", callee: helper, args: [receiver, needle, position], type: entry.result, loc };
|
|
620
|
+
}
|
|
621
|
+
if (entry.method === "charAt" || entry.method === "charCodeAt" || entry.method === "slice" || entry.method === "substring" || entry.method === "repeat") {
|
|
622
|
+
const defaults = [numLit(0, loc)];
|
|
623
|
+
if (entry.method === "slice" || entry.method === "substring") {
|
|
624
|
+
defaults.push({ kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc });
|
|
625
|
+
}
|
|
626
|
+
const args = defaults.map((value, index) => lowerPositionArgument(lowerer, argumentNodes[index], value));
|
|
627
|
+
const subject = entry.method === "repeat" ? "string repeat count" : "string position";
|
|
628
|
+
// Keep ordinary numeric calls on the direct intrinsic path, including the
|
|
629
|
+
// existing boundary validation for island values in numeric slots.
|
|
630
|
+
if (args.every(arg => arg.type.kind === "f64" || arg.type.kind === "jsval")) {
|
|
631
|
+
return { kind: "strIntrinsic", method: entry.method, receiver, args, type: entry.result, loc };
|
|
632
|
+
}
|
|
633
|
+
// A helper evaluates all arguments before conversions can invoke hooks or
|
|
634
|
+
// throw. Its parameters also give owned strings/unions a per-call lifetime
|
|
635
|
+
// when the call occurs in a loop condition or short-circuit expression.
|
|
636
|
+
const key = `str.positions:${entry.method}:${args.map(arg => typeKey(arg.type)).join(":")}`;
|
|
637
|
+
let helper = lowerer.widthHelpers.get(key);
|
|
638
|
+
if (!helper) {
|
|
639
|
+
helper = `%str.positions.${lowerer.widthHelpers.size}`;
|
|
640
|
+
const params = [receiver, ...args].map((arg, index) => ({ localId: `arg.${index}`, name: `arg${index}`, type: arg.type }));
|
|
641
|
+
const result = {
|
|
642
|
+
kind: "strIntrinsic", method: entry.method, receiver: varRef("arg.0", STRING, loc),
|
|
643
|
+
args: args.map((arg, index) => positionNumber(lowerer, varRef(`arg.${index + 1}`, arg.type, loc), defaults[index], argumentNodes[index] ?? call, subject)),
|
|
644
|
+
type: entry.result, loc,
|
|
645
|
+
};
|
|
646
|
+
lowerer.widthHelpers.set(key, helper);
|
|
647
|
+
lowerer.liftedFns.push({
|
|
648
|
+
name: helper, params, returnType: entry.result,
|
|
649
|
+
locals: params.map(param => ({ id: param.localId, name: param.name, type: param.type, mutable: false })),
|
|
650
|
+
body: [{ kind: "return", value: result, loc }], loc,
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
return { kind: "call", callee: helper, args: [receiver, ...args], type: entry.result, loc };
|
|
269
654
|
}
|
|
655
|
+
const args = argumentNodes.map((a) => lowerer.lowerExpr(a));
|
|
270
656
|
return {
|
|
271
657
|
kind: "strIntrinsic",
|
|
272
658
|
method: entry.method,
|