mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-12 02:13:04 +08:00
Merge main
This commit is contained in:
+4
-1
@@ -2109,6 +2109,8 @@ declare module "os" {
|
||||
export function version(): string;
|
||||
export function endianness(): "BE" | "LE";
|
||||
export function getPriority(pid?: number): number;
|
||||
export function setPriority(priority: number): void;
|
||||
export function setPriority(pid: number, priority: number): void;
|
||||
/* The TARGET platform's null device ("\\\\.\\nul" under a win32 triple). */
|
||||
export const devNull: string;
|
||||
/* The platform's constants: null-prototype objects with read-only
|
||||
@@ -3618,7 +3620,8 @@ declare module "querystring" {
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| ReadonlyArray<string | number | boolean>
|
||||
| bigint
|
||||
| ReadonlyArray<string | number | boolean | bigint>
|
||||
| null
|
||||
| undefined;
|
||||
}
|
||||
|
||||
@@ -2190,6 +2190,12 @@ export class LlEmitter {
|
||||
if (this.mod.lib.exports.some((e) => e.returns === "bytes")) {
|
||||
this.declare(`declare void @scr_library_bytes_out(ptr, ptr, ptr)`);
|
||||
}
|
||||
if (this.mod.lib.exports.some((e) => e.params.includes("cstring"))) {
|
||||
this.declare(`declare ptr @scr_library_cstr_in(ptr, ptr)`);
|
||||
}
|
||||
if (this.mod.lib.exports.some((e) => e.returns === "cstring")) {
|
||||
this.declare(`declare ptr @scr_library_cstr_out(ptr)`);
|
||||
}
|
||||
}
|
||||
// Helpers assemble BEFORE the declaration table flushes (they add
|
||||
// write/abort declarations).
|
||||
@@ -2966,6 +2972,15 @@ export class LlEmitter {
|
||||
``,
|
||||
);
|
||||
}
|
||||
if (e.inboundCstringTrap !== undefined) {
|
||||
// The cstring-in helper's host-contract trap message (a NULL
|
||||
// `const char *`): same assembled structured form and SC4012 code.
|
||||
const trapBytes = Buffer.byteLength(e.inboundCstringTrap, "utf8");
|
||||
out.push(
|
||||
`@sc_lib_cstr_trap_${e.symbol} = internal constant [${trapBytes + 1} x i8] c"${llStrBytes(e.inboundCstringTrap)}"`,
|
||||
``,
|
||||
);
|
||||
}
|
||||
e.params.forEach((cls, i) => {
|
||||
switch (cls) {
|
||||
case "f64":
|
||||
@@ -3023,6 +3038,13 @@ export class LlEmitter {
|
||||
);
|
||||
args.push(`ptr %c${i}`);
|
||||
break;
|
||||
case "cstring":
|
||||
params.push(`ptr %a${i}`);
|
||||
body.push(
|
||||
` %c${i} = call ptr @scr_library_cstr_in(ptr %a${i}, ptr @sc_lib_cstr_trap_${e.symbol})`,
|
||||
);
|
||||
args.push(`ptr %c${i}`);
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (e.returns === "string" || e.returns === "bytes") {
|
||||
@@ -3087,6 +3109,16 @@ export class LlEmitter {
|
||||
` ret void`,
|
||||
);
|
||||
break;
|
||||
case "cstring":
|
||||
// The result's arena copy, returned as `const char *`.
|
||||
retType = "ptr";
|
||||
body.push(
|
||||
` %r = call ptr ${target}(${callArgs})`,
|
||||
` call void @scr_library_check_exc()`,
|
||||
` %z = call ptr @scr_library_cstr_out(ptr %r)`,
|
||||
` ret ptr %z`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
out.push(
|
||||
`define ${retType} @${e.symbol}(${params.join(", ")}) ${FN_ATTRS} { ; library export ${e.fnName}`,
|
||||
|
||||
@@ -333,6 +333,16 @@ export function emitCallExpr(
|
||||
natArgs.push(`ptr ${data}`, `${host.sizeType} ${len}`);
|
||||
break;
|
||||
}
|
||||
case "cstring": {
|
||||
// The string's own NUL-terminated data, after the check that
|
||||
// it holds no U+0000 (a C string cannot carry one).
|
||||
host.declare(`declare ptr @scr_library_cstr_arg(ptr)`);
|
||||
const data = B.tmp();
|
||||
B.line(`${data} = call ptr @scr_library_cstr_arg(ptr ${arg.name})`);
|
||||
natTypes.push("ptr");
|
||||
natArgs.push(`ptr ${data}`);
|
||||
break;
|
||||
}
|
||||
case "bytes": {
|
||||
const lenPtr = B.tmp();
|
||||
const len = B.tmp();
|
||||
|
||||
@@ -188,6 +188,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"os.uptime": "scr_os_uptime",
|
||||
"os.loadavg": "scr_os_loadavg",
|
||||
"os.getPriority": "scr_os_get_priority",
|
||||
"os.setPriority": "scr_os_set_priority",
|
||||
"os.cpusTake": "scr_os_cpus_take",
|
||||
"os.cpusModel": "scr_os_cpus_model",
|
||||
"os.cpusField": "scr_os_cpus_field",
|
||||
@@ -477,6 +478,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"strdec.write": "scr_strdec_write",
|
||||
"strdec.next": "scr_strdec_next",
|
||||
"strdec.end": "scr_strdec_end",
|
||||
"strdec.encoding": "scr_strdec_encoding",
|
||||
"stats.isFile": "scr_stats_is_file",
|
||||
"stats.isDirectory": "scr_stats_is_dir",
|
||||
"stats.isSymbolicLink": "scr_stats_is_symlink",
|
||||
|
||||
@@ -129,6 +129,7 @@ const WASI_UNAVAILABLE_HOST_FACTS: ReadonlyMap<string, string> = new Map([
|
||||
["os.loadavg", "system load averages (WASI Preview 1 has no load API)"],
|
||||
["os.uptime", "system uptime (WASI Preview 1 has no boot-time API)"],
|
||||
["os.getPriority", "process scheduling priority (WASI Preview 1 has no priority API)"],
|
||||
["os.setPriority", "process scheduling priority (WASI Preview 1 has no priority API)"],
|
||||
["process.memoryUsageTake", "process memory statistics (WASI Preview 1 has no memory API)"],
|
||||
["process.rss", "process memory statistics (WASI Preview 1 has no memory API)"],
|
||||
["process.ppid", "the parent process id (WASI Preview 1 has no process API)"],
|
||||
|
||||
@@ -40,6 +40,7 @@ import { lowerForkCall } from "./child-process.js";
|
||||
import {
|
||||
lowerOsCpusCall,
|
||||
lowerOsGetPriorityCall,
|
||||
lowerOsSetPriorityCall,
|
||||
lowerOsNetworkInterfacesCall,
|
||||
lowerOsUserInfoCall,
|
||||
lowerPathFormatCall,
|
||||
@@ -163,6 +164,9 @@ export function lowerBuiltinModuleCall(
|
||||
if (bi.module === "os" && bi.member === "cpus") {
|
||||
return lowerOsCpusCall(lowerer, expr, loc);
|
||||
}
|
||||
if (bi.module === "os" && bi.member === "setPriority") {
|
||||
return lowerOsSetPriorityCall(lowerer, expr, loc);
|
||||
}
|
||||
if (bi.module === "os" && bi.member === "getPriority") {
|
||||
return lowerOsGetPriorityCall(lowerer, expr, loc);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
STRING,
|
||||
type SrcLoc,
|
||||
UNDEFINED_T,
|
||||
VOID,
|
||||
arrayOf,
|
||||
typeKey,
|
||||
} from "../../../ir/ir.js";
|
||||
@@ -511,6 +512,29 @@ export function lowerPathFormatCall(
|
||||
return { kind: "call", callee: helper, args: [value], type: STRING, loc };
|
||||
}
|
||||
|
||||
/** `os.setPriority([pid, ]priority)` — the one-argument form is pid 0
|
||||
* (Node's `if (priority === undefined) { priority = pid; pid = 0; }`);
|
||||
* the runtime validates both. */
|
||||
export function lowerOsSetPriorityCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const args = call.arguments;
|
||||
if (args.length < 1 || args.length > 2 || args.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(
|
||||
"setPriority with these arguments",
|
||||
call,
|
||||
"setPriority(priority) or setPriority(pid, priority) takes numbers",
|
||||
);
|
||||
}
|
||||
const [pid, priority] =
|
||||
args.length === 1
|
||||
? [numLit(0, loc), lowerer.lowerExprExpecting(args[0]!, F64)]
|
||||
: [lowerer.lowerExprExpecting(args[0]!, F64), lowerer.lowerExprExpecting(args[1]!, F64)];
|
||||
return { kind: "libCall", fn: "os.setPriority", args: [pid, priority], type: VOID, loc };
|
||||
}
|
||||
|
||||
/** `os.getPriority(pid?)` — an omitted or undefined pid is 0 (Node's
|
||||
* `if (pid === undefined) pid = 0`); the runtime validates the rest. */
|
||||
export function lowerOsGetPriorityCall(
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
} from "../../../ir/ir.js";
|
||||
import { optionalStringTags } from "./arguments.js";
|
||||
import { type BuiltinModuleFn } from "../surfaces.js";
|
||||
import { lowerDynDispatchMethodCall } from "../lower-calls.js";
|
||||
|
||||
/** URL component assignment captures its receiver and RHS once, and yields
|
||||
* the original RHS after the setter performs string conversion. */
|
||||
@@ -255,10 +256,12 @@ export function lowerSearchParamsNew(
|
||||
return { kind: "libCall", fn: "sp.fromPairs", args: [init], type: SEARCH_PARAMS_T, loc };
|
||||
}
|
||||
if (init.type.kind === "array" && init.type.elem.kind === "record") {
|
||||
const pairs = searchParamsFromTuples(lowerer, init, init.type.elem, loc);
|
||||
if (pairs) return pairs;
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
arg,
|
||||
"URLSearchParams from tuple-typed pairs (type the pairs as string[][] — the tuple rows have a different layout)",
|
||||
"URLSearchParams from tuple rows other than [string, string] (type the pairs as [string, string][] or string[][])",
|
||||
);
|
||||
}
|
||||
lowerer.unsupported(
|
||||
@@ -268,6 +271,97 @@ export function lowerSearchParamsNew(
|
||||
);
|
||||
}
|
||||
|
||||
/** `new URLSearchParams(pairs)` over a `[string, string][]` (an
|
||||
* Object.entries result, for example): a helper per tuple shape appends
|
||||
* each pair's name and value in array order. Null for other row types. */
|
||||
function searchParamsFromTuples(
|
||||
lowerer: Lowerer,
|
||||
init: IrExpr,
|
||||
row: IrType & { kind: "record" },
|
||||
loc: SrcLoc,
|
||||
): IrExpr | null {
|
||||
const shape = lowerer.shapes.get(row.shapeId);
|
||||
if (
|
||||
!shape?.tuple ||
|
||||
shape.fields.length !== 2 ||
|
||||
!shape.fields.every(
|
||||
(field) => (field.name === "0" || field.name === "1") && field.type.kind === "string",
|
||||
)
|
||||
)
|
||||
return null;
|
||||
const key = `sp.fromTuples:${row.shapeId}`;
|
||||
let helper = lowerer.valueHelpers.get(key);
|
||||
if (!helper) {
|
||||
helper = `%sp.fromTuples.${lowerer.valueHelpers.size}`;
|
||||
lowerer.valueHelpers.set(key, helper);
|
||||
const pairsT = init.type;
|
||||
const sp = (): IrExpr => varRef("sp.0", SEARCH_PARAMS_T, loc);
|
||||
const pair = (): IrExpr => varRef("pair.0", row, loc);
|
||||
const field = (name: "0" | "1"): IrExpr => ({
|
||||
kind: "recordGet",
|
||||
obj: pair(),
|
||||
shapeId: row.shapeId,
|
||||
field: name,
|
||||
type: STRING,
|
||||
loc,
|
||||
});
|
||||
const body: IrStmt[] = [
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: "sp.0",
|
||||
init: { kind: "libCall", fn: "sp.new", args: [], type: SEARCH_PARAMS_T, loc },
|
||||
loc,
|
||||
},
|
||||
countedFor(
|
||||
loc,
|
||||
{
|
||||
kind: "arrIntrinsic",
|
||||
method: "length",
|
||||
receiver: varRef("pairs.0", pairsT, loc),
|
||||
args: [],
|
||||
type: F64,
|
||||
loc,
|
||||
},
|
||||
(index) => [
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: "pair.0",
|
||||
init: { kind: "arrayGet", arr: varRef("pairs.0", pairsT, loc), index, type: row, loc },
|
||||
loc,
|
||||
},
|
||||
{
|
||||
kind: "exprStmt",
|
||||
expr: {
|
||||
kind: "libCall",
|
||||
fn: "sp.append",
|
||||
args: [sp(), field("0"), field("1")],
|
||||
type: VOID,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
},
|
||||
],
|
||||
),
|
||||
{ kind: "return", value: sp(), loc },
|
||||
];
|
||||
const fn: IrFunction = {
|
||||
name: helper,
|
||||
params: [{ localId: "pairs.0", name: "pairs", type: pairsT }],
|
||||
returnType: SEARCH_PARAMS_T,
|
||||
locals: [
|
||||
{ id: "pairs.0", name: "pairs", type: pairsT, mutable: false },
|
||||
{ id: "sp.0", name: "sp", type: SEARCH_PARAMS_T, mutable: false },
|
||||
{ id: "i.0", name: "i", type: F64, mutable: true },
|
||||
{ id: "pair.0", name: "pair", type: row, mutable: false },
|
||||
],
|
||||
body,
|
||||
loc,
|
||||
};
|
||||
lowerer.liftedFns.push(fn);
|
||||
}
|
||||
return { kind: "call", callee: helper, args: [init], type: SEARCH_PARAMS_T, loc };
|
||||
}
|
||||
|
||||
function lowerOptionalStringSearchParams(
|
||||
lowerer: Lowerer,
|
||||
init: IrExpr,
|
||||
@@ -534,12 +628,14 @@ export function lowerSearchParamsMethodCall(
|
||||
if (name === "forEach" && args.length === 1) {
|
||||
return lowerSpForEachCall(lowerer, call, access);
|
||||
}
|
||||
if (name === "keys" || name === "values" || name === "entries") {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call,
|
||||
`URLSearchParams iterator objects outside a for-of head (write \`for (const x of sp.${name}())\` directly)`,
|
||||
);
|
||||
if ((name === "keys" || name === "values" || name === "entries") && args.length === 0) {
|
||||
// Outside a for-of head the iterator is a live object: the checked-
|
||||
// dynamic view's iterator walks the pair list by index
|
||||
// (scr_native_sp_step), so entries appended after it was created are
|
||||
// visited, as in Node.
|
||||
const boxed = lowerer.coerceInto(access.expression, lowerer.lowerExpr(access.expression), DYN);
|
||||
const iterator = lowerDynDispatchMethodCall(lowerer, call, access, boxed, false);
|
||||
if (iterator) return iterator;
|
||||
}
|
||||
lowerer.noLowering(
|
||||
`URLSearchParams.${name}`,
|
||||
@@ -751,3 +847,21 @@ export function lowerLegacyUrlCall(
|
||||
}
|
||||
return { kind: "libCall", fn: fn.fn, args, type: fn.result, loc };
|
||||
}
|
||||
|
||||
/** A URLSearchParams value, or a keys()/values()/entries() call on one: an
|
||||
* iterable whose checked-dynamic view walks the live pair list. */
|
||||
export function isSearchParamsIterable(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
while (ts.isParenthesizedExpression(node)) node = node.expression;
|
||||
if (lowerer.mapTypeOf(lowerer.typeOf(node))?.kind === "searchParams") return true;
|
||||
if (
|
||||
!ts.isCallExpression(node) ||
|
||||
node.arguments.length !== 0 ||
|
||||
!ts.isPropertyAccessExpression(node.expression)
|
||||
)
|
||||
return false;
|
||||
const name = node.expression.name.text;
|
||||
return (
|
||||
(name === "keys" || name === "values" || name === "entries") &&
|
||||
lowerer.mapTypeOf(lowerer.typeOf(node.expression.expression))?.kind === "searchParams"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1004,6 +1004,10 @@ export function lowerArrayFromCall(
|
||||
if (src.type.kind === "dyn" && args.length === 1) {
|
||||
return lowerCheckedArrayFrom(lowerer, src, loc);
|
||||
}
|
||||
// Array.from(sp): the checked-dynamic view's live pair iteration.
|
||||
if (src.type.kind === "searchParams" && args.length === 1) {
|
||||
return lowerCheckedArrayFrom(lowerer, lowerer.coerceInto(args[0]!, src, DYN), loc);
|
||||
}
|
||||
// A generator (or an iterator adapted into one) drains in order.
|
||||
if (src.type.kind === "generator" && args.length === 1) {
|
||||
const arrT = lowerer.mapTypeOf(lowerer.typeOf(call));
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
} from "../collection-ingestion.js";
|
||||
import { strCharsCall } from "./array-construction.js";
|
||||
import { arrayElementRead } from "./array-iteration.js";
|
||||
import { isSearchParamsIterable } from "../builtins/url.js";
|
||||
|
||||
/** Ambient Map method calls. `get`/`set`/`has`/`delete`/`clear` lower to
|
||||
* mapIntrinsic; `forEach` desugars to a direct call of a synthetic loop
|
||||
@@ -1173,6 +1174,11 @@ export function lowerMapSeedNew(
|
||||
mapT,
|
||||
);
|
||||
}
|
||||
// new Map(sp) / new Map(sp.entries()): the checked-dynamic view's live
|
||||
// pairs seed the map in order.
|
||||
if (isSearchParamsIterable(lowerer, argNode)) {
|
||||
return mapFromSeedValue(lowerer, lowerer.lowerExprExpecting(argNode, DYN), mapT);
|
||||
}
|
||||
const input = collectionInput(lowerer, argNode);
|
||||
if (input) {
|
||||
if (!collectionDestinationMatches(lowerer, input, mapT)) return null;
|
||||
|
||||
@@ -79,7 +79,7 @@ import { isAssertClassNew } from "./assert-instances.js";
|
||||
import { lowerArrayConstructor } from "./containers/array-construction.js";
|
||||
import { lowerMapSeedNew, lowerSetSeedNew } from "./containers/collection-methods.js";
|
||||
import { lowerObjectConstructor } from "./containers/indexed-objects.js";
|
||||
import { bufEncoding } from "./containers/bytes.js";
|
||||
import { knownBufEncoding } from "./containers/bytes.js";
|
||||
import { isSafeToRepeat } from "./expressions/evaluation-safety.js";
|
||||
import { lowerSearchParamsNew } from "./builtins/url.js";
|
||||
import { lowerTextCodecNew } from "./builtins/text-codecs.js";
|
||||
@@ -8972,24 +8972,41 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
);
|
||||
}
|
||||
// `new StringDecoder(encoding?)` (node:string_decoder): the decoder
|
||||
// is a two-field record — the CANONICAL encoding name (aliases fold
|
||||
// at compile time, exactly what `.encoding` answers in Node) and
|
||||
// the packed-f64 pending state starting at 0 (nothing buffered).
|
||||
// The encoding must be a literal (Node's alias set); omitted means
|
||||
// utf8, Node's default.
|
||||
// is a two-field record — the CANONICAL encoding name (exactly what
|
||||
// `.encoding` answers in Node) and the packed-f64 pending state
|
||||
// starting at 0 (nothing buffered). A literal Buffer encoding folds
|
||||
// its alias at compile time; any other encoding argument is
|
||||
// normalized at run time (Node's normalizeEncoding, which throws
|
||||
// ERR_UNKNOWN_ENCODING for an unknown name). Omitted means utf8,
|
||||
// Node's default.
|
||||
if (symbol && symbol.name === "StringDecoder" && lowerer.isStdlibSymbol(symbol)) {
|
||||
const args = expr.arguments ?? [];
|
||||
if (args.length > 1) {
|
||||
lowerer.noLowering("new StringDecoder with 2 arguments", expr, undefined, symbol);
|
||||
}
|
||||
const encName =
|
||||
args.length === 1 ? bufEncoding(lowerer, "new StringDecoder", args[0]!) : "utf8";
|
||||
const encNode = args[0];
|
||||
const encT = encNode ? lowerer.typeOf(encNode) : undefined;
|
||||
const encName = !encNode
|
||||
? "utf8"
|
||||
: encT!.isStringLiteralType()
|
||||
? knownBufEncoding(encT!.value)
|
||||
: undefined;
|
||||
const encoding: IrExpr =
|
||||
encName !== undefined
|
||||
? { kind: "strLit", value: encName, type: STRING, loc }
|
||||
: {
|
||||
kind: "libCall",
|
||||
fn: "strdec.encoding",
|
||||
args: [lowerer.lowerExprExpecting(encNode!, DYN)],
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
const decT = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
||||
if (decT?.kind !== "record") lowerer.badType(expr, lowerer.typeOf(expr));
|
||||
return {
|
||||
kind: "recordLit",
|
||||
fields: [
|
||||
{ name: "%enc", value: { kind: "strLit", value: encName, type: STRING, loc } },
|
||||
{ name: "%enc", value: encoding },
|
||||
{ name: "%pending", value: { kind: "numLit", value: 0, type: F64, loc } },
|
||||
],
|
||||
type: decT,
|
||||
|
||||
@@ -225,7 +225,7 @@ import {
|
||||
} from "./lower-builtin-values.js";
|
||||
import { lowerArrayFromValue } from "./containers/array-construction.js";
|
||||
import { lowerPerfHooksTypeof } from "./builtins/performance.js";
|
||||
import { lowerUrlAssignment } from "./builtins/url.js";
|
||||
import { isSearchParamsIterable, lowerUrlAssignment } from "./builtins/url.js";
|
||||
import { jsBindingHasOpenWrites } from "./lower-stmts.js";
|
||||
import { checkedClassInstanceOf } from "./class-construction.js";
|
||||
import { lowerModuleNamespaceElement } from "./module-namespace-elements.js";
|
||||
@@ -5889,6 +5889,11 @@ export function lowerArrayLiteral(
|
||||
const drained = generatorDrain(lowerer, src, type.elem, locOf(el));
|
||||
if (drained) src = drained;
|
||||
}
|
||||
// `[...sp]` and `[...sp.entries()]` over a URLSearchParams: its
|
||||
// checked-dynamic view iterates the live pair list, and the drained
|
||||
// [name, value] pairs convert to the literal's tuple elements.
|
||||
const searchParamsSource = isSearchParamsIterable(lowerer, el.expression);
|
||||
if (src.type.kind === "searchParams") src = lowerer.coerceInto(el.expression, src, DYN);
|
||||
// A native checked iterable can supply scalar elements even when
|
||||
// the checker inferred a typed array from its producer. Drain once
|
||||
// and validate the elements before the ordinary spread copy.
|
||||
@@ -5897,7 +5902,8 @@ export function lowerArrayLiteral(
|
||||
(type.elem.kind === "dyn" ||
|
||||
type.elem.kind === "f64" ||
|
||||
type.elem.kind === "string" ||
|
||||
type.elem.kind === "bool")
|
||||
type.elem.kind === "bool" ||
|
||||
searchParamsSource)
|
||||
) {
|
||||
src = lowerer.coerceInto(el.expression, checkedIteratorPack(lowerer, src, locOf(el)), type);
|
||||
}
|
||||
|
||||
@@ -5037,6 +5037,23 @@ function jsObjectBindingExpands(
|
||||
) {
|
||||
const name = property(node.left);
|
||||
if (name === null || (name !== undefined && !names.has(name))) expands = true;
|
||||
// `state = { _tag: "Done", right }` stores an object with properties
|
||||
// the initializer's shape lacks; later reads of them must see them.
|
||||
const target = unwrap(node.left);
|
||||
const value = unwrap(node.right);
|
||||
const targetSymbol = ts.isIdentifier(target) ? lowerer.resolveValueSymbol(target) : undefined;
|
||||
if (
|
||||
targetSymbol &&
|
||||
aliases.has(targetSymbol) &&
|
||||
ts.isObjectLiteralExpression(value) &&
|
||||
value.properties.some(
|
||||
(field) =>
|
||||
!(ts.isPropertyAssignment(field) || ts.isShorthandPropertyAssignment(field)) ||
|
||||
!(ts.isIdentifier(field.name) || ts.isStringLiteral(field.name)) ||
|
||||
!names.has(field.name.text),
|
||||
)
|
||||
)
|
||||
expands = true;
|
||||
}
|
||||
if (ts.isDeleteExpression(node) && property(node.expression) !== undefined) expands = true;
|
||||
if (
|
||||
|
||||
@@ -1485,6 +1485,9 @@ export const BUILTIN_MODULE_FNS: Record<
|
||||
result: F64,
|
||||
valueParams: [{ mode: "optional", type: F64, defaultValue: 0 }],
|
||||
},
|
||||
// A one-argument call is setPriority(0, priority)
|
||||
// (lowerOsSetPriorityCall completes it).
|
||||
setPriority: { fn: "os.setPriority", params: [F64, F64], result: VOID },
|
||||
// Special-cased (lowerOsCpusCall): the call site's CpuInfo[] assembles
|
||||
// from one uv_cpu_info snapshot.
|
||||
cpus: { fn: "os.cpusTake", params: [], result: VOID },
|
||||
|
||||
@@ -34,6 +34,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"cp.spawnDynamic",
|
||||
"child.onDyn",
|
||||
"text.decoderEncoding",
|
||||
"strdec.encoding",
|
||||
"dyn.construct",
|
||||
"dyn.reflectApply",
|
||||
"dyn.reflectGet",
|
||||
@@ -556,6 +557,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"dyn.arrayProtoCall",
|
||||
"process.chdir",
|
||||
"os.getPriority",
|
||||
"os.setPriority",
|
||||
// Pattern length TypeError, brace-sequence RangeError, and the
|
||||
// SyntaxError of a segment regex invalid under the u flag.
|
||||
"path.matchesGlob",
|
||||
|
||||
@@ -550,6 +550,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
/** os.getPriority(pid): validateInt32 (RangeError ERR_OUT_OF_RANGE),
|
||||
* then the priority; a failure throws Node's SystemError. */
|
||||
"os.getPriority": { argTypes: [F64], result: F64 },
|
||||
/** os.setPriority(pid, priority): validateInt32 for both (priority in
|
||||
* -20..19, RangeError ERR_OUT_OF_RANGE), then the priority; a failure
|
||||
* throws Node's SystemError. */
|
||||
"os.setPriority": { argTypes: [F64, F64], result: VOID },
|
||||
/** os.cpus(): cpusTake snapshots every CPU and answers the count;
|
||||
* cpusModel(i) and cpusField(i, k) (k: 0 speed, 1 user, 2 nice, 3 sys,
|
||||
* 4 idle, 5 irq) read the snapshot. Never throw. */
|
||||
@@ -3785,6 +3789,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
"strdec.write": { argTypes: [STRING, F64, BYTES_U8], result: STRING },
|
||||
"strdec.next": { argTypes: [STRING, F64, BYTES_U8], result: F64 },
|
||||
"strdec.end": { argTypes: [STRING, F64], result: STRING },
|
||||
/** A runtime encoding argument's canonical name (Node's normalizeEncoding:
|
||||
* nullish and "" are utf8); an unknown name throws ERR_UNKNOWN_ENCODING. */
|
||||
"strdec.encoding": { argTypes: [DYN], result: STRING },
|
||||
// node:readline: the interface handle is f64; the callbacks' func types
|
||||
// are program-dependent (the emitter picks the adapter by parameter
|
||||
// count), so those slots are null like child.onExit's.
|
||||
|
||||
@@ -193,6 +193,7 @@ const MODULE_FIELDS: Record<StringFields<Omit<IrModule, "functions">>, Role> = {
|
||||
"lib.exports[].fnName": "reference",
|
||||
"lib.exports[].inboundBytesTrap": "other",
|
||||
"lib.exports[].inboundIntTrap": "other",
|
||||
"lib.exports[].inboundCstringTrap": "other",
|
||||
"lib.trapOverlays[].code": "other",
|
||||
"lib.trapOverlays[].teaching": "other",
|
||||
"lib.trapOverlays[].remediation": "other",
|
||||
|
||||
@@ -1176,8 +1176,19 @@ export interface IrLibExport {
|
||||
/** i64/u64 (ask 4): int64_t/uint64_t at the C edge — inbound values
|
||||
* range-check in the wrapper (`inboundIntTrap`), internal call sites
|
||||
* and returns are compile-time proven before this lands on the IR. */
|
||||
params: ("f64" | "bool" | "string" | "bytes" | "u8" | "u32" | "i32" | "i64" | "u64")[];
|
||||
returns: "f64" | "bool" | "string" | "bytes" | "void" | "i64" | "u64";
|
||||
params: (
|
||||
| "f64"
|
||||
| "bool"
|
||||
| "string"
|
||||
| "cstring"
|
||||
| "bytes"
|
||||
| "u8"
|
||||
| "u32"
|
||||
| "i32"
|
||||
| "i64"
|
||||
| "u64"
|
||||
)[];
|
||||
returns: "f64" | "bool" | "string" | "cstring" | "bytes" | "void" | "i64" | "u64";
|
||||
/** The exact sink-message bytes this wrapper passes to the inbound-bytes
|
||||
* marshalling helper's trap — present exactly when a parameter is
|
||||
* bytes-classed. Already the assembled structured trap-teaching form
|
||||
@@ -1192,6 +1203,10 @@ export interface IrLibExport {
|
||||
* value cannot ride f64 exactly (|v| past 2^53−1). Same assembled
|
||||
* structured form and the same SC4012 code — one host-contract story. */
|
||||
inboundIntTrap?: string;
|
||||
/** The sibling message for a NULL cstring parameter: present exactly
|
||||
* when a parameter is cstring-classed, passed to scr_library_cstr_in.
|
||||
* Same assembled structured form and the same SC4012 code. */
|
||||
inboundCstringTrap?: string;
|
||||
}
|
||||
|
||||
/** One runtime-trap overlay row: the profile's teaching (replaces the
|
||||
@@ -1218,7 +1233,7 @@ export interface IrLibCallback {
|
||||
name: string;
|
||||
/** The runtime channel slot (profile declaration order). */
|
||||
slot: number;
|
||||
params: ("f64" | "bool" | "string" | "bytes" | "u8" | "u32" | "i32")[];
|
||||
params: ("f64" | "bool" | "string" | "cstring" | "bytes" | "u8" | "u32" | "i32")[];
|
||||
returns: "f64" | "bool" | "u8" | "u32" | "i32" | "i64" | "u64" | "pointer" | "void";
|
||||
/** The unregistered-call trap text (a DETECTED trap: plain bytes the
|
||||
* library funnel classifies SC4025 and assembles with the current
|
||||
|
||||
@@ -357,7 +357,7 @@ export function validateModule(mod: IrModule): IrValidationError[] {
|
||||
const fits = (cls: string, t: IrType): boolean =>
|
||||
cls === "bool"
|
||||
? t.kind === "bool"
|
||||
: cls === "string"
|
||||
: cls === "string" || cls === "cstring"
|
||||
? t.kind === "string"
|
||||
: cls === "bytes"
|
||||
? t.kind === "bytes" && t.elem === "u8"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*
|
||||
* Ratified shape (the ratified library-mode design §5.1 + the ABI ratification session):
|
||||
* {
|
||||
* "profile_format": 1,
|
||||
* "profile_format": 1 | 2, // 2 adds the cstring class
|
||||
* "name": "<embedder identity string>",
|
||||
* "entry": "src/lib.ts", // ONE module, profile-relative
|
||||
* "emission": "llvm", // pins the emission; no fallback
|
||||
@@ -268,6 +268,7 @@ export const LIB_PARAM_CLASSES = [
|
||||
"f64",
|
||||
"bool",
|
||||
"string",
|
||||
"cstring",
|
||||
"bytes",
|
||||
"u8",
|
||||
"u32",
|
||||
@@ -282,7 +283,16 @@ export const LIB_PARAM_CLASSES = [
|
||||
* u8/u32/i32 plumbing classes stay param-only: they were ratified as
|
||||
* inbound plumbing, and outbound integers are the proven classes'
|
||||
* business. */
|
||||
export const LIB_RETURN_CLASSES = ["f64", "bool", "string", "bytes", "void", "i64", "u64"] as const;
|
||||
export const LIB_RETURN_CLASSES = [
|
||||
"f64",
|
||||
"bool",
|
||||
"string",
|
||||
"cstring",
|
||||
"bytes",
|
||||
"void",
|
||||
"i64",
|
||||
"u64",
|
||||
] as const;
|
||||
|
||||
/** Ask 4's declared integer classes (the prove-or-refuse pair). */
|
||||
export type LibIntClass = "i64" | "u64";
|
||||
@@ -297,7 +307,16 @@ export type LibReturnClass = (typeof LIB_RETURN_CLASSES)[number];
|
||||
* declared integer classes stay export-map surface: an outbound i64/u64
|
||||
* demands the prove-or-refuse machinery, which is scoped to export
|
||||
* returns today. */
|
||||
const LIB_CALLBACK_PARAM_CLASSES = ["f64", "bool", "string", "bytes", "u8", "u32", "i32"] as const;
|
||||
const LIB_CALLBACK_PARAM_CLASSES = [
|
||||
"f64",
|
||||
"bool",
|
||||
"string",
|
||||
"cstring",
|
||||
"bytes",
|
||||
"u8",
|
||||
"u32",
|
||||
"i32",
|
||||
] as const;
|
||||
/** Marshalling classes legal in a CALLBACK RETURN position (inbound: host
|
||||
* → compiled code). Scalars only — every conversion to f64 is exact by
|
||||
* construction; a buffer return would need an ownership contract the mode
|
||||
@@ -377,7 +396,7 @@ export interface LibrarySidecarConfig {
|
||||
}
|
||||
|
||||
export interface LibraryProfile {
|
||||
profileFormat: 1;
|
||||
profileFormat: 1 | 2;
|
||||
name: string;
|
||||
/** The profile file that produced this configuration. Semantic
|
||||
* designation errors anchor here, where the offending name is written. */
|
||||
@@ -516,8 +535,16 @@ export function loadLibraryProfile(
|
||||
}
|
||||
const p = raw as Record<string, unknown>;
|
||||
const format = req<number>(p["profile_format"], "profile_format", "number");
|
||||
if (format !== 1)
|
||||
throw new ProfileError(`unsupported profile_format ${format} (this scriptc reads format 1)`);
|
||||
if (format !== 1 && format !== 2)
|
||||
throw new ProfileError(
|
||||
`unsupported profile_format ${format} (this scriptc reads formats 1 and 2)`,
|
||||
);
|
||||
// The cstring class (a NUL-terminated `const char *`) is format 2.
|
||||
const cstringClass = (cls: string, path: string): void => {
|
||||
if (cls === "cstring" && format < 2) {
|
||||
throw new ProfileError(`'${path}' class 'cstring' requires profile_format 2`);
|
||||
}
|
||||
};
|
||||
// Root keys are strict (checked AFTER the format gate, so a future
|
||||
// format's profile refuses as the wrong format, not as a typo). The
|
||||
// ask-5 key names get the pointed message: a `fences` array pasted at
|
||||
@@ -656,6 +683,7 @@ export function loadLibraryProfile(
|
||||
`'${path}.params[${j}]' must be one of ${LIB_PARAM_CLASSES.join("/")}, got ${JSON.stringify(c)}`,
|
||||
);
|
||||
}
|
||||
cstringClass(c, `${path}.params[${j}]`);
|
||||
return c as LibParamClass;
|
||||
});
|
||||
const returns = req<string>(ee["returns"], `${path}.returns`, "string");
|
||||
@@ -666,6 +694,7 @@ export function loadLibraryProfile(
|
||||
: `'${path}.returns' must be one of ${LIB_RETURN_CLASSES.join("/")}, got '${returns}'`;
|
||||
throw new ProfileError(detail);
|
||||
}
|
||||
cstringClass(returns, `${path}.returns`);
|
||||
entries.push({ export: exportName, symbol, params, returns: returns as LibReturnClass });
|
||||
});
|
||||
|
||||
@@ -709,12 +738,13 @@ export function loadLibraryProfile(
|
||||
`'${path}.params[${j}]' must be one of ${LIB_CALLBACK_PARAM_CLASSES.join("/")}, got ${JSON.stringify(cls)}`,
|
||||
);
|
||||
}
|
||||
cstringClass(cls, `${path}.params[${j}]`);
|
||||
return cls as LibCallbackParamClass;
|
||||
});
|
||||
const cbReturns = req<string>(cc["returns"], `${path}.returns`, "string");
|
||||
if (!(LIB_CALLBACK_RETURN_CLASSES as readonly string[]).includes(cbReturns)) {
|
||||
const detail =
|
||||
cbReturns === "string" || cbReturns === "bytes"
|
||||
cbReturns === "string" || cbReturns === "cstring" || cbReturns === "bytes"
|
||||
? `'${path}.returns': a callback return stays scalar — a buffer flowing host → library needs an ownership contract the mode does not define; pass buffers as callback parameters (library → host, borrowed for the call) or entry parameters instead`
|
||||
: `'${path}.returns' must be one of ${LIB_CALLBACK_RETURN_CLASSES.join("/")}, got '${cbReturns}'`;
|
||||
throw new ProfileError(detail);
|
||||
@@ -1010,7 +1040,7 @@ export function loadLibraryProfile(
|
||||
return {
|
||||
ok: true,
|
||||
profile: {
|
||||
profileFormat: 1,
|
||||
profileFormat: format as 1 | 2,
|
||||
name,
|
||||
profilePath,
|
||||
entry,
|
||||
|
||||
@@ -65,6 +65,7 @@ function libClassFits(cls: string, t: IrType): boolean {
|
||||
case "bool":
|
||||
return t.kind === "bool";
|
||||
case "string":
|
||||
case "cstring":
|
||||
return t.kind === "string";
|
||||
case "bytes":
|
||||
return t.kind === "bytes" && t.elem === "u8";
|
||||
@@ -178,6 +179,18 @@ function resolveLibrarySection(
|
||||
profileRemediation(profile, LIB_INBOUND_BYTES_TRAP_CODE),
|
||||
);
|
||||
}
|
||||
if (e.params.includes("cstring")) {
|
||||
// The host-contract trap for a NULL cstring parameter: a C string
|
||||
// argument is a pointer to NUL-terminated text, never NULL. Same
|
||||
// code and assembly-once discipline as the bytes and integer traps.
|
||||
resolvedExport.inboundCstringTrap = assembleTrapTeaching(
|
||||
profileTeaching(profile, LIB_INBOUND_BYTES_TRAP_CODE) ??
|
||||
"scriptc: library inbound cstring parameter is NULL\n",
|
||||
LIB_INBOUND_BYTES_TRAP_CODE,
|
||||
e.symbol,
|
||||
profileRemediation(profile, LIB_INBOUND_BYTES_TRAP_CODE),
|
||||
);
|
||||
}
|
||||
if (e.params.includes("i64") || e.params.includes("u64")) {
|
||||
// The sibling host-contract trap for inbound declared-integer
|
||||
// parameters (ask 4): a value past ±(2^53−1) cannot ride f64
|
||||
@@ -524,10 +537,12 @@ export function prepareLibrary(
|
||||
// SC4024 diagnostics, unused channels legal, undeclared references
|
||||
// refused with the callback teaching. The library lane never loads a
|
||||
// native-FFI manifest, so the channel set owns the surface outright.
|
||||
// A cstring channel parameter is a string on the TypeScript side; the
|
||||
// channel's own classes (lib.callbacks) drive the marshalling.
|
||||
const cbImports: IrFfiImport[] = profile.callbacks.map((cb) => ({
|
||||
name: cb.name,
|
||||
symbol: cb.name,
|
||||
params: [...cb.params],
|
||||
params: cb.params.map((cls) => (cls === "cstring" ? "string" : cls)),
|
||||
returns: cb.returns,
|
||||
}));
|
||||
const integerSlotRoots: string[] = [];
|
||||
|
||||
@@ -1512,6 +1512,12 @@
|
||||
"name": "os.release",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.os.setPriority",
|
||||
"kind": "node-builtin",
|
||||
"name": "os.setPriority",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.os.tmpdir",
|
||||
"kind": "node-builtin",
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5833-url-search-params-iterators.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5834-url-search-params-iterators-dynamic.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5836-url-search-params-tuple-pairs.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5838-qs-stringify-bigint.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5882-string-decoder-runtime-encoding.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5883-os-set-priority.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5939-js-let-object-reassigned-wider.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -2462,6 +2462,22 @@ static ScrStr *scr_bytes_normalize_encoding(const ScrStr *raw) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* new StringDecoder(encoding) with an encoding known only at run time:
|
||||
* Node's normalizeEncoding. undefined, null and "" are utf8; a Buffer
|
||||
* encoding name in any ASCII case answers its canonical name (+1); any
|
||||
* other string throws ERR_UNKNOWN_ENCODING (NULL). Encodings that are not
|
||||
* strings have no lowering. */
|
||||
ScrStr *scr_strdec_encoding(const ScrDyn *enc) {
|
||||
if (enc->kind == SCR_DYN_UNDEF || enc->kind == SCR_DYN_NULL) return scr_str_new("utf8", 4);
|
||||
if (enc->kind != SCR_DYN_STR) {
|
||||
static const char message[] = "new StringDecoder with an encoding that is not a string has no lowering";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
return NULL;
|
||||
}
|
||||
if (enc->v.str->len == 0) return scr_str_new("utf8", 4);
|
||||
return scr_bytes_normalize_encoding(enc->v.str);
|
||||
}
|
||||
|
||||
ScrStr *scr_bytes_to_str_checked(const ScrBytes *b, const ScrStr *enc) {
|
||||
/* Node returns before resolving the encoding when there are no bytes
|
||||
* to decode, so even an unknown runtime name answers the empty string. */
|
||||
|
||||
@@ -119,6 +119,7 @@ double scr_bytes_to_u8_clamp(double value);
|
||||
ScrStr *scr_strdec_write(const ScrStr *enc, double pending, const ScrBytes *chunk);
|
||||
double scr_strdec_next(const ScrStr *enc, double pending, const ScrBytes *chunk);
|
||||
ScrStr *scr_strdec_end(const ScrStr *enc, double pending);
|
||||
ScrStr *scr_strdec_encoding(const ScrDyn *enc); /* +1, or NULL with ERR_UNKNOWN_ENCODING pending */
|
||||
|
||||
/* `new Uint8Array(n)` / Buffer.alloc(n): zero-filled. n goes through
|
||||
* ToIndex like JS (NaN → 0, truncate toward zero; negative or > 2^53-1
|
||||
|
||||
@@ -294,6 +294,15 @@ double scr_library_i64_in(int64_t v, const char *trap_msg);
|
||||
double scr_library_u64_in(uint64_t v, const char *trap_msg);
|
||||
void scr_library_str_out(ScrStr *s, const uint8_t **out, size_t *out_len);
|
||||
void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len);
|
||||
/* The cstring class (a NUL-terminated `const char *`). Inbound: a copy
|
||||
* decoded as UTF-8 with U+FFFD replacement; NULL delivers trap_msg (the
|
||||
* assembled SC4012 message). Outbound results move into the arena and
|
||||
* return their data; callback arguments return the string's own data,
|
||||
* borrowed for the call. A string containing U+0000 cannot be a C string:
|
||||
* both outbound forms trap instead of truncating. */
|
||||
ScrStr *scr_library_cstr_in(const char *p, const char *trap_msg); /* +1 */
|
||||
const char *scr_library_cstr_out(ScrStr *s);
|
||||
const char *scr_library_cstr_arg(const ScrStr *s);
|
||||
|
||||
#define scr_atexit(fn) scr_library_register_reset(fn)
|
||||
#elif defined(SCR_WORKERS)
|
||||
|
||||
@@ -1449,6 +1449,8 @@ ScrDyn *scr_dyn_iter_pack(const ScrDyn *src, const ScrStr *msg) {
|
||||
return scr_dyn_not_iterable(src, msg);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_native_iterator_drain(const ScrDyn *src);
|
||||
|
||||
/* The for-of-over-dyn pack accessors: the emitted index loop drives them
|
||||
* over a scr_dyn_iter_pack result (ARR by construction — the defensive
|
||||
* arms cover nothing reachable from that lowering). Never throw. */
|
||||
@@ -1463,6 +1465,13 @@ ScrDyn *scr_dyn_map_seed_entries(const ScrDyn *src) {
|
||||
scr_dyn_release(view);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
}
|
||||
/* A native iterator (sp.entries(), a Map's entries()) or a handle that
|
||||
* steps through live entries (URLSearchParams): producing its entries
|
||||
* runs no program code, so draining them first is what the consumer
|
||||
* observes. */
|
||||
if (src->kind == SCR_DYN_HANDLE &&
|
||||
(src->v.handle.tag == SCR_DYNH_ITERATOR || scr_dyn_handle_ops_of(src)->iter_step))
|
||||
return scr_native_iterator_drain(src);
|
||||
if (src->kind == SCR_DYN_HANDLE || src->kind == SCR_DYN_TYPED_REF) {
|
||||
static const char message[] = "new Map(entries) over native non-array iterables has no lowering";
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
|
||||
@@ -4576,6 +4585,33 @@ ScrDyn *scr_dyn_native_handle_iterator(ScrDyn *source, unsigned selection) {
|
||||
return scr_native_collection_iterator(source, selection);
|
||||
}
|
||||
|
||||
/* The values a native iterator (or a live-stepping handle's entries
|
||||
* iterator) produces from its current position, into a fresh array; NULL
|
||||
* with the exception pending. */
|
||||
static ScrDyn *scr_native_iterator_drain(const ScrDyn *src) {
|
||||
ScrDyn *iterator = src->v.handle.tag == SCR_DYNH_ITERATOR
|
||||
? scr_dyn_retain((ScrDyn *)src)
|
||||
: scr_dyn_native_handle_iterator((ScrDyn *)src, 2);
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
for (;;) {
|
||||
ScrDyn *step = scr_native_iterator_next(iterator->v.handle.ptr);
|
||||
if (!step) {
|
||||
scr_dyn_release(out);
|
||||
out = NULL;
|
||||
break;
|
||||
}
|
||||
bool done = scr_dyn_truthy(scr_dyn_obj_get(step, "done", 4));
|
||||
if (!done) {
|
||||
const ScrDyn *value = scr_dyn_obj_get(step, "value", 5);
|
||||
scr_dyn_arr_push(out, scr_dyn_retain((ScrDyn *)(value ? value : scr_dyn_undefined())));
|
||||
}
|
||||
scr_dyn_release(step);
|
||||
if (done) break;
|
||||
}
|
||||
scr_dyn_release(iterator);
|
||||
return out;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_builtin_iterator_checked(int kind) {
|
||||
ScrDyn *self = scr_dyn_this_get();
|
||||
ScrDyn *result = NULL;
|
||||
|
||||
@@ -371,6 +371,30 @@ void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len) {
|
||||
*out_len = b->len;
|
||||
}
|
||||
|
||||
ScrStr *scr_library_cstr_in(const char *p, const char *trap_msg) {
|
||||
if (p == NULL) scr_trap(trap_msg);
|
||||
return scr_str_from_utf8_lossy((const uint8_t *)p, strlen(p));
|
||||
}
|
||||
|
||||
/* A C string ends at its first NUL: text holding U+0000 cannot cross. */
|
||||
static void scr_library_cstr_check(const ScrStr *s) {
|
||||
if (memchr(s->data, 0, s->len) != NULL) {
|
||||
scr_trap("scriptc: TypeError: a cstring value contains U+0000, which a C string cannot "
|
||||
"hold\n");
|
||||
}
|
||||
}
|
||||
|
||||
const char *scr_library_cstr_out(ScrStr *s) {
|
||||
scr_library_arena_keep(s, true);
|
||||
scr_library_cstr_check(s);
|
||||
return s->data; /* NUL-terminated after len (ScrStr layout) */
|
||||
}
|
||||
|
||||
const char *scr_library_cstr_arg(const ScrStr *s) {
|
||||
scr_library_cstr_check(s);
|
||||
return s->data;
|
||||
}
|
||||
|
||||
/* ── the reset registry + session reset ─────────────────────────────────
|
||||
* Where an executable's always-linked units atexit() their lazy teardowns,
|
||||
* library builds register here (scr_atexit in scr_runtime.h): registered
|
||||
|
||||
@@ -296,23 +296,27 @@ static void scr_os_system_error(const char *syscall, int uv_code) {
|
||||
if (cell->kind == SCR_EXC_OBJ) scr_error_set_code((ScrError *)cell->payload, "ERR_SYSTEM_ERROR");
|
||||
}
|
||||
|
||||
static bool scr_os_validate_int32(const char *name, double value) {
|
||||
static bool scr_os_validate_int32_in(const char *name, double value, int min, int max) {
|
||||
if (isfinite(value) && trunc(value) == value) {
|
||||
if (value >= -2147483648.0 && value <= 2147483647.0) return true;
|
||||
if (value >= (double)min && value <= (double)max) return true;
|
||||
}
|
||||
char received[48];
|
||||
scr_num_received(value, received);
|
||||
char text[192];
|
||||
int n = isfinite(value) && trunc(value) == value
|
||||
? snprintf(text, sizeof text,
|
||||
"The value of \"%s\" is out of range. It must be >= -2147483648 && <= 2147483647. Received %s",
|
||||
name, received)
|
||||
"The value of \"%s\" is out of range. It must be >= %d && <= %d. Received %s",
|
||||
name, min, max, received)
|
||||
: snprintf(text, sizeof text, "The value of \"%s\" is out of range. It must be an integer. Received %s",
|
||||
name, received);
|
||||
scr_throw_error_msg_code(SCR_ERR_RANGE, text, n < 0 ? 0 : (size_t)n, "ERR_OUT_OF_RANGE");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool scr_os_validate_int32(const char *name, double value) {
|
||||
return scr_os_validate_int32_in(name, value, INT32_MIN, INT32_MAX);
|
||||
}
|
||||
|
||||
double scr_os_get_priority(double pid) {
|
||||
if (!scr_os_validate_int32("pid", pid)) return 0;
|
||||
#ifdef _WIN32
|
||||
@@ -351,6 +355,48 @@ double scr_os_get_priority(double pid) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ── setPriority ──────────────────────────────────────────────────────
|
||||
* validateInt32(pid, 'pid') and validateInt32(priority, 'priority', -20,
|
||||
* 19) (a one-argument call passes pid 0), then uv_os_setpriority; a
|
||||
* failure is Node's ERR_SYSTEM_ERROR. */
|
||||
|
||||
void scr_os_set_priority(double pid, double priority) {
|
||||
if (!scr_os_validate_int32("pid", pid)) return;
|
||||
if (!scr_os_validate_int32_in("priority", priority, SCR_OS_PRIORITY_HIGHEST, SCR_OS_PRIORITY_LOW)) return;
|
||||
int value = (int)priority;
|
||||
#ifdef _WIN32
|
||||
/* libuv's mapping of nice values to Windows priority classes. */
|
||||
DWORD klass = value < SCR_OS_PRIORITY_HIGH ? REALTIME_PRIORITY_CLASS
|
||||
: value < SCR_OS_PRIORITY_ABOVE_NORMAL ? HIGH_PRIORITY_CLASS
|
||||
: value < SCR_OS_PRIORITY_NORMAL ? ABOVE_NORMAL_PRIORITY_CLASS
|
||||
: value < SCR_OS_PRIORITY_BELOW_NORMAL ? NORMAL_PRIORITY_CLASS
|
||||
: value < SCR_OS_PRIORITY_LOW ? BELOW_NORMAL_PRIORITY_CLASS
|
||||
: IDLE_PRIORITY_CLASS;
|
||||
HANDLE handle = pid == 0 ? GetCurrentProcess()
|
||||
: OpenProcess(PROCESS_SET_INFORMATION, FALSE, (DWORD)(int)pid);
|
||||
if (handle == NULL) {
|
||||
DWORD err = GetLastError();
|
||||
scr_os_system_error("uv_os_setpriority", err == ERROR_ACCESS_DENIED ? SCR_NODE_UV__EPERM
|
||||
: err == ERROR_INVALID_PARAMETER ? SCR_NODE_UV__EINVAL
|
||||
: SCR_NODE_UV__UNKNOWN);
|
||||
return;
|
||||
}
|
||||
BOOL ok = SetPriorityClass(handle, klass);
|
||||
DWORD err = ok ? 0 : GetLastError();
|
||||
if (pid != 0) CloseHandle(handle);
|
||||
if (!ok) {
|
||||
scr_os_system_error("uv_os_setpriority", err == ERROR_ACCESS_DENIED ? SCR_NODE_UV__EPERM
|
||||
: SCR_NODE_UV__UNKNOWN);
|
||||
}
|
||||
#elif defined(__wasi__)
|
||||
(void)value;
|
||||
scr_os_system_error("uv_os_setpriority", SCR_NODE_UV__ENOSYS);
|
||||
#else
|
||||
if (setpriority(PRIO_PROCESS, (id_t)(int)pid, value) != 0)
|
||||
scr_os_system_error("uv_os_setpriority", SCR_NODE_UV__ERR(errno));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ── cpus ─────────────────────────────────────────────────────────────
|
||||
* uv_cpu_info, snapshotted once per os.cpus() call: scr_os_cpus_take
|
||||
* reads every CPU and answers the count; the accessors read that
|
||||
|
||||
@@ -27,6 +27,10 @@ ScrArr *scr_os_loadavg(void);
|
||||
/* os.getPriority(pid): validateInt32, then the scheduling priority;
|
||||
* failures throw Node's RangeError or SystemError (ERR_SYSTEM_ERROR). */
|
||||
double scr_os_get_priority(double pid);
|
||||
/* os.setPriority(pid, priority): validateInt32 for both (priority within
|
||||
* -20..19), then the scheduling priority; failures throw Node's RangeError
|
||||
* or SystemError (ERR_SYSTEM_ERROR). */
|
||||
void scr_os_set_priority(double pid, double priority);
|
||||
/* os.cpus(): take answers the CPU count of a fresh snapshot; model and
|
||||
* field (0 speed, 1 user, 2 nice, 3 sys, 4 idle, 5 irq) read it. */
|
||||
double scr_os_cpus_take(void);
|
||||
|
||||
@@ -478,8 +478,8 @@ ScrDyn *scr_qs_parse_dyn(const ScrStr *qs) {
|
||||
/* ── stringify ───────────────────────────────────────────────────────── */
|
||||
|
||||
/* Node's encodeStringified over one dyn value: strings escape, finite
|
||||
* numbers render then escape, booleans are bare, everything else is the
|
||||
* empty value. Appends to b. */
|
||||
* numbers render then escape, bigints and booleans are bare, everything
|
||||
* else is the empty value. Appends to b. */
|
||||
static void qs_stringify_value(QsBuf *b, const ScrDyn *v) {
|
||||
switch (v->kind) {
|
||||
case SCR_DYN_STR: {
|
||||
@@ -497,12 +497,18 @@ static void qs_stringify_value(QsBuf *b, const ScrDyn *v) {
|
||||
scr_str_release(num);
|
||||
return;
|
||||
}
|
||||
case SCR_DYN_BIGINT: {
|
||||
ScrStr *text = scr_bigint_to_string(v->v.bigint, 10);
|
||||
qb_append_str(b, text);
|
||||
scr_str_release(text);
|
||||
return;
|
||||
}
|
||||
case SCR_DYN_BOOL:
|
||||
if (v->v.b) qb_append(b, "true", 4);
|
||||
else qb_append(b, "false", 5);
|
||||
return;
|
||||
default:
|
||||
return; /* null/undefined/objects/arrays/functions → '' */
|
||||
return; /* null/undefined/symbols/objects/arrays/functions → '' */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1686,9 +1686,10 @@ static ScrDyn *scr_native_sp_step(void *ptr, double *index, unsigned selection)
|
||||
} else value = scr_dyn_new_str(selection == 1 ? key : text);
|
||||
scr_str_release(key); scr_str_release(text);
|
||||
}
|
||||
/* { value, done }, in the iterator result's own key order. */
|
||||
ScrDyn *result = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(result, "done", 4, scr_dyn_new_bool(done));
|
||||
scr_dyn_obj_set(result, "value", 5, value);
|
||||
scr_dyn_obj_set(result, "done", 4, scr_dyn_new_bool(done));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user