Keep the reply scanner in a constant object

A native build of a program that installs the C scanner but decodes no
reply, such as the self-hosted source parser and CommonJS checks, failed
at startup with "Cannot access 'valueEnd' before initialization": the
build left the module variable uninitialized because its only other use
was the assignment in useNativeJsonValueEnd, which checks it first.

The scanner is now a property of a constant object that the decoders
and useNativeJsonValueEnd read, so it is initialized in every program.
This commit is contained in:
Chris Tate
2026-10-24 17:03:27 -05:00
parent 5e7338ad2c
commit 3d4aeddb82
@@ -104,15 +104,20 @@ export function jsonValueEnd(bytes: Uint8Array, start: number): number {
}
/** The scanner the decoders find value ends with: jsonValueEnd, or the
* native compiler's scanner (useNativeJsonValueEnd). */
let valueEnd: (bytes: Uint8Array, start: number) => number = jsonValueEnd;
* native compiler's scanner (useNativeJsonValueEnd). It is a property of a
* constant object: a program that installs the scanner but decodes no
* reply must still initialize it, and an assigned module variable with no
* other reader would be left uninitialized by a native build. */
const scanner: { valueEnd: (bytes: Uint8Array, start: number) => number } = {
valueEnd: jsonValueEnd,
};
/** Has the decoders find value ends with `scan`, which returns what
* jsonValueEnd returns, or -1 where jsonValueEnd throws. The native compiler
* installs its C scanner (native/ts7-process.c), which skips the strings that
* make up most reply bytes with memchr. */
export function useNativeJsonValueEnd(scan: (bytes: Uint8Array, start: number) => number): void {
valueEnd = (bytes, start) => {
scanner.valueEnd = (bytes, start) => {
const end = scan(bytes, start);
if (end < 0) malformedReply();
return end;
@@ -174,7 +179,7 @@ export function decodeSemanticObject<T extends { id: number }, R>(
): R | null {
const start = skipJsonSpace(bytes, 0);
if (start === bytes.length) return null;
const end = valueEnd(bytes, start);
const end = scanner.valueEnd(bytes, start);
if (skipJsonSpace(bytes, end) !== bytes.length) malformedReply();
return replyElement(bytes, start, end, cached, build);
}
@@ -190,7 +195,7 @@ export function decodeSemanticObjects<T extends { id: number }, R>(
let index = skipJsonSpace(bytes, 0);
if (index === bytes.length) return null;
if (bytes[index] !== OPEN_BRACKET) {
const end = valueEnd(bytes, index);
const end = scanner.valueEnd(bytes, index);
if (
skipJsonSpace(bytes, end) !== bytes.length ||
parseJsonSlice<null>(bytes, index, end) !== null
@@ -205,7 +210,7 @@ export function decodeSemanticObjects<T extends { id: number }, R>(
return result;
}
for (;;) {
const end = valueEnd(bytes, index);
const end = scanner.valueEnd(bytes, index);
if (end === index) malformedReply();
result.push(replyElement(bytes, index, end, cached, build));
index = skipJsonSpace(bytes, end);