numbl 0.4.13 → 0.4.15

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.
@@ -1,2 +1,2 @@
1
1
  /** Numbl version, used for JIT disk cache invalidation. */
2
- export declare const NUMBL_VERSION = "0.4.13";
2
+ export declare const NUMBL_VERSION = "0.4.15";
@@ -0,0 +1,7 @@
1
+ export declare function extractSnippetByLine(source: string, lineNumber: number, contextLines?: number, column?: number): string | null;
2
+ export declare function formatError(message: string, errorType: "syntax" | "semantic" | "runtime" | "unknown", line: number | null, snippet: string | null): string;
3
+ /** Format any error thrown by executeCode against the code that raised it. */
4
+ export declare function formatExecuteError(error: unknown, code: string): {
5
+ message: string;
6
+ cancelled: boolean;
7
+ };
package/dist-cli/cli.js CHANGED
@@ -64277,7 +64277,7 @@ function getSourceLine(getSource, file, line) {
64277
64277
  }
64278
64278
 
64279
64279
  // src/numbl-core/version.ts
64280
- var NUMBL_VERSION = "0.4.13";
64280
+ var NUMBL_VERSION = "0.4.15";
64281
64281
 
64282
64282
  // src/cli-repl.ts
64283
64283
  import { createInterface } from "readline";
@@ -66415,6 +66415,9 @@ function execStmtInner(stmt) {
66415
66415
  const signal = this.execBlockStmts(stmt.tryBody);
66416
66416
  if (signal) return signal;
66417
66417
  } catch (e) {
66418
+ if (e instanceof CancellationError) {
66419
+ throw e;
66420
+ }
66418
66421
  if (stmt.catchVar) {
66419
66422
  this.env.set(stmt.catchVar, this.rt.wrapError(e));
66420
66423
  }
@@ -71730,6 +71733,24 @@ function mtoc2_sprintf_format(fmt, args) {
71730
71733
  case "t":
71731
71734
  result += " ";
71732
71735
  break;
71736
+ case "r":
71737
+ result += "\r";
71738
+ break;
71739
+ case "a":
71740
+ result += "\x07";
71741
+ break;
71742
+ case "b":
71743
+ result += "\b";
71744
+ break;
71745
+ case "f":
71746
+ result += "\f";
71747
+ break;
71748
+ case "v":
71749
+ result += "\v";
71750
+ break;
71751
+ case "0":
71752
+ result += "\0";
71753
+ break;
71733
71754
  case "\\":
71734
71755
  result += "\\";
71735
71756
  break;
@@ -74507,7 +74528,7 @@ var JS_SNIPPETS = {
74507
74528
  "error_fmt.js": "// JS sibling of `error_fmt.h`. Format the message + arguments and\n// throw a runtime error. Mirrors numbl's `error(...)` behavior\n// (stderr + halt).\n//\n// The thrown error carries an `identifier` string property so a\n// `try / catch ME` arm in the interpreter can populate the `ME`\n// struct's `identifier` field. When the caller passes no identifier\n// (the common `error('msg')` form), it's the empty string.\n\n\nfunction mtoc2_error_fmt(fmt, ...args) {\n const msg = mtoc2_sprintf_format(fmt, args);\n // Numbl writes to stderr; in the JS host we route through the same\n // `$write` channel by emitting a \"stderr\" sentinel prefix the host\n // can split on. For now, throw \u2014 the CLI's runner formats the\n // exception to stderr, matching the user-visible result.\n const err = new Error(msg);\n err.identifier = \"\";\n throw err;\n}\n\nfunction mtoc2_error_fmt_id(identifier, fmt, ...args) {\n const msg = mtoc2_sprintf_format(fmt, args);\n const err = new Error(msg);\n err.identifier = identifier;\n throw err;\n}\n",
74508
74529
  "format_complex.js": '// JS sibling of `format_complex.h`. Format a scalar complex `{re, im}`\n// the way numbl\'s `formatComplex` does. Each component is rendered\n// through `mtoc2_format_double` so the scalar formatting stays in\n// lockstep with numbl\'s `formatNumber`.\n\n\nfunction mtoc2_format_complex(z) {\n const re = z.re;\n const im = z.im;\n if (im === 0) return mtoc2_format_double(re);\n if (re === 0) return mtoc2_format_double(im) + "i";\n if (im < 0) {\n return mtoc2_format_double(re) + " - " + mtoc2_format_double(-im) + "i";\n }\n return mtoc2_format_double(re) + " + " + mtoc2_format_double(im) + "i";\n}\n',
74509
74530
  "format_double.js": '// JS sibling of `format_double.h` \u2014 same numbl-compatible formatter,\n// in JS. Used by `emitJs` for the `disp` scalar path and (later) by\n// the interpreter via the re-export in `snippets.gen.ts`.\n\nfunction mtoc2_format_double(x) {\n if (Number.isNaN(x)) return "NaN";\n // MATLAB spells these Inf / -Inf (matches the interpreter\'s formatNumber).\n if (x === Infinity) return "Inf";\n if (x === -Infinity) return "-Inf";\n if (x === 0) x = 0;\n if (Math.abs(x) < 1e15 && x === Math.trunc(x)) return String(x);\n\n const buf = x.toExponential(4);\n const ePos = buf.indexOf("e");\n if (ePos < 0) return buf;\n const decExp = parseInt(buf.slice(ePos + 1), 10);\n\n if (decExp < -6 || decExp >= 5) {\n let mantissaEnd = ePos - 1;\n while (mantissaEnd > 0 && buf[mantissaEnd] === "0") mantissaEnd--;\n if (mantissaEnd >= 0 && buf[mantissaEnd] === ".") mantissaEnd--;\n let expPart = buf.slice(ePos + 1);\n let sign = "+";\n if (expPart[0] === "+" || expPart[0] === "-") {\n sign = expPart[0];\n expPart = expPart.slice(1);\n }\n while (expPart.length > 1 && expPart[0] === "0") expPart = expPart.slice(1);\n return buf.slice(0, mantissaEnd + 1) + "e" + sign + expPart;\n }\n\n let fracDigits = 4 - decExp;\n if (fracDigits < 0) fracDigits = 0;\n let s = x.toFixed(fracDigits);\n if (s.includes(".")) {\n while (s.endsWith("0")) s = s.slice(0, -1);\n if (s.endsWith(".")) s = s.slice(0, -1);\n }\n return s;\n}\n',
74510
- "format_engine.js": '// JS sibling of `format_engine.h`. Numbl-compatible printf-style\n// formatter, ported from `numbl/src/numbl-core/helpers/string.ts`\'s\n// `sprintfFormat`. Shared by fprintf / sprintf / error / assert on\n// the JS side.\n//\n// Spec set: d i u f e E g G x X o s c %\n// Flags: - + 0 # space. Precision `.N`. Width digits. `*` consumes the\n// next numeric arg as a width.\n// Escapes interpreted AT FORMAT TIME: \\n \\t \\\\.\n// Numeric tensors flatten column-major into the scalar stream; the\n// format string cycles through args until consumed (with a "no arg\n// consumed this pass" guard against infinite output).\n\nfunction isTensor(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "tensor";\n}\nfunction isChar(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "char";\n}\n\nfunction toNumber(v) {\n if (typeof v === "number") return v;\n if (typeof v === "boolean") return v ? 1 : 0;\n if (typeof v === "string") return Number(v);\n if (isChar(v)) {\n // Match the interpreter\'s toNumber (runtime/convert.ts): a single\n // char yields its code point; a multi-char value has no scalar\n // numeric form. `Number("A")` would wrongly give NaN.\n if (v.value.length === 1) return v.value.charCodeAt(0);\n throw new Error("Cannot convert multi-char to number");\n }\n if (isTensor(v) && v.data.length === 1) return v.data[0];\n return Number(v);\n}\n\nfunction toString(v) {\n if (typeof v === "string") return v;\n if (typeof v === "number") return String(v);\n if (typeof v === "boolean") return v ? "1" : "0";\n if (isChar(v)) return v.value;\n return String(v);\n}\n\nfunction numStr(n) {\n if (n === Infinity) return "Inf";\n if (n === -Infinity) return "-Inf";\n if (Number.isNaN(n)) return "NaN";\n if (n === 0) return "0";\n const prec = 5;\n const exp = Math.floor(Math.log10(Math.abs(n)));\n let s;\n if (exp < -4 || exp >= prec) {\n s = n.toExponential(prec - 1);\n const ePos = s.indexOf("e");\n let mantissa = s.slice(0, ePos);\n const expPart0 = s.slice(ePos);\n if (mantissa.includes(".")) mantissa = mantissa.replace(/\\.?0+$/, "");\n const expPart = expPart0.replace(/([eE][+-])(\\d)$/, "$1" + "0$2");\n s = mantissa + expPart;\n } else {\n if (Number.isInteger(n)) return String(n);\n s = n.toPrecision(prec);\n if (s.includes(".")) s = s.replace(/\\.?0+$/, "");\n }\n return s;\n}\n\nfunction applyWidth(spec, str) {\n const m = spec.match(/^%([-+ #]*)0?(\\d+)?/);\n if (!m) return str;\n const explicitFlags = m[1] || "";\n const leftAlign = explicitFlags.includes("-");\n const afterPercent = spec.slice(1);\n const flagAndWidth = afterPercent.match(/^([-+ #]*)(0?)(\\d+)?/);\n const zeroFlag = flagAndWidth ? flagAndWidth[2] === "0" : false;\n const width = flagAndWidth && flagAndWidth[3] ? parseInt(flagAndWidth[3]) : 0;\n if (width <= str.length) return str;\n const zeroPad = !leftAlign && zeroFlag;\n const padLen = width - str.length;\n if (leftAlign) return str + " ".repeat(padLen);\n if (zeroPad) {\n if (str[0] === "-" || str[0] === "+") {\n return str[0] + "0".repeat(padLen) + str.slice(1);\n }\n return "0".repeat(padLen) + str;\n }\n return " ".repeat(padLen) + str;\n}\n\nfunction mtoc2_sprintf_format(fmt, args) {\n // Char literals (`\'foo\'`) come through wrapped as\n // `{mtoc2Tag:"char", value:"..."}` to match the interpreter\'s\n // RuntimeValue convention; unwrap so the format walker indexes\n // a plain JS string.\n if (isChar(fmt)) fmt = fmt.value;\n // Flatten args column-major (tensors expand element-by-element).\n const flat = [];\n for (const arg of args) {\n if (isTensor(arg)) {\n for (let k = 0; k < arg.data.length; k++) flat.push(arg.data[k]);\n } else {\n flat.push(arg);\n }\n }\n\n let result = "";\n let argIdx = 0;\n\n do {\n const startArgIdx = argIdx;\n let outOfArgs = false;\n let i = 0;\n while (i < fmt.length && !outOfArgs) {\n if (fmt[i] === "%" && i + 1 < fmt.length) {\n i++;\n let spec = "%";\n while (i < fmt.length && !"dfigeEsoxXuc%".includes(fmt[i])) {\n if (fmt[i] === "*") {\n if (argIdx >= flat.length) {\n outOfArgs = true;\n break;\n }\n spec += String(Math.round(toNumber(flat[argIdx++])));\n i++;\n } else {\n spec += fmt[i];\n i++;\n }\n }\n if (outOfArgs) break;\n if (i < fmt.length) {\n const ch = fmt[i];\n i++;\n if (ch === "%") {\n result += "%";\n } else if (argIdx >= flat.length) {\n outOfArgs = true;\n } else if (ch === "d" || ch === "i" || ch === "u") {\n const raw = toNumber(flat[argIdx++]);\n const isInt = Number.isInteger(raw);\n const canPrintAsInt = ch === "u" ? isInt && raw >= 0 : isInt;\n if (!canPrintAsInt) {\n let eStr = raw.toExponential(6);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n result += applyWidth(spec, eStr);\n } else {\n const n = raw;\n const flags = spec.slice(1);\n const hasPlus = flags.includes("+");\n const leftAlign = flags.includes("-");\n const widthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const width = widthMatch ? parseInt(widthMatch[1]) : 0;\n const zeroPad = !leftAlign && /^[-+ ]*0/.test(spec.slice(1));\n const s = String(Math.abs(n));\n const sign = n < 0 ? "-" : hasPlus ? "+" : "";\n if (width > 0) {\n const padChar = zeroPad ? "0" : " ";\n const padLen = Math.max(0, width - sign.length - s.length);\n const pad = padChar.repeat(padLen);\n result += leftAlign\n ? sign + s + " ".repeat(padLen)\n : zeroPad\n ? sign + pad + s\n : pad + sign + s;\n } else {\n result += sign + s;\n }\n }\n } else if (ch === "f") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const fFlags = spec.slice(1);\n const fHasPlus = fFlags.includes("+");\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n const formatted = n.toFixed(prec);\n const fSign = n < 0 ? "" : fHasPlus ? "+" : "";\n result += applyWidth(spec, fSign + formatted);\n }\n } else if (ch === "e" || ch === "E") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n let eStr = n.toExponential(prec);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n if (ch === "E") eStr = eStr.toUpperCase();\n result += applyWidth(spec, eStr);\n }\n } else if (ch === "x" || ch === "X") {\n const n = Math.round(toNumber(flat[argIdx++]));\n let s = Math.abs(n).toString(16);\n if (ch === "X") s = s.toUpperCase();\n result += applyWidth(spec, s);\n } else if (ch === "o") {\n const n = Math.round(toNumber(flat[argIdx++]));\n result += applyWidth(spec, Math.abs(n).toString(8));\n } else if (ch === "g" || ch === "G") {\n const gVal = toNumber(flat[argIdx++]);\n if (!isFinite(gVal) || isNaN(gVal)) {\n result += applyWidth(spec, numStr(gVal));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const gPrec = precMatch ? parseInt(precMatch[1]) : 6;\n let gStr;\n if (gVal === 0) {\n gStr = "0";\n } else {\n const exp = Math.floor(Math.log10(Math.abs(gVal)));\n if (exp < -4 || exp >= gPrec) {\n gStr = gVal.toExponential(gPrec - 1);\n const ePos = gStr.indexOf("e");\n let mantissa = gStr.slice(0, ePos);\n let expPart = gStr.slice(ePos);\n if (mantissa.includes(".")) {\n mantissa = mantissa.replace(/\\.?0+$/, "");\n }\n expPart = expPart.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n gStr = mantissa + expPart;\n } else {\n gStr = gVal.toPrecision(gPrec);\n if (gStr.includes(".")) {\n gStr = gStr.replace(/\\.?0+$/, "");\n }\n if (gStr.includes("e")) {\n gStr = String(parseFloat(gStr));\n }\n }\n }\n if (ch === "G") gStr = gStr.toUpperCase();\n result += applyWidth(spec, gStr);\n }\n } else if (ch === "s") {\n const sArg = flat[argIdx++];\n // A numeric/logical argument to %s is a character code (MATLAB:\n // sprintf(\'%s\', 65) -> \'A\').\n const sVal =\n typeof sArg === "number" || typeof sArg === "boolean"\n ? String.fromCharCode(Math.round(toNumber(sArg)))\n : toString(sArg);\n const sFlags = spec.slice(1);\n const sLeftAlign = sFlags.includes("-");\n const sWidthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const sWidth = sWidthMatch ? parseInt(sWidthMatch[1]) : 0;\n if (sWidth > sVal.length) {\n const sPad = " ".repeat(sWidth - sVal.length);\n result += sLeftAlign ? sVal + sPad : sPad + sVal;\n } else {\n result += sVal;\n }\n } else if (ch === "c") {\n result += String.fromCharCode(Math.round(toNumber(flat[argIdx++])));\n } else {\n result += spec + ch;\n argIdx++;\n }\n }\n } else if (fmt[i] === "\\\\" && i + 1 < fmt.length) {\n i++;\n switch (fmt[i]) {\n case "n":\n result += "\\n";\n break;\n case "t":\n result += "\\t";\n break;\n case "\\\\":\n result += "\\\\";\n break;\n default:\n result += "\\\\" + fmt[i];\n }\n i++;\n } else {\n result += fmt[i];\n i++;\n }\n }\n if (argIdx === startArgIdx) break;\n } while (argIdx < flat.length);\n\n return result;\n}\n',
74531
+ "format_engine.js": '// JS sibling of `format_engine.h`. Numbl-compatible printf-style\n// formatter, ported from `numbl/src/numbl-core/helpers/string.ts`\'s\n// `sprintfFormat`. Shared by fprintf / sprintf / error / assert on\n// the JS side.\n//\n// Spec set: d i u f e E g G x X o s c %\n// Flags: - + 0 # space. Precision `.N`. Width digits. `*` consumes the\n// next numeric arg as a width.\n// Escapes interpreted AT FORMAT TIME: \\n \\t \\r \\a \\b \\f \\v \\0 \\\\.\n// Numeric tensors flatten column-major into the scalar stream; the\n// format string cycles through args until consumed (with a "no arg\n// consumed this pass" guard against infinite output).\n\nfunction isTensor(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "tensor";\n}\nfunction isChar(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "char";\n}\n\nfunction toNumber(v) {\n if (typeof v === "number") return v;\n if (typeof v === "boolean") return v ? 1 : 0;\n if (typeof v === "string") return Number(v);\n if (isChar(v)) {\n // Match the interpreter\'s toNumber (runtime/convert.ts): a single\n // char yields its code point; a multi-char value has no scalar\n // numeric form. `Number("A")` would wrongly give NaN.\n if (v.value.length === 1) return v.value.charCodeAt(0);\n throw new Error("Cannot convert multi-char to number");\n }\n if (isTensor(v) && v.data.length === 1) return v.data[0];\n return Number(v);\n}\n\nfunction toString(v) {\n if (typeof v === "string") return v;\n if (typeof v === "number") return String(v);\n if (typeof v === "boolean") return v ? "1" : "0";\n if (isChar(v)) return v.value;\n return String(v);\n}\n\nfunction numStr(n) {\n if (n === Infinity) return "Inf";\n if (n === -Infinity) return "-Inf";\n if (Number.isNaN(n)) return "NaN";\n if (n === 0) return "0";\n const prec = 5;\n const exp = Math.floor(Math.log10(Math.abs(n)));\n let s;\n if (exp < -4 || exp >= prec) {\n s = n.toExponential(prec - 1);\n const ePos = s.indexOf("e");\n let mantissa = s.slice(0, ePos);\n const expPart0 = s.slice(ePos);\n if (mantissa.includes(".")) mantissa = mantissa.replace(/\\.?0+$/, "");\n const expPart = expPart0.replace(/([eE][+-])(\\d)$/, "$1" + "0$2");\n s = mantissa + expPart;\n } else {\n if (Number.isInteger(n)) return String(n);\n s = n.toPrecision(prec);\n if (s.includes(".")) s = s.replace(/\\.?0+$/, "");\n }\n return s;\n}\n\nfunction applyWidth(spec, str) {\n const m = spec.match(/^%([-+ #]*)0?(\\d+)?/);\n if (!m) return str;\n const explicitFlags = m[1] || "";\n const leftAlign = explicitFlags.includes("-");\n const afterPercent = spec.slice(1);\n const flagAndWidth = afterPercent.match(/^([-+ #]*)(0?)(\\d+)?/);\n const zeroFlag = flagAndWidth ? flagAndWidth[2] === "0" : false;\n const width = flagAndWidth && flagAndWidth[3] ? parseInt(flagAndWidth[3]) : 0;\n if (width <= str.length) return str;\n const zeroPad = !leftAlign && zeroFlag;\n const padLen = width - str.length;\n if (leftAlign) return str + " ".repeat(padLen);\n if (zeroPad) {\n if (str[0] === "-" || str[0] === "+") {\n return str[0] + "0".repeat(padLen) + str.slice(1);\n }\n return "0".repeat(padLen) + str;\n }\n return " ".repeat(padLen) + str;\n}\n\nfunction mtoc2_sprintf_format(fmt, args) {\n // Char literals (`\'foo\'`) come through wrapped as\n // `{mtoc2Tag:"char", value:"..."}` to match the interpreter\'s\n // RuntimeValue convention; unwrap so the format walker indexes\n // a plain JS string.\n if (isChar(fmt)) fmt = fmt.value;\n // Flatten args column-major (tensors expand element-by-element).\n const flat = [];\n for (const arg of args) {\n if (isTensor(arg)) {\n for (let k = 0; k < arg.data.length; k++) flat.push(arg.data[k]);\n } else {\n flat.push(arg);\n }\n }\n\n let result = "";\n let argIdx = 0;\n\n do {\n const startArgIdx = argIdx;\n let outOfArgs = false;\n let i = 0;\n while (i < fmt.length && !outOfArgs) {\n if (fmt[i] === "%" && i + 1 < fmt.length) {\n i++;\n let spec = "%";\n while (i < fmt.length && !"dfigeEsoxXuc%".includes(fmt[i])) {\n if (fmt[i] === "*") {\n if (argIdx >= flat.length) {\n outOfArgs = true;\n break;\n }\n spec += String(Math.round(toNumber(flat[argIdx++])));\n i++;\n } else {\n spec += fmt[i];\n i++;\n }\n }\n if (outOfArgs) break;\n if (i < fmt.length) {\n const ch = fmt[i];\n i++;\n if (ch === "%") {\n result += "%";\n } else if (argIdx >= flat.length) {\n outOfArgs = true;\n } else if (ch === "d" || ch === "i" || ch === "u") {\n const raw = toNumber(flat[argIdx++]);\n const isInt = Number.isInteger(raw);\n const canPrintAsInt = ch === "u" ? isInt && raw >= 0 : isInt;\n if (!canPrintAsInt) {\n let eStr = raw.toExponential(6);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n result += applyWidth(spec, eStr);\n } else {\n const n = raw;\n const flags = spec.slice(1);\n const hasPlus = flags.includes("+");\n const leftAlign = flags.includes("-");\n const widthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const width = widthMatch ? parseInt(widthMatch[1]) : 0;\n const zeroPad = !leftAlign && /^[-+ ]*0/.test(spec.slice(1));\n const s = String(Math.abs(n));\n const sign = n < 0 ? "-" : hasPlus ? "+" : "";\n if (width > 0) {\n const padChar = zeroPad ? "0" : " ";\n const padLen = Math.max(0, width - sign.length - s.length);\n const pad = padChar.repeat(padLen);\n result += leftAlign\n ? sign + s + " ".repeat(padLen)\n : zeroPad\n ? sign + pad + s\n : pad + sign + s;\n } else {\n result += sign + s;\n }\n }\n } else if (ch === "f") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const fFlags = spec.slice(1);\n const fHasPlus = fFlags.includes("+");\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n const formatted = n.toFixed(prec);\n const fSign = n < 0 ? "" : fHasPlus ? "+" : "";\n result += applyWidth(spec, fSign + formatted);\n }\n } else if (ch === "e" || ch === "E") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n let eStr = n.toExponential(prec);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n if (ch === "E") eStr = eStr.toUpperCase();\n result += applyWidth(spec, eStr);\n }\n } else if (ch === "x" || ch === "X") {\n const n = Math.round(toNumber(flat[argIdx++]));\n let s = Math.abs(n).toString(16);\n if (ch === "X") s = s.toUpperCase();\n result += applyWidth(spec, s);\n } else if (ch === "o") {\n const n = Math.round(toNumber(flat[argIdx++]));\n result += applyWidth(spec, Math.abs(n).toString(8));\n } else if (ch === "g" || ch === "G") {\n const gVal = toNumber(flat[argIdx++]);\n if (!isFinite(gVal) || isNaN(gVal)) {\n result += applyWidth(spec, numStr(gVal));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const gPrec = precMatch ? parseInt(precMatch[1]) : 6;\n let gStr;\n if (gVal === 0) {\n gStr = "0";\n } else {\n const exp = Math.floor(Math.log10(Math.abs(gVal)));\n if (exp < -4 || exp >= gPrec) {\n gStr = gVal.toExponential(gPrec - 1);\n const ePos = gStr.indexOf("e");\n let mantissa = gStr.slice(0, ePos);\n let expPart = gStr.slice(ePos);\n if (mantissa.includes(".")) {\n mantissa = mantissa.replace(/\\.?0+$/, "");\n }\n expPart = expPart.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n gStr = mantissa + expPart;\n } else {\n gStr = gVal.toPrecision(gPrec);\n if (gStr.includes(".")) {\n gStr = gStr.replace(/\\.?0+$/, "");\n }\n if (gStr.includes("e")) {\n gStr = String(parseFloat(gStr));\n }\n }\n }\n if (ch === "G") gStr = gStr.toUpperCase();\n result += applyWidth(spec, gStr);\n }\n } else if (ch === "s") {\n const sArg = flat[argIdx++];\n // A numeric/logical argument to %s is a character code (MATLAB:\n // sprintf(\'%s\', 65) -> \'A\').\n const sVal =\n typeof sArg === "number" || typeof sArg === "boolean"\n ? String.fromCharCode(Math.round(toNumber(sArg)))\n : toString(sArg);\n const sFlags = spec.slice(1);\n const sLeftAlign = sFlags.includes("-");\n const sWidthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const sWidth = sWidthMatch ? parseInt(sWidthMatch[1]) : 0;\n if (sWidth > sVal.length) {\n const sPad = " ".repeat(sWidth - sVal.length);\n result += sLeftAlign ? sVal + sPad : sPad + sVal;\n } else {\n result += sVal;\n }\n } else if (ch === "c") {\n result += String.fromCharCode(Math.round(toNumber(flat[argIdx++])));\n } else {\n result += spec + ch;\n argIdx++;\n }\n }\n } else if (fmt[i] === "\\\\" && i + 1 < fmt.length) {\n i++;\n switch (fmt[i]) {\n case "n":\n result += "\\n";\n break;\n case "t":\n result += "\\t";\n break;\n case "r":\n result += "\\r";\n break;\n case "a":\n result += "\\x07";\n break;\n case "b":\n result += "\\b";\n break;\n case "f":\n result += "\\f";\n break;\n case "v":\n result += "\\v";\n break;\n case "0":\n result += "\\0";\n break;\n case "\\\\":\n result += "\\\\";\n break;\n default:\n result += "\\\\" + fmt[i];\n }\n i++;\n } else {\n result += fmt[i];\n i++;\n }\n }\n if (argIdx === startArgIdx) break;\n } while (argIdx < flat.length);\n\n return result;\n}\n',
74511
74532
  "fprintf.js": "// JS sibling of `fprintf.h`. Routes formatted output through\n// `mtoc2_sprintf_format` then writes via `$write` (the JS analog of\n// C's stdout). Numbl-compatible spec set & semantics.\n\n\nfunction mtoc2_fprintf(fmt, ...args) {\n $write(mtoc2_sprintf_format(fmt, args));\n}\n",
74512
74533
  "sprintf.js": '// JS sibling of `sprintf.h`. Two output variants:\n// - `mtoc2_sprintf_str(fmt, args)` returns the formatted text as a\n// plain JS string (the equivalent of numbl\'s `String` kind).\n// - `mtoc2_sprintf_char(fmt, args)` returns it as a `mtoc2_char_tensor_t`\n// placeholder \u2014 we keep the JS shape as a `{mtoc2Tag: "char", value}`\n// object so disp/length etc. dispatch correctly.\n\n\nfunction mtoc2_sprintf_str(fmt, ...args) {\n return mtoc2_sprintf_format(fmt, args);\n}\n\nfunction mtoc2_sprintf_char(fmt, ...args) {\n return { mtoc2Tag: "char", value: mtoc2_sprintf_format(fmt, args) };\n}\n',
74513
74534
  "warning_fmt.js": '// JS sibling of `warning_fmt.h`. Format the message and write\n// `Warning: <msg>\\n` via `$write`. Matches numbl\n// (`runtime/specialBuiltins.ts:190` \u2014 `rt.output("Warning: " + msg + "\\n")`).\n\n\nfunction mtoc2_warning_fmt(fmt, ...args) {\n $write("Warning: " + mtoc2_sprintf_format(fmt, args) + "\\n");\n}\n\nfunction mtoc2_warning_fmt_id(_id, fmt, ...args) {\n // The id is informational \u2014 numbl threads it to lastwarn state,\n // which mtoc2 doesn\'t expose. Drop it here so the visible output\n // matches the bare form.\n $write("Warning: " + mtoc2_sprintf_format(fmt, args) + "\\n");\n}\n',
@@ -97175,6 +97196,9 @@ ${sections.join("\n\n")}` : `// No ${label} generated`;
97175
97196
  result.implicitCwdPath = implicitCwdPath;
97176
97197
  return result;
97177
97198
  } catch (e) {
97199
+ if (e instanceof CancellationError) {
97200
+ throw e;
97201
+ }
97178
97202
  const generatedJS = buildDump(jsSections, "JS");
97179
97203
  const generatedC = buildDump(cSections, "C");
97180
97204
  if (e instanceof RuntimeError) {
@@ -98617,8 +98641,9 @@ static int mtoc2_format_double(char *out, size_t cap, double x) {
98617
98641
  * - spec set: d i u f e E g G x X o s c %
98618
98642
  * - flags - + 0 # space; precision \`.N\`; width digits; \`*\` consumes
98619
98643
  * the next numeric arg as a width
98620
- * - escapes \\n \\t \\\\ interpreted AT FORMAT TIME (matching the numbl
98621
- * lexer, which preserves backslash bytes verbatim)
98644
+ * - escapes \\n \\t \\r \\a \\b \\f \\v \\0 \\\\ interpreted AT FORMAT TIME
98645
+ * (matching the numbl lexer, which preserves backslash bytes
98646
+ * verbatim)
98622
98647
  * - numeric tensors flatten column-major into the scalar stream;
98623
98648
  * format string cycles through the args until the stream is
98624
98649
  * exhausted (with a "no arg consumed this pass" guard against
@@ -98927,13 +98952,19 @@ static long mtoc2__num_to_str(char *buf, size_t cap, double x) {
98927
98952
  return n;
98928
98953
  }
98929
98954
 
98930
- /* Emit one \`\\\` escape sequence \u2014 numbl interprets only \\n, \\t, \\\\
98931
- * at format time; any other \`\\x\` passes through verbatim. */
98955
+ /* Emit one \`\\\` escape sequence \u2014 numbl interprets \\n, \\t, \\r, \\a, \\b,
98956
+ * \\f, \\v, \\0, \\\\ at format time; any other \`\\x\` passes through verbatim. */
98932
98957
  static long mtoc2__emit_escape(mtoc2__writer_fn writer, void *ctx,
98933
98958
  char next) {
98934
98959
  switch (next) {
98935
98960
  case 'n': writer(ctx, "\\n", 1); return 1;
98936
98961
  case 't': writer(ctx, "\\t", 1); return 1;
98962
+ case 'r': writer(ctx, "\\r", 1); return 1;
98963
+ case 'a': writer(ctx, "\\a", 1); return 1;
98964
+ case 'b': writer(ctx, "\\b", 1); return 1;
98965
+ case 'f': writer(ctx, "\\f", 1); return 1;
98966
+ case 'v': writer(ctx, "\\v", 1); return 1;
98967
+ case '0': writer(ctx, "\\0", 1); return 1;
98937
98968
  case '\\\\': writer(ctx, "\\\\", 1); return 1;
98938
98969
  default: {
98939
98970
  char buf[2] = { '\\\\', next };
@@ -11,6 +11,7 @@ export { FigureView } from "./graphics/FigureView.js";
11
11
  export { figuresReducer, initialFiguresState, } from "./graphics/figuresReducer.js";
12
12
  export type { FigureState, AxesState, FiguresState, FiguresAction, } from "./graphics/figuresReducer.js";
13
13
  export type * from "./graphics/types.js";
14
+ export { restoreNaNs } from "./graphics/restoreNaNs.js";
14
15
  export { exportFigureHdf5, downloadFigureHdf5, } from "./graphics/exportFigureHdf5.js";
15
16
  export { importFigureHdf5 } from "./graphics/importFigureHdf5.js";
16
17
  export { FIGURE_HDF5_VERSION } from "./graphics/figureHdf5Schema.js";
@@ -33866,6 +33866,38 @@ var figuresReducer = (state, action) => {
33866
33866
  }
33867
33867
  };
33868
33868
 
33869
+ // src/graphics/restoreNaNs.ts
33870
+ function restoreNullsToNaN(arr) {
33871
+ for (let i = 0; i < arr.length; i++) {
33872
+ if (arr[i] === null) arr[i] = NaN;
33873
+ }
33874
+ }
33875
+ function restoreNaNs(instr) {
33876
+ if (instr.type === "plot") {
33877
+ for (const t of instr.traces) {
33878
+ restoreNullsToNaN(t.x);
33879
+ restoreNullsToNaN(t.y);
33880
+ }
33881
+ } else if (instr.type === "plot3") {
33882
+ for (const t of instr.traces) {
33883
+ restoreNullsToNaN(t.x);
33884
+ restoreNullsToNaN(t.y);
33885
+ restoreNullsToNaN(t.z);
33886
+ }
33887
+ } else if (instr.type === "surf" || instr.type === "mesh") {
33888
+ restoreNullsToNaN(instr.trace.x);
33889
+ restoreNullsToNaN(instr.trace.y);
33890
+ restoreNullsToNaN(instr.trace.z);
33891
+ if (instr.trace.c) restoreNullsToNaN(instr.trace.c);
33892
+ } else if (instr.type === "imagesc") {
33893
+ restoreNullsToNaN(instr.trace.z);
33894
+ } else if (instr.type === "contour") {
33895
+ restoreNullsToNaN(instr.trace.x);
33896
+ restoreNullsToNaN(instr.trace.y);
33897
+ restoreNullsToNaN(instr.trace.z);
33898
+ }
33899
+ }
33900
+
33869
33901
  // src/graphics/figureHdf5Schema.ts
33870
33902
  var FIGURE_HDF5_VERSION = 1;
33871
33903
  var GRID_FIELDS = {
@@ -35319,6 +35351,7 @@ export {
35319
35351
  initialFiguresState,
35320
35352
  loadFigureFromHash,
35321
35353
  numblStoreUploader,
35354
+ restoreNaNs,
35322
35355
  setFigureUploader,
35323
35356
  uploadFigureForViewer
35324
35357
  };
package/dist-lib/lib.js CHANGED
@@ -65601,6 +65601,9 @@ function execStmtInner(stmt) {
65601
65601
  const signal = this.execBlockStmts(stmt.tryBody);
65602
65602
  if (signal) return signal;
65603
65603
  } catch (e) {
65604
+ if (e instanceof CancellationError) {
65605
+ throw e;
65606
+ }
65604
65607
  if (stmt.catchVar) {
65605
65608
  this.env.set(stmt.catchVar, this.rt.wrapError(e));
65606
65609
  }
@@ -70916,6 +70919,24 @@ function mtoc2_sprintf_format(fmt, args) {
70916
70919
  case "t":
70917
70920
  result += " ";
70918
70921
  break;
70922
+ case "r":
70923
+ result += "\r";
70924
+ break;
70925
+ case "a":
70926
+ result += "\x07";
70927
+ break;
70928
+ case "b":
70929
+ result += "\b";
70930
+ break;
70931
+ case "f":
70932
+ result += "\f";
70933
+ break;
70934
+ case "v":
70935
+ result += "\v";
70936
+ break;
70937
+ case "0":
70938
+ result += "\0";
70939
+ break;
70919
70940
  case "\\":
70920
70941
  result += "\\";
70921
70942
  break;
@@ -73693,7 +73714,7 @@ var JS_SNIPPETS = {
73693
73714
  "error_fmt.js": "// JS sibling of `error_fmt.h`. Format the message + arguments and\n// throw a runtime error. Mirrors numbl's `error(...)` behavior\n// (stderr + halt).\n//\n// The thrown error carries an `identifier` string property so a\n// `try / catch ME` arm in the interpreter can populate the `ME`\n// struct's `identifier` field. When the caller passes no identifier\n// (the common `error('msg')` form), it's the empty string.\n\n\nfunction mtoc2_error_fmt(fmt, ...args) {\n const msg = mtoc2_sprintf_format(fmt, args);\n // Numbl writes to stderr; in the JS host we route through the same\n // `$write` channel by emitting a \"stderr\" sentinel prefix the host\n // can split on. For now, throw \u2014 the CLI's runner formats the\n // exception to stderr, matching the user-visible result.\n const err = new Error(msg);\n err.identifier = \"\";\n throw err;\n}\n\nfunction mtoc2_error_fmt_id(identifier, fmt, ...args) {\n const msg = mtoc2_sprintf_format(fmt, args);\n const err = new Error(msg);\n err.identifier = identifier;\n throw err;\n}\n",
73694
73715
  "format_complex.js": '// JS sibling of `format_complex.h`. Format a scalar complex `{re, im}`\n// the way numbl\'s `formatComplex` does. Each component is rendered\n// through `mtoc2_format_double` so the scalar formatting stays in\n// lockstep with numbl\'s `formatNumber`.\n\n\nfunction mtoc2_format_complex(z) {\n const re = z.re;\n const im = z.im;\n if (im === 0) return mtoc2_format_double(re);\n if (re === 0) return mtoc2_format_double(im) + "i";\n if (im < 0) {\n return mtoc2_format_double(re) + " - " + mtoc2_format_double(-im) + "i";\n }\n return mtoc2_format_double(re) + " + " + mtoc2_format_double(im) + "i";\n}\n',
73695
73716
  "format_double.js": '// JS sibling of `format_double.h` \u2014 same numbl-compatible formatter,\n// in JS. Used by `emitJs` for the `disp` scalar path and (later) by\n// the interpreter via the re-export in `snippets.gen.ts`.\n\nfunction mtoc2_format_double(x) {\n if (Number.isNaN(x)) return "NaN";\n // MATLAB spells these Inf / -Inf (matches the interpreter\'s formatNumber).\n if (x === Infinity) return "Inf";\n if (x === -Infinity) return "-Inf";\n if (x === 0) x = 0;\n if (Math.abs(x) < 1e15 && x === Math.trunc(x)) return String(x);\n\n const buf = x.toExponential(4);\n const ePos = buf.indexOf("e");\n if (ePos < 0) return buf;\n const decExp = parseInt(buf.slice(ePos + 1), 10);\n\n if (decExp < -6 || decExp >= 5) {\n let mantissaEnd = ePos - 1;\n while (mantissaEnd > 0 && buf[mantissaEnd] === "0") mantissaEnd--;\n if (mantissaEnd >= 0 && buf[mantissaEnd] === ".") mantissaEnd--;\n let expPart = buf.slice(ePos + 1);\n let sign = "+";\n if (expPart[0] === "+" || expPart[0] === "-") {\n sign = expPart[0];\n expPart = expPart.slice(1);\n }\n while (expPart.length > 1 && expPart[0] === "0") expPart = expPart.slice(1);\n return buf.slice(0, mantissaEnd + 1) + "e" + sign + expPart;\n }\n\n let fracDigits = 4 - decExp;\n if (fracDigits < 0) fracDigits = 0;\n let s = x.toFixed(fracDigits);\n if (s.includes(".")) {\n while (s.endsWith("0")) s = s.slice(0, -1);\n if (s.endsWith(".")) s = s.slice(0, -1);\n }\n return s;\n}\n',
73696
- "format_engine.js": '// JS sibling of `format_engine.h`. Numbl-compatible printf-style\n// formatter, ported from `numbl/src/numbl-core/helpers/string.ts`\'s\n// `sprintfFormat`. Shared by fprintf / sprintf / error / assert on\n// the JS side.\n//\n// Spec set: d i u f e E g G x X o s c %\n// Flags: - + 0 # space. Precision `.N`. Width digits. `*` consumes the\n// next numeric arg as a width.\n// Escapes interpreted AT FORMAT TIME: \\n \\t \\\\.\n// Numeric tensors flatten column-major into the scalar stream; the\n// format string cycles through args until consumed (with a "no arg\n// consumed this pass" guard against infinite output).\n\nfunction isTensor(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "tensor";\n}\nfunction isChar(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "char";\n}\n\nfunction toNumber(v) {\n if (typeof v === "number") return v;\n if (typeof v === "boolean") return v ? 1 : 0;\n if (typeof v === "string") return Number(v);\n if (isChar(v)) {\n // Match the interpreter\'s toNumber (runtime/convert.ts): a single\n // char yields its code point; a multi-char value has no scalar\n // numeric form. `Number("A")` would wrongly give NaN.\n if (v.value.length === 1) return v.value.charCodeAt(0);\n throw new Error("Cannot convert multi-char to number");\n }\n if (isTensor(v) && v.data.length === 1) return v.data[0];\n return Number(v);\n}\n\nfunction toString(v) {\n if (typeof v === "string") return v;\n if (typeof v === "number") return String(v);\n if (typeof v === "boolean") return v ? "1" : "0";\n if (isChar(v)) return v.value;\n return String(v);\n}\n\nfunction numStr(n) {\n if (n === Infinity) return "Inf";\n if (n === -Infinity) return "-Inf";\n if (Number.isNaN(n)) return "NaN";\n if (n === 0) return "0";\n const prec = 5;\n const exp = Math.floor(Math.log10(Math.abs(n)));\n let s;\n if (exp < -4 || exp >= prec) {\n s = n.toExponential(prec - 1);\n const ePos = s.indexOf("e");\n let mantissa = s.slice(0, ePos);\n const expPart0 = s.slice(ePos);\n if (mantissa.includes(".")) mantissa = mantissa.replace(/\\.?0+$/, "");\n const expPart = expPart0.replace(/([eE][+-])(\\d)$/, "$1" + "0$2");\n s = mantissa + expPart;\n } else {\n if (Number.isInteger(n)) return String(n);\n s = n.toPrecision(prec);\n if (s.includes(".")) s = s.replace(/\\.?0+$/, "");\n }\n return s;\n}\n\nfunction applyWidth(spec, str) {\n const m = spec.match(/^%([-+ #]*)0?(\\d+)?/);\n if (!m) return str;\n const explicitFlags = m[1] || "";\n const leftAlign = explicitFlags.includes("-");\n const afterPercent = spec.slice(1);\n const flagAndWidth = afterPercent.match(/^([-+ #]*)(0?)(\\d+)?/);\n const zeroFlag = flagAndWidth ? flagAndWidth[2] === "0" : false;\n const width = flagAndWidth && flagAndWidth[3] ? parseInt(flagAndWidth[3]) : 0;\n if (width <= str.length) return str;\n const zeroPad = !leftAlign && zeroFlag;\n const padLen = width - str.length;\n if (leftAlign) return str + " ".repeat(padLen);\n if (zeroPad) {\n if (str[0] === "-" || str[0] === "+") {\n return str[0] + "0".repeat(padLen) + str.slice(1);\n }\n return "0".repeat(padLen) + str;\n }\n return " ".repeat(padLen) + str;\n}\n\nfunction mtoc2_sprintf_format(fmt, args) {\n // Char literals (`\'foo\'`) come through wrapped as\n // `{mtoc2Tag:"char", value:"..."}` to match the interpreter\'s\n // RuntimeValue convention; unwrap so the format walker indexes\n // a plain JS string.\n if (isChar(fmt)) fmt = fmt.value;\n // Flatten args column-major (tensors expand element-by-element).\n const flat = [];\n for (const arg of args) {\n if (isTensor(arg)) {\n for (let k = 0; k < arg.data.length; k++) flat.push(arg.data[k]);\n } else {\n flat.push(arg);\n }\n }\n\n let result = "";\n let argIdx = 0;\n\n do {\n const startArgIdx = argIdx;\n let outOfArgs = false;\n let i = 0;\n while (i < fmt.length && !outOfArgs) {\n if (fmt[i] === "%" && i + 1 < fmt.length) {\n i++;\n let spec = "%";\n while (i < fmt.length && !"dfigeEsoxXuc%".includes(fmt[i])) {\n if (fmt[i] === "*") {\n if (argIdx >= flat.length) {\n outOfArgs = true;\n break;\n }\n spec += String(Math.round(toNumber(flat[argIdx++])));\n i++;\n } else {\n spec += fmt[i];\n i++;\n }\n }\n if (outOfArgs) break;\n if (i < fmt.length) {\n const ch = fmt[i];\n i++;\n if (ch === "%") {\n result += "%";\n } else if (argIdx >= flat.length) {\n outOfArgs = true;\n } else if (ch === "d" || ch === "i" || ch === "u") {\n const raw = toNumber(flat[argIdx++]);\n const isInt = Number.isInteger(raw);\n const canPrintAsInt = ch === "u" ? isInt && raw >= 0 : isInt;\n if (!canPrintAsInt) {\n let eStr = raw.toExponential(6);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n result += applyWidth(spec, eStr);\n } else {\n const n = raw;\n const flags = spec.slice(1);\n const hasPlus = flags.includes("+");\n const leftAlign = flags.includes("-");\n const widthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const width = widthMatch ? parseInt(widthMatch[1]) : 0;\n const zeroPad = !leftAlign && /^[-+ ]*0/.test(spec.slice(1));\n const s = String(Math.abs(n));\n const sign = n < 0 ? "-" : hasPlus ? "+" : "";\n if (width > 0) {\n const padChar = zeroPad ? "0" : " ";\n const padLen = Math.max(0, width - sign.length - s.length);\n const pad = padChar.repeat(padLen);\n result += leftAlign\n ? sign + s + " ".repeat(padLen)\n : zeroPad\n ? sign + pad + s\n : pad + sign + s;\n } else {\n result += sign + s;\n }\n }\n } else if (ch === "f") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const fFlags = spec.slice(1);\n const fHasPlus = fFlags.includes("+");\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n const formatted = n.toFixed(prec);\n const fSign = n < 0 ? "" : fHasPlus ? "+" : "";\n result += applyWidth(spec, fSign + formatted);\n }\n } else if (ch === "e" || ch === "E") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n let eStr = n.toExponential(prec);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n if (ch === "E") eStr = eStr.toUpperCase();\n result += applyWidth(spec, eStr);\n }\n } else if (ch === "x" || ch === "X") {\n const n = Math.round(toNumber(flat[argIdx++]));\n let s = Math.abs(n).toString(16);\n if (ch === "X") s = s.toUpperCase();\n result += applyWidth(spec, s);\n } else if (ch === "o") {\n const n = Math.round(toNumber(flat[argIdx++]));\n result += applyWidth(spec, Math.abs(n).toString(8));\n } else if (ch === "g" || ch === "G") {\n const gVal = toNumber(flat[argIdx++]);\n if (!isFinite(gVal) || isNaN(gVal)) {\n result += applyWidth(spec, numStr(gVal));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const gPrec = precMatch ? parseInt(precMatch[1]) : 6;\n let gStr;\n if (gVal === 0) {\n gStr = "0";\n } else {\n const exp = Math.floor(Math.log10(Math.abs(gVal)));\n if (exp < -4 || exp >= gPrec) {\n gStr = gVal.toExponential(gPrec - 1);\n const ePos = gStr.indexOf("e");\n let mantissa = gStr.slice(0, ePos);\n let expPart = gStr.slice(ePos);\n if (mantissa.includes(".")) {\n mantissa = mantissa.replace(/\\.?0+$/, "");\n }\n expPart = expPart.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n gStr = mantissa + expPart;\n } else {\n gStr = gVal.toPrecision(gPrec);\n if (gStr.includes(".")) {\n gStr = gStr.replace(/\\.?0+$/, "");\n }\n if (gStr.includes("e")) {\n gStr = String(parseFloat(gStr));\n }\n }\n }\n if (ch === "G") gStr = gStr.toUpperCase();\n result += applyWidth(spec, gStr);\n }\n } else if (ch === "s") {\n const sArg = flat[argIdx++];\n // A numeric/logical argument to %s is a character code (MATLAB:\n // sprintf(\'%s\', 65) -> \'A\').\n const sVal =\n typeof sArg === "number" || typeof sArg === "boolean"\n ? String.fromCharCode(Math.round(toNumber(sArg)))\n : toString(sArg);\n const sFlags = spec.slice(1);\n const sLeftAlign = sFlags.includes("-");\n const sWidthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const sWidth = sWidthMatch ? parseInt(sWidthMatch[1]) : 0;\n if (sWidth > sVal.length) {\n const sPad = " ".repeat(sWidth - sVal.length);\n result += sLeftAlign ? sVal + sPad : sPad + sVal;\n } else {\n result += sVal;\n }\n } else if (ch === "c") {\n result += String.fromCharCode(Math.round(toNumber(flat[argIdx++])));\n } else {\n result += spec + ch;\n argIdx++;\n }\n }\n } else if (fmt[i] === "\\\\" && i + 1 < fmt.length) {\n i++;\n switch (fmt[i]) {\n case "n":\n result += "\\n";\n break;\n case "t":\n result += "\\t";\n break;\n case "\\\\":\n result += "\\\\";\n break;\n default:\n result += "\\\\" + fmt[i];\n }\n i++;\n } else {\n result += fmt[i];\n i++;\n }\n }\n if (argIdx === startArgIdx) break;\n } while (argIdx < flat.length);\n\n return result;\n}\n',
73717
+ "format_engine.js": '// JS sibling of `format_engine.h`. Numbl-compatible printf-style\n// formatter, ported from `numbl/src/numbl-core/helpers/string.ts`\'s\n// `sprintfFormat`. Shared by fprintf / sprintf / error / assert on\n// the JS side.\n//\n// Spec set: d i u f e E g G x X o s c %\n// Flags: - + 0 # space. Precision `.N`. Width digits. `*` consumes the\n// next numeric arg as a width.\n// Escapes interpreted AT FORMAT TIME: \\n \\t \\r \\a \\b \\f \\v \\0 \\\\.\n// Numeric tensors flatten column-major into the scalar stream; the\n// format string cycles through args until consumed (with a "no arg\n// consumed this pass" guard against infinite output).\n\nfunction isTensor(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "tensor";\n}\nfunction isChar(v) {\n return typeof v === "object" && v !== null && v.mtoc2Tag === "char";\n}\n\nfunction toNumber(v) {\n if (typeof v === "number") return v;\n if (typeof v === "boolean") return v ? 1 : 0;\n if (typeof v === "string") return Number(v);\n if (isChar(v)) {\n // Match the interpreter\'s toNumber (runtime/convert.ts): a single\n // char yields its code point; a multi-char value has no scalar\n // numeric form. `Number("A")` would wrongly give NaN.\n if (v.value.length === 1) return v.value.charCodeAt(0);\n throw new Error("Cannot convert multi-char to number");\n }\n if (isTensor(v) && v.data.length === 1) return v.data[0];\n return Number(v);\n}\n\nfunction toString(v) {\n if (typeof v === "string") return v;\n if (typeof v === "number") return String(v);\n if (typeof v === "boolean") return v ? "1" : "0";\n if (isChar(v)) return v.value;\n return String(v);\n}\n\nfunction numStr(n) {\n if (n === Infinity) return "Inf";\n if (n === -Infinity) return "-Inf";\n if (Number.isNaN(n)) return "NaN";\n if (n === 0) return "0";\n const prec = 5;\n const exp = Math.floor(Math.log10(Math.abs(n)));\n let s;\n if (exp < -4 || exp >= prec) {\n s = n.toExponential(prec - 1);\n const ePos = s.indexOf("e");\n let mantissa = s.slice(0, ePos);\n const expPart0 = s.slice(ePos);\n if (mantissa.includes(".")) mantissa = mantissa.replace(/\\.?0+$/, "");\n const expPart = expPart0.replace(/([eE][+-])(\\d)$/, "$1" + "0$2");\n s = mantissa + expPart;\n } else {\n if (Number.isInteger(n)) return String(n);\n s = n.toPrecision(prec);\n if (s.includes(".")) s = s.replace(/\\.?0+$/, "");\n }\n return s;\n}\n\nfunction applyWidth(spec, str) {\n const m = spec.match(/^%([-+ #]*)0?(\\d+)?/);\n if (!m) return str;\n const explicitFlags = m[1] || "";\n const leftAlign = explicitFlags.includes("-");\n const afterPercent = spec.slice(1);\n const flagAndWidth = afterPercent.match(/^([-+ #]*)(0?)(\\d+)?/);\n const zeroFlag = flagAndWidth ? flagAndWidth[2] === "0" : false;\n const width = flagAndWidth && flagAndWidth[3] ? parseInt(flagAndWidth[3]) : 0;\n if (width <= str.length) return str;\n const zeroPad = !leftAlign && zeroFlag;\n const padLen = width - str.length;\n if (leftAlign) return str + " ".repeat(padLen);\n if (zeroPad) {\n if (str[0] === "-" || str[0] === "+") {\n return str[0] + "0".repeat(padLen) + str.slice(1);\n }\n return "0".repeat(padLen) + str;\n }\n return " ".repeat(padLen) + str;\n}\n\nfunction mtoc2_sprintf_format(fmt, args) {\n // Char literals (`\'foo\'`) come through wrapped as\n // `{mtoc2Tag:"char", value:"..."}` to match the interpreter\'s\n // RuntimeValue convention; unwrap so the format walker indexes\n // a plain JS string.\n if (isChar(fmt)) fmt = fmt.value;\n // Flatten args column-major (tensors expand element-by-element).\n const flat = [];\n for (const arg of args) {\n if (isTensor(arg)) {\n for (let k = 0; k < arg.data.length; k++) flat.push(arg.data[k]);\n } else {\n flat.push(arg);\n }\n }\n\n let result = "";\n let argIdx = 0;\n\n do {\n const startArgIdx = argIdx;\n let outOfArgs = false;\n let i = 0;\n while (i < fmt.length && !outOfArgs) {\n if (fmt[i] === "%" && i + 1 < fmt.length) {\n i++;\n let spec = "%";\n while (i < fmt.length && !"dfigeEsoxXuc%".includes(fmt[i])) {\n if (fmt[i] === "*") {\n if (argIdx >= flat.length) {\n outOfArgs = true;\n break;\n }\n spec += String(Math.round(toNumber(flat[argIdx++])));\n i++;\n } else {\n spec += fmt[i];\n i++;\n }\n }\n if (outOfArgs) break;\n if (i < fmt.length) {\n const ch = fmt[i];\n i++;\n if (ch === "%") {\n result += "%";\n } else if (argIdx >= flat.length) {\n outOfArgs = true;\n } else if (ch === "d" || ch === "i" || ch === "u") {\n const raw = toNumber(flat[argIdx++]);\n const isInt = Number.isInteger(raw);\n const canPrintAsInt = ch === "u" ? isInt && raw >= 0 : isInt;\n if (!canPrintAsInt) {\n let eStr = raw.toExponential(6);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n result += applyWidth(spec, eStr);\n } else {\n const n = raw;\n const flags = spec.slice(1);\n const hasPlus = flags.includes("+");\n const leftAlign = flags.includes("-");\n const widthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const width = widthMatch ? parseInt(widthMatch[1]) : 0;\n const zeroPad = !leftAlign && /^[-+ ]*0/.test(spec.slice(1));\n const s = String(Math.abs(n));\n const sign = n < 0 ? "-" : hasPlus ? "+" : "";\n if (width > 0) {\n const padChar = zeroPad ? "0" : " ";\n const padLen = Math.max(0, width - sign.length - s.length);\n const pad = padChar.repeat(padLen);\n result += leftAlign\n ? sign + s + " ".repeat(padLen)\n : zeroPad\n ? sign + pad + s\n : pad + sign + s;\n } else {\n result += sign + s;\n }\n }\n } else if (ch === "f") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const fFlags = spec.slice(1);\n const fHasPlus = fFlags.includes("+");\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n const formatted = n.toFixed(prec);\n const fSign = n < 0 ? "" : fHasPlus ? "+" : "";\n result += applyWidth(spec, fSign + formatted);\n }\n } else if (ch === "e" || ch === "E") {\n const n = toNumber(flat[argIdx++]);\n if (!isFinite(n) || isNaN(n)) {\n result += applyWidth(spec, numStr(n));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const prec = precMatch ? parseInt(precMatch[1]) : 6;\n let eStr = n.toExponential(prec);\n eStr = eStr.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n if (ch === "E") eStr = eStr.toUpperCase();\n result += applyWidth(spec, eStr);\n }\n } else if (ch === "x" || ch === "X") {\n const n = Math.round(toNumber(flat[argIdx++]));\n let s = Math.abs(n).toString(16);\n if (ch === "X") s = s.toUpperCase();\n result += applyWidth(spec, s);\n } else if (ch === "o") {\n const n = Math.round(toNumber(flat[argIdx++]));\n result += applyWidth(spec, Math.abs(n).toString(8));\n } else if (ch === "g" || ch === "G") {\n const gVal = toNumber(flat[argIdx++]);\n if (!isFinite(gVal) || isNaN(gVal)) {\n result += applyWidth(spec, numStr(gVal));\n } else {\n const precMatch = spec.match(/\\.(\\d+)/);\n const gPrec = precMatch ? parseInt(precMatch[1]) : 6;\n let gStr;\n if (gVal === 0) {\n gStr = "0";\n } else {\n const exp = Math.floor(Math.log10(Math.abs(gVal)));\n if (exp < -4 || exp >= gPrec) {\n gStr = gVal.toExponential(gPrec - 1);\n const ePos = gStr.indexOf("e");\n let mantissa = gStr.slice(0, ePos);\n let expPart = gStr.slice(ePos);\n if (mantissa.includes(".")) {\n mantissa = mantissa.replace(/\\.?0+$/, "");\n }\n expPart = expPart.replace(/e([+-])(\\d)$/, "e$1" + "0$2");\n gStr = mantissa + expPart;\n } else {\n gStr = gVal.toPrecision(gPrec);\n if (gStr.includes(".")) {\n gStr = gStr.replace(/\\.?0+$/, "");\n }\n if (gStr.includes("e")) {\n gStr = String(parseFloat(gStr));\n }\n }\n }\n if (ch === "G") gStr = gStr.toUpperCase();\n result += applyWidth(spec, gStr);\n }\n } else if (ch === "s") {\n const sArg = flat[argIdx++];\n // A numeric/logical argument to %s is a character code (MATLAB:\n // sprintf(\'%s\', 65) -> \'A\').\n const sVal =\n typeof sArg === "number" || typeof sArg === "boolean"\n ? String.fromCharCode(Math.round(toNumber(sArg)))\n : toString(sArg);\n const sFlags = spec.slice(1);\n const sLeftAlign = sFlags.includes("-");\n const sWidthMatch = spec.match(/^%[^0-9]*(\\d+)/);\n const sWidth = sWidthMatch ? parseInt(sWidthMatch[1]) : 0;\n if (sWidth > sVal.length) {\n const sPad = " ".repeat(sWidth - sVal.length);\n result += sLeftAlign ? sVal + sPad : sPad + sVal;\n } else {\n result += sVal;\n }\n } else if (ch === "c") {\n result += String.fromCharCode(Math.round(toNumber(flat[argIdx++])));\n } else {\n result += spec + ch;\n argIdx++;\n }\n }\n } else if (fmt[i] === "\\\\" && i + 1 < fmt.length) {\n i++;\n switch (fmt[i]) {\n case "n":\n result += "\\n";\n break;\n case "t":\n result += "\\t";\n break;\n case "r":\n result += "\\r";\n break;\n case "a":\n result += "\\x07";\n break;\n case "b":\n result += "\\b";\n break;\n case "f":\n result += "\\f";\n break;\n case "v":\n result += "\\v";\n break;\n case "0":\n result += "\\0";\n break;\n case "\\\\":\n result += "\\\\";\n break;\n default:\n result += "\\\\" + fmt[i];\n }\n i++;\n } else {\n result += fmt[i];\n i++;\n }\n }\n if (argIdx === startArgIdx) break;\n } while (argIdx < flat.length);\n\n return result;\n}\n',
73697
73718
  "fprintf.js": "// JS sibling of `fprintf.h`. Routes formatted output through\n// `mtoc2_sprintf_format` then writes via `$write` (the JS analog of\n// C's stdout). Numbl-compatible spec set & semantics.\n\n\nfunction mtoc2_fprintf(fmt, ...args) {\n $write(mtoc2_sprintf_format(fmt, args));\n}\n",
73698
73719
  "sprintf.js": '// JS sibling of `sprintf.h`. Two output variants:\n// - `mtoc2_sprintf_str(fmt, args)` returns the formatted text as a\n// plain JS string (the equivalent of numbl\'s `String` kind).\n// - `mtoc2_sprintf_char(fmt, args)` returns it as a `mtoc2_char_tensor_t`\n// placeholder \u2014 we keep the JS shape as a `{mtoc2Tag: "char", value}`\n// object so disp/length etc. dispatch correctly.\n\n\nfunction mtoc2_sprintf_str(fmt, ...args) {\n return mtoc2_sprintf_format(fmt, args);\n}\n\nfunction mtoc2_sprintf_char(fmt, ...args) {\n return { mtoc2Tag: "char", value: mtoc2_sprintf_format(fmt, args) };\n}\n',
73699
73720
  "warning_fmt.js": '// JS sibling of `warning_fmt.h`. Format the message and write\n// `Warning: <msg>\\n` via `$write`. Matches numbl\n// (`runtime/specialBuiltins.ts:190` \u2014 `rt.output("Warning: " + msg + "\\n")`).\n\n\nfunction mtoc2_warning_fmt(fmt, ...args) {\n $write("Warning: " + mtoc2_sprintf_format(fmt, args) + "\\n");\n}\n\nfunction mtoc2_warning_fmt_id(_id, fmt, ...args) {\n // The id is informational \u2014 numbl threads it to lastwarn state,\n // which mtoc2 doesn\'t expose. Drop it here so the visible output\n // matches the bare form.\n $write("Warning: " + mtoc2_sprintf_format(fmt, args) + "\\n");\n}\n',
@@ -93041,6 +93062,9 @@ ${sections.join("\n\n")}` : `// No ${label} generated`;
93041
93062
  result.implicitCwdPath = implicitCwdPath;
93042
93063
  return result;
93043
93064
  } catch (e) {
93065
+ if (e instanceof CancellationError) {
93066
+ throw e;
93067
+ }
93044
93068
  const generatedJS = buildDump(jsSections, "JS");
93045
93069
  const generatedC = buildDump(cSections, "C");
93046
93070
  if (e instanceof RuntimeError) {
@@ -1,2 +1,2 @@
1
1
  /** Numbl version, used for JIT disk cache invalidation. */
2
- export declare const NUMBL_VERSION = "0.4.13";
2
+ export declare const NUMBL_VERSION = "0.4.15";