numbl 0.4.7 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cli/cli.js CHANGED
@@ -39194,6 +39194,8 @@ var SPECIAL_BUILTIN_NAMES = [
39194
39194
  "delete",
39195
39195
  "rmdir",
39196
39196
  "movefile",
39197
+ "copyfile",
39198
+ "fileattrib",
39197
39199
  "unzip",
39198
39200
  "dir",
39199
39201
  "warning",
@@ -39203,6 +39205,7 @@ var SPECIAL_BUILTIN_NAMES = [
39203
39205
  "userpath",
39204
39206
  "getenv",
39205
39207
  "setenv",
39208
+ "maxNumCompThreads",
39206
39209
  "pwd",
39207
39210
  "cd",
39208
39211
  "ode45",
@@ -40144,6 +40147,65 @@ function registerSpecialBuiltins(rt) {
40144
40147
  RTV.char("")
40145
40148
  ];
40146
40149
  });
40150
+ registerSpecial("copyfile", (nargout, args) => {
40151
+ const io = requireFileIO();
40152
+ if (!io.copyfile)
40153
+ throw new RuntimeError("copyfile is not available in this environment");
40154
+ const margs = args.map((a) => ensureRuntimeValue(a));
40155
+ if (margs.length < 1)
40156
+ throw new RuntimeError("copyfile requires at least 1 argument");
40157
+ const source = toString(margs[0]);
40158
+ const destination = margs.length >= 2 ? toString(margs[1]) : rt.system?.cwd() ?? ".";
40159
+ let force = false;
40160
+ if (margs.length >= 3) {
40161
+ const third = toString(margs[2]);
40162
+ if (third.toLowerCase() === "f") force = true;
40163
+ }
40164
+ const ok = io.copyfile(source, destination, force);
40165
+ if (nargout === 0) {
40166
+ if (!ok)
40167
+ throw new RuntimeError(
40168
+ `copyfile: cannot copy '${source}' to '${destination}'`
40169
+ );
40170
+ return void 0;
40171
+ }
40172
+ return nargout <= 1 ? RTV.num(ok ? 1 : 0) : [
40173
+ RTV.num(ok ? 1 : 0),
40174
+ RTV.char(ok ? "" : `Cannot copy '${source}' to '${destination}'`),
40175
+ RTV.char("")
40176
+ ];
40177
+ });
40178
+ registerSpecial("fileattrib", (nargout, args) => {
40179
+ const io = requireFileIO();
40180
+ if (!io.fileattrib)
40181
+ throw new RuntimeError("fileattrib is not available in this environment");
40182
+ const margs = args.map((a) => ensureRuntimeValue(a));
40183
+ if (margs.length < 1)
40184
+ throw new RuntimeError("fileattrib requires at least 1 argument");
40185
+ const p2 = toString(margs[0]);
40186
+ const info = io.fileattrib(p2);
40187
+ if (nargout === 0) {
40188
+ if (!info)
40189
+ throw new RuntimeError(
40190
+ `fileattrib: cannot find '${p2}': No such file or directory`
40191
+ );
40192
+ return void 0;
40193
+ }
40194
+ const values = info ? RTV.struct(
40195
+ /* @__PURE__ */ new Map([
40196
+ ["Name", RTV.char(info.Name)],
40197
+ ["archive", RTV.num(0)],
40198
+ ["system", RTV.num(0)],
40199
+ ["hidden", RTV.num(0)],
40200
+ ["directory", RTV.num(info.directory ? 1 : 0)],
40201
+ ["UserRead", RTV.num(info.UserRead ? 1 : 0)],
40202
+ ["UserWrite", RTV.num(info.UserWrite ? 1 : 0)],
40203
+ ["UserExecute", RTV.num(info.UserExecute ? 1 : 0)]
40204
+ ])
40205
+ ) : RTV.char(`No such file or directory: ${p2}`);
40206
+ if (nargout <= 1) return RTV.num(info ? 1 : 0);
40207
+ return [RTV.num(info ? 1 : 0), values, RTV.char("")];
40208
+ });
40147
40209
  registerSpecial("unzip", (nargout, args) => {
40148
40210
  const io = requireFileIO();
40149
40211
  if (!io.unzip)
@@ -40469,6 +40531,7 @@ function registerSpecialBuiltins(rt) {
40469
40531
  }
40470
40532
  return RTV.char(sys?.getEnv(toString(args[0])) ?? "");
40471
40533
  });
40534
+ registerSpecial("maxNumCompThreads", () => RTV.num(1));
40472
40535
  registerSpecialVoid("setenv", (args) => {
40473
40536
  const sys = rt.system;
40474
40537
  if (args.length === 2) {
@@ -62854,7 +62917,7 @@ function getSourceLine(getSource, file, line) {
62854
62917
  }
62855
62918
 
62856
62919
  // src/numbl-core/version.ts
62857
- var NUMBL_VERSION = "0.4.7";
62920
+ var NUMBL_VERSION = "0.4.8";
62858
62921
 
62859
62922
  // src/cli-repl.ts
62860
62923
  import { createInterface } from "readline";
@@ -62870,6 +62933,13 @@ import { join as join4 } from "path";
62870
62933
  import { homedir as homedir2 } from "os";
62871
62934
 
62872
62935
  // src/numbl-core/jsUserFunctions.ts
62936
+ function callHandle(handle, args, nargout = 1) {
62937
+ const rt = getCurrentRuntime();
62938
+ if (!rt) {
62939
+ throw new RuntimeError("callHandle: no active runtime to invoke handle");
62940
+ }
62941
+ return rt.index(handle, args, nargout);
62942
+ }
62873
62943
  function funcNameFromFile(fileName) {
62874
62944
  const base = fileName.split("/").pop();
62875
62945
  return base.replace(/\.numbl\.js$/, "");
@@ -62915,6 +62985,8 @@ function instantiateWasm(wasmData) {
62915
62985
  const moduleImports = WebAssembly.Module.imports(wasmModule);
62916
62986
  const importObject = {};
62917
62987
  const neededModules = new Set(moduleImports.map((i) => i.module));
62988
+ const callbacks = /* @__PURE__ */ new Map();
62989
+ let nextCbId = 1;
62918
62990
  if (neededModules.has("wasi_snapshot_preview1")) {
62919
62991
  importObject.wasi_snapshot_preview1 = {
62920
62992
  fd_write: () => 0,
@@ -62934,10 +63006,36 @@ function instantiateWasm(wasmData) {
62934
63006
  if (neededModules.has("env")) {
62935
63007
  importObject.env = {
62936
63008
  emscripten_notify_memory_growth: () => {
63009
+ },
63010
+ // Scalar callback: WASM calls back into a registered handle with one
63011
+ // f64 and receives an f64. Exceptions thrown here (incl. a missing id)
63012
+ // propagate out through the WASM call into the apply.
63013
+ numbl_cb_d: (id, x) => {
63014
+ const fn = callbacks.get(id);
63015
+ if (!fn) {
63016
+ throw new RuntimeError(
63017
+ `numbl_cb_d: no callback registered for id ${id}`
63018
+ );
63019
+ }
63020
+ return fn(x);
62937
63021
  }
62938
63022
  };
62939
63023
  }
62940
- return new WebAssembly.Instance(wasmModule, importObject);
63024
+ const instance = new WebAssembly.Instance(
63025
+ wasmModule,
63026
+ importObject
63027
+ );
63028
+ instance.callbacks = {
63029
+ add(fn) {
63030
+ const id = nextCbId++;
63031
+ callbacks.set(id, fn);
63032
+ return id;
63033
+ },
63034
+ remove(id) {
63035
+ callbacks.delete(id);
63036
+ }
63037
+ };
63038
+ return instance;
62941
63039
  }
62942
63040
  function resolveBindings(file, directives, getWasmInstance, nativeBridge2) {
62943
63041
  const wasmInstance = directives.wasm ? getWasmInstance(directives.wasm) : void 0;
@@ -63012,6 +63110,8 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge2) {
63012
63110
  "wasm",
63013
63111
  "native",
63014
63112
  "importJS",
63113
+ "callHandle",
63114
+ "toNumber",
63015
63115
  libFile.source
63016
63116
  );
63017
63117
  const exports = factory(
@@ -63021,7 +63121,9 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge2) {
63021
63121
  dummyRegister,
63022
63122
  wasmInstance,
63023
63123
  nativeLib,
63024
- importJS
63124
+ importJS,
63125
+ callHandle,
63126
+ toNumber
63025
63127
  );
63026
63128
  libCache.set(name, exports);
63027
63129
  return exports;
@@ -63059,6 +63161,8 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge2) {
63059
63161
  "wasm",
63060
63162
  "native",
63061
63163
  "importJS",
63164
+ "callHandle",
63165
+ "toNumber",
63062
63166
  file.source
63063
63167
  );
63064
63168
  factory(
@@ -63068,7 +63172,9 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge2) {
63068
63172
  registerFn,
63069
63173
  wasmInstance,
63070
63174
  nativeLib,
63071
- importJS
63175
+ importJS,
63176
+ callHandle,
63177
+ toNumber
63072
63178
  );
63073
63179
  if (!builtin) {
63074
63180
  throw new Error(
@@ -64688,6 +64794,15 @@ __export(interpreterExec_exports, {
64688
64794
  writeLValueBase: () => writeLValueBase
64689
64795
  });
64690
64796
 
64797
+ // src/numbl-core/jitDeclineDiagnostics.ts
64798
+ var lastDecline = null;
64799
+ function recordJitDecline(d) {
64800
+ lastDecline = d;
64801
+ }
64802
+ function getLastJitDecline() {
64803
+ return lastDecline;
64804
+ }
64805
+
64691
64806
  // src/numbl-core/runtime/cow.ts
64692
64807
  function cowCopy(v) {
64693
64808
  if (isRuntimeTensor(v)) {
@@ -64990,14 +65105,16 @@ function execStmtInner(stmt) {
64990
65105
  case "Directive": {
64991
65106
  if (stmt.directive === "assert_jit") {
64992
65107
  const wantC = stmt.args.includes("c");
65108
+ const decline = getLastJitDecline();
65109
+ const why = decline ? ` Most recent JIT decline (${decline.where}, ${decline.kind}): ${decline.message}` : ` (no JIT decline reason was recorded \u2014 the unit may have been declined before lowering, e.g. an unsupported input type.)`;
64993
65110
  if (this.optimization === "1") {
64994
65111
  throw new RuntimeError(
64995
- `%!numbl:assert_jit: expected the enclosing loop/function/script to be JS-JIT-compiled at --opt 1, but it ran in the interpreter. (Run with --opt 0 to silence.)`
65112
+ `%!numbl:assert_jit: expected the enclosing loop/function/script to be JS-JIT-compiled at --opt 1, but it ran in the interpreter.${why} (Run with --opt 0 to silence.)`
64996
65113
  );
64997
65114
  }
64998
65115
  if (this.optimization === "2" && wantC) {
64999
65116
  throw new RuntimeError(
65000
- `%!numbl:assert_jit c: expected the enclosing loop/function/script to be C-JIT-compiled at --opt 2, but it ran in the interpreter. (Run with --opt 0 to silence.)`
65117
+ `%!numbl:assert_jit c: expected the enclosing loop/function/script to be C-JIT-compiled at --opt 2, but it ran in the interpreter.${why} (Run with --opt 0 to silence.)`
65001
65118
  );
65002
65119
  }
65003
65120
  }
@@ -78009,6 +78126,30 @@ var sin = defineUnaryRealMath({
78009
78126
  complex: { cFnComplex: "mtoc2_csin", jsFnComplex: cSin }
78010
78127
  });
78011
78128
 
78129
+ // src/numbl-core/jit/builtins/defs/math/rand.ts
78130
+ var rand = {
78131
+ name: "rand",
78132
+ transfer(argTypes, nargout) {
78133
+ if (nargout > 1) {
78134
+ throw new UnsupportedConstruct(
78135
+ `'rand' does not support multi-output (nargout=${nargout})`
78136
+ );
78137
+ }
78138
+ if (argTypes.length !== 0) {
78139
+ throw new UnsupportedConstruct(
78140
+ `JS-JIT 'rand' supports only the scalar form rand() so far (got ${argTypes.length} arg(s)); matrix/seed forms run in the interpreter`
78141
+ );
78142
+ }
78143
+ return [scalarDouble("nonneg")];
78144
+ },
78145
+ emitJs() {
78146
+ return "$rand()";
78147
+ },
78148
+ call() {
78149
+ return [rngRandom()];
78150
+ }
78151
+ };
78152
+
78012
78153
  // src/numbl-core/jit/builtins/defs/math/tan.ts
78013
78154
  var tan = defineUnaryRealMath({
78014
78155
  name: "tan",
@@ -83392,6 +83533,7 @@ for (const b of [
83392
83533
  stdBuiltin,
83393
83534
  min,
83394
83535
  max,
83536
+ rand,
83395
83537
  any,
83396
83538
  all,
83397
83539
  zeros,
@@ -89231,12 +89373,13 @@ function emitJsProgram(prog, opts = {}) {
89231
89373
  const wrapperLines = [];
89232
89374
  if (opts.exposeSpec !== void 0) {
89233
89375
  wrapperLines.push(
89234
- `return function ($h) { globalThis.$write = $h.write; globalThis.$plotDispatch = $h.plotDispatch; return ${opts.exposeSpec}; };`
89376
+ `return function ($h) { globalThis.$write = $h.write; globalThis.$plotDispatch = $h.plotDispatch; globalThis.$rand = $h.rand; return ${opts.exposeSpec}; };`
89235
89377
  );
89236
89378
  } else {
89237
89379
  wrapperLines.push("function run($h) {");
89238
89380
  wrapperLines.push(" globalThis.$write = $h.write;");
89239
89381
  wrapperLines.push(" globalThis.$plotDispatch = $h.plotDispatch;");
89382
+ wrapperLines.push(" globalThis.$rand = $h.rand;");
89240
89383
  const locals = collectAssignedLocals(prog.topLevelStmts);
89241
89384
  if (locals.length > 0) {
89242
89385
  wrapperLines.push(` let ${locals.join(", ")};`);
@@ -94431,6 +94574,7 @@ function getOrCreateSession(interp) {
94431
94574
  function buildHostHelpers(rt) {
94432
94575
  return {
94433
94576
  write: (s) => rt.output(s),
94577
+ rand: () => rngRandom(),
94434
94578
  plotDispatch: (name, args) => {
94435
94579
  const runtimeArgs = args.map((a) => jitToNumbl(a));
94436
94580
  const handled = dispatchPlotBuiltin(
@@ -94523,6 +94667,11 @@ var jitCallExecutor = {
94523
94667
  return { specFn };
94524
94668
  } catch (e) {
94525
94669
  if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
94670
+ recordJitDecline({
94671
+ message: e.message,
94672
+ kind: e.constructor.name,
94673
+ where: "jit-call"
94674
+ });
94526
94675
  return null;
94527
94676
  }
94528
94677
  throw e;
@@ -94649,6 +94798,11 @@ var jitLoopExecutor = {
94649
94798
  return { specFn };
94650
94799
  } catch (e) {
94651
94800
  if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
94801
+ recordJitDecline({
94802
+ message: e.message,
94803
+ kind: e.constructor.name,
94804
+ where: "jit-loop"
94805
+ });
94652
94806
  return null;
94653
94807
  }
94654
94808
  throw e;
@@ -94789,6 +94943,11 @@ var jitTopLevelExecutor = {
94789
94943
  return { specFn, nargout };
94790
94944
  } catch (e) {
94791
94945
  if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
94946
+ recordJitDecline({
94947
+ message: e.message,
94948
+ kind: e.constructor.name,
94949
+ where: "jit-top-level"
94950
+ });
94792
94951
  return null;
94793
94952
  }
94794
94953
  throw e;
@@ -95463,7 +95622,8 @@ import {
95463
95622
  rmSync,
95464
95623
  rmdirSync,
95465
95624
  renameSync,
95466
- chmodSync
95625
+ chmodSync,
95626
+ realpathSync
95467
95627
  } from "fs";
95468
95628
  import { unzipSync } from "fflate";
95469
95629
  import { execFileSync } from "child_process";
@@ -95855,6 +96015,51 @@ var NodeFileIOAdapter = class {
95855
96015
  return false;
95856
96016
  }
95857
96017
  }
96018
+ copyfile(source, destination, force) {
96019
+ try {
96020
+ const src = expandTilde(source);
96021
+ const srcStat = statSync2(src);
96022
+ let dst = expandTilde(destination);
96023
+ try {
96024
+ if (statSync2(dst).isDirectory()) dst = join3(dst, basename(src));
96025
+ } catch {
96026
+ }
96027
+ if (srcStat.isDirectory()) {
96028
+ copyDirRecursive(src, dst);
96029
+ return true;
96030
+ }
96031
+ const parent = dirname(dst);
96032
+ if (parent && parent !== "." && parent !== "/") {
96033
+ mkdirSync(parent, { recursive: true });
96034
+ }
96035
+ if (force) {
96036
+ try {
96037
+ chmodSync(dst, 438);
96038
+ } catch {
96039
+ }
96040
+ }
96041
+ writeFileSync(dst, readFileSync3(src));
96042
+ return true;
96043
+ } catch {
96044
+ return false;
96045
+ }
96046
+ }
96047
+ fileattrib(path) {
96048
+ try {
96049
+ const p2 = expandTilde(path);
96050
+ const st2 = statSync2(p2);
96051
+ const mode = st2.mode;
96052
+ return {
96053
+ Name: realpathSync(p2),
96054
+ directory: st2.isDirectory(),
96055
+ UserRead: (mode & 256) !== 0,
96056
+ UserWrite: (mode & 128) !== 0,
96057
+ UserExecute: (mode & 64) !== 0
96058
+ };
96059
+ } catch {
96060
+ return null;
96061
+ }
96062
+ }
95858
96063
  deleteFile(pattern) {
95859
96064
  const p2 = expandTilde(pattern);
95860
96065
  if (p2.includes("*") || p2.includes("?")) {
@@ -98736,6 +98941,9 @@ Options (for build-site):
98736
98941
  the first script)
98737
98942
  --repo-url <url> Source repository URL, shown as a link in the deployed
98738
98943
  site (default: from numbl-project.json)
98944
+ --max-initial-output-panel-height <px>
98945
+ Cap the initial height (px) of the output panel so the
98946
+ figure panel below it starts larger (desktop layout)
98739
98947
 
98740
98948
  Options (for parse):
98741
98949
  --dump-ast <file> Write the AST as indented JSON to <file> (default: stdout)
@@ -99265,6 +99473,9 @@ async function cmdBuildAddon(args) {
99265
99473
  }
99266
99474
  var SITE_DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([".git", ".github", "node_modules"]);
99267
99475
  var SITE_MAX_FILE_BYTES = 25 * 1024 * 1024;
99476
+ function isAgentInstructionFile(name) {
99477
+ return name === "GEMINI.md" || /^(CLAUDE|AGENT)[^/]*\.md$/i.test(name);
99478
+ }
99268
99479
  function readNumblIgnore(projectDir) {
99269
99480
  const p2 = join7(projectDir, ".numblignore");
99270
99481
  if (!existsSync3(p2)) return [];
@@ -99313,6 +99524,7 @@ function collectSiteFiles(projectDir, outDirAbs, ignore) {
99313
99524
  walk2(full);
99314
99525
  } else if (e.isFile()) {
99315
99526
  if (e.name === ".numblignore" || e.name === ".DS_Store") continue;
99527
+ if (isAgentInstructionFile(e.name)) continue;
99316
99528
  const st2 = statSync3(full);
99317
99529
  if (st2.size > SITE_MAX_FILE_BYTES) {
99318
99530
  console.error(
@@ -99334,6 +99546,7 @@ async function cmdBuildSite(args) {
99334
99546
  let title;
99335
99547
  let entry;
99336
99548
  let repoUrl;
99549
+ let maxInitialOutputPanelHeight;
99337
99550
  const positional = [];
99338
99551
  for (let i = 0; i < args.length; i++) {
99339
99552
  const a = args[i];
@@ -99342,7 +99555,16 @@ async function cmdBuildSite(args) {
99342
99555
  else if (a === "--title") title = args[++i];
99343
99556
  else if (a === "--entry") entry = args[++i];
99344
99557
  else if (a === "--repo-url") repoUrl = args[++i];
99345
- else if (a.startsWith("-")) {
99558
+ else if (a === "--max-initial-output-panel-height") {
99559
+ const n = Number(args[++i]);
99560
+ if (!Number.isFinite(n) || n <= 0) {
99561
+ console.error(
99562
+ "Error: --max-initial-output-panel-height must be a positive number"
99563
+ );
99564
+ process.exit(1);
99565
+ }
99566
+ maxInitialOutputPanelHeight = n;
99567
+ } else if (a.startsWith("-")) {
99346
99568
  console.error(`Unknown option: ${a}`);
99347
99569
  process.exit(1);
99348
99570
  } else positional.push(a);
@@ -99395,6 +99617,8 @@ async function cmdBuildSite(args) {
99395
99617
  if (title !== void 0) manifest.title = title;
99396
99618
  if (entry !== void 0) manifest.entry = entry;
99397
99619
  if (repoUrl !== void 0) manifest.repository = repoUrl;
99620
+ if (maxInitialOutputPanelHeight !== void 0)
99621
+ manifest.maxInitialOutputPanelHeight = maxInitialOutputPanelHeight;
99398
99622
  if (!manifest.title) manifest.title = basename2(projectDirAbs);
99399
99623
  const zipEntries = {};
99400
99624
  for (const f of collected) {
@@ -99425,6 +99649,11 @@ async function cmdBuildSite(args) {
99425
99649
  let indexHtml = readFileSync6(indexPath, "utf-8");
99426
99650
  indexHtml = indexHtml.includes("<!-- numbl:base -->") ? indexHtml.replace("<!-- numbl:base -->", inject) : indexHtml.replace("</head>", ` ${inject}
99427
99651
  </head>`);
99652
+ const escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
99653
+ indexHtml = indexHtml.replace(
99654
+ /<title>.*?<\/title>/,
99655
+ `<title>${escapeHtml(manifest.title)}</title>`
99656
+ );
99428
99657
  writeFileSync4(indexPath, indexHtml);
99429
99658
  const redirectTarget = normBase ?? "/";
99430
99659
  writeFileSync4(
package/dist-lib/lib.d.ts CHANGED
@@ -12,6 +12,10 @@ export { setDelaunayBackend } from "./numbl-core/native/geometry-bridge.js";
12
12
  export type { ExecOptions, ExecResult, ProfileData, BuiltinProfileEntry, BuiltinProfileBreakdown, } from "./numbl-core/executeCode.js";
13
13
  export type { FileIOAdapter } from "./numbl-core/fileIOAdapter.js";
14
14
  export type { SystemAdapter } from "./numbl-core/systemAdapter.js";
15
+ export { VirtualFileSystem } from "./vfs/VirtualFileSystem.js";
16
+ export { BrowserFileIOAdapter } from "./vfs/BrowserFileIOAdapter.js";
17
+ export { BrowserSystemAdapter } from "./vfs/BrowserSystemAdapter.js";
18
+ export type { UihtmlSession } from "./numbl-core/executeCode.js";
15
19
  export type { WorkspaceFile, NativeBridge, } from "./numbl-core/workspace/index.js";
16
20
  export type { PlotInstruction } from "./graphics/types.js";
17
21
  export type { RuntimeValue, RuntimeTensor, RuntimeString, RuntimeLogical, RuntimeCell, RuntimeStruct, RuntimeFunction, RuntimeDictionary, } from "./numbl-core/runtime/index.js";