numbl 0.4.4 → 0.4.5
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/README.md +5 -1
- package/dist-cli/cli.js +435 -32
- package/dist-lib/graphics/figuresReducer.d.ts +9 -0
- package/dist-lib/graphics/types.d.ts +11 -0
- package/dist-lib/lib.js +339 -3
- package/dist-lib/numbl-core/executeCode.d.ts +10 -0
- package/dist-lib/numbl-core/runtime/plotBuiltinDispatch.d.ts +6 -0
- package/dist-lib/numbl-core/runtime/runtime.d.ts +22 -0
- package/dist-lib/numbl-core/runtime/uihtmlSession.d.ts +40 -0
- package/dist-lib/numbl-core/version.d.ts +1 -1
- package/dist-plot-viewer/assets/{index-Ct51ZiF1.js → index-CPiPZdGN.js} +246 -168
- package/dist-plot-viewer/index.html +1 -1
- package/dist-site-viewer/assets/index-Y_Z9U6zO.js +4826 -0
- package/dist-site-viewer/assets/{numbl-worker-s3tsbJJ2.js → numbl-worker-CdHI6Ort.js} +211 -126
- package/dist-site-viewer/index.html +1 -1
- package/dist-site-viewer/numbl-embed.js +55 -13
- package/package.json +1 -1
- package/dist-site-viewer/assets/index-USrK1-DZ.js +0 -4748
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Numbl
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A numerical computing environment compatible with MATLAB syntax, featuring 400+ built-in functions. Runs in your browser or on the command line.
|
|
4
4
|
|
|
5
5
|
[](https://numbl.org/embed-repl)
|
|
6
6
|
|
|
@@ -61,3 +61,7 @@ Jeremy Magland and Dan Fortunato, Center for Computational Mathematics, Flatiron
|
|
|
61
61
|
## License
|
|
62
62
|
|
|
63
63
|
Apache 2.0
|
|
64
|
+
|
|
65
|
+
## Disclaimer
|
|
66
|
+
|
|
67
|
+
numbl is not affiliated with, endorsed by, or supported by MathWorks, Inc. MATLAB is a registered trademark of MathWorks, Inc.
|
package/dist-cli/cli.js
CHANGED
|
@@ -347,8 +347,33 @@ Expected directory: ${distDir}`
|
|
|
347
347
|
const sseClients = [];
|
|
348
348
|
let messageId = 0;
|
|
349
349
|
const pendingMessages = [];
|
|
350
|
+
let uihtmlHandler;
|
|
350
351
|
const server = createServer((req, res) => {
|
|
351
352
|
const url2 = new URL(req.url ?? "/", `http://localhost`);
|
|
353
|
+
if (req.method === "POST" && url2.pathname === "/uihtml-event") {
|
|
354
|
+
const chunks = [];
|
|
355
|
+
let size2 = 0;
|
|
356
|
+
let aborted = false;
|
|
357
|
+
req.on("data", (chunk) => {
|
|
358
|
+
size2 += chunk.length;
|
|
359
|
+
if (size2 > 5e6) {
|
|
360
|
+
aborted = true;
|
|
361
|
+
req.destroy();
|
|
362
|
+
} else {
|
|
363
|
+
chunks.push(chunk);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
req.on("end", () => {
|
|
367
|
+
if (aborted) return;
|
|
368
|
+
try {
|
|
369
|
+
uihtmlHandler?.(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
res.writeHead(204);
|
|
373
|
+
res.end();
|
|
374
|
+
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
352
377
|
if (url2.pathname === "/events") {
|
|
353
378
|
res.writeHead(200, {
|
|
354
379
|
"Content-Type": "text/event-stream",
|
|
@@ -411,12 +436,7 @@ Expected directory: ${distDir}`
|
|
|
411
436
|
};
|
|
412
437
|
process.on("SIGINT", onExit);
|
|
413
438
|
process.on("SIGTERM", onExit);
|
|
414
|
-
function
|
|
415
|
-
messageId++;
|
|
416
|
-
const payload = `id: ${messageId}
|
|
417
|
-
data: ${JSON.stringify(instructions)}
|
|
418
|
-
|
|
419
|
-
`;
|
|
439
|
+
function broadcast(payload) {
|
|
420
440
|
if (sseClients.length === 0) {
|
|
421
441
|
pendingMessages.push(payload);
|
|
422
442
|
} else {
|
|
@@ -425,22 +445,41 @@ data: ${JSON.stringify(instructions)}
|
|
|
425
445
|
}
|
|
426
446
|
}
|
|
427
447
|
}
|
|
448
|
+
function sendInstructions(instructions) {
|
|
449
|
+
messageId++;
|
|
450
|
+
broadcast(`id: ${messageId}
|
|
451
|
+
data: ${JSON.stringify(instructions)}
|
|
452
|
+
|
|
453
|
+
`);
|
|
454
|
+
}
|
|
455
|
+
function sendUihtmlEvent(compId, name, dataJson) {
|
|
456
|
+
messageId++;
|
|
457
|
+
const data = JSON.stringify({ compId, name, dataJson });
|
|
458
|
+
broadcast(`id: ${messageId}
|
|
459
|
+
event: uihtml
|
|
460
|
+
data: ${data}
|
|
461
|
+
|
|
462
|
+
`);
|
|
463
|
+
}
|
|
464
|
+
function setUihtmlEventHandler(fn) {
|
|
465
|
+
uihtmlHandler = fn;
|
|
466
|
+
}
|
|
428
467
|
function scriptDone() {
|
|
429
468
|
messageId++;
|
|
430
|
-
|
|
469
|
+
broadcast(`id: ${messageId}
|
|
431
470
|
event: done
|
|
432
471
|
data: {}
|
|
433
472
|
|
|
434
|
-
|
|
435
|
-
if (sseClients.length === 0) {
|
|
436
|
-
pendingMessages.push(payload);
|
|
437
|
-
} else {
|
|
438
|
-
for (const client of sseClients) {
|
|
439
|
-
client.write(payload);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
473
|
+
`);
|
|
442
474
|
}
|
|
443
|
-
return {
|
|
475
|
+
return {
|
|
476
|
+
sendInstructions,
|
|
477
|
+
sendUihtmlEvent,
|
|
478
|
+
setUihtmlEventHandler,
|
|
479
|
+
scriptDone,
|
|
480
|
+
port,
|
|
481
|
+
closed
|
|
482
|
+
};
|
|
444
483
|
}
|
|
445
484
|
function openBrowser(url) {
|
|
446
485
|
const platform = process.platform;
|
|
@@ -465,12 +504,34 @@ function createPlotHandler(disabled, plotOpts) {
|
|
|
465
504
|
return {
|
|
466
505
|
onDrawnow: void 0,
|
|
467
506
|
flushAndWait: async () => {
|
|
507
|
+
},
|
|
508
|
+
sendUihtmlEvent: () => {
|
|
509
|
+
},
|
|
510
|
+
setUihtmlSession: () => {
|
|
468
511
|
}
|
|
469
512
|
};
|
|
470
513
|
}
|
|
471
514
|
let plotServer = null;
|
|
472
515
|
let serverStarting = null;
|
|
473
516
|
const pendingBatches = [];
|
|
517
|
+
let session = null;
|
|
518
|
+
const wireServer = (ps) => {
|
|
519
|
+
ps.setUihtmlEventHandler((e) => {
|
|
520
|
+
if (!session) return;
|
|
521
|
+
const eventType = e.kind === "dataChanged" ? "DataChanged" : "HTMLEventReceived";
|
|
522
|
+
try {
|
|
523
|
+
session.dispatchEvent(e.compId, eventType, {
|
|
524
|
+
name: e.name,
|
|
525
|
+
data: e.data
|
|
526
|
+
});
|
|
527
|
+
} catch (err) {
|
|
528
|
+
process.stderr.write(
|
|
529
|
+
`Error in uihtml callback: ${err instanceof Error ? err.message : String(err)}
|
|
530
|
+
`
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
};
|
|
474
535
|
const onDrawnow = (instructions) => {
|
|
475
536
|
if (plotServer) {
|
|
476
537
|
plotServer.sendInstructions(instructions);
|
|
@@ -479,6 +540,7 @@ function createPlotHandler(disabled, plotOpts) {
|
|
|
479
540
|
if (!serverStarting) {
|
|
480
541
|
serverStarting = startPlotServer(plotOpts).then((ps) => {
|
|
481
542
|
plotServer = ps;
|
|
543
|
+
wireServer(ps);
|
|
482
544
|
for (const batch of pendingBatches) {
|
|
483
545
|
ps.sendInstructions(batch);
|
|
484
546
|
}
|
|
@@ -500,7 +562,13 @@ function createPlotHandler(disabled, plotOpts) {
|
|
|
500
562
|
await plotServer.closed;
|
|
501
563
|
}
|
|
502
564
|
};
|
|
503
|
-
|
|
565
|
+
const sendUihtmlEvent = (compId, name, dataJson) => {
|
|
566
|
+
plotServer?.sendUihtmlEvent(compId, name, dataJson);
|
|
567
|
+
};
|
|
568
|
+
const setUihtmlSession = (s) => {
|
|
569
|
+
session = s;
|
|
570
|
+
};
|
|
571
|
+
return { onDrawnow, flushAndWait, sendUihtmlEvent, setUihtmlSession };
|
|
504
572
|
}
|
|
505
573
|
|
|
506
574
|
// src/numbl-core/runtime/refcount.ts
|
|
@@ -34494,7 +34562,15 @@ function dispatchPlotBuiltin(name, args, instructions, state) {
|
|
|
34494
34562
|
return true;
|
|
34495
34563
|
// ── Graphics ops: figure / labels / hold / layout ──────────────
|
|
34496
34564
|
case "figure": {
|
|
34497
|
-
|
|
34565
|
+
let handle;
|
|
34566
|
+
if (args.length > 0) {
|
|
34567
|
+
handle = toNumber(args[0]);
|
|
34568
|
+
state.maxFigureHandle = Math.max(state.maxFigureHandle ?? 0, handle);
|
|
34569
|
+
} else {
|
|
34570
|
+
handle = (state.maxFigureHandle ?? 0) + 1;
|
|
34571
|
+
state.maxFigureHandle = handle;
|
|
34572
|
+
}
|
|
34573
|
+
state.currentFigureHandle = handle;
|
|
34498
34574
|
plotInstr(instructions, { type: "set_figure_handle", handle });
|
|
34499
34575
|
return true;
|
|
34500
34576
|
}
|
|
@@ -38386,6 +38462,10 @@ var SPECIAL_BUILTIN_NAMES = [
|
|
|
38386
38462
|
"stream2",
|
|
38387
38463
|
"ishold",
|
|
38388
38464
|
"figure",
|
|
38465
|
+
"drawuihtml",
|
|
38466
|
+
"registeruihtmlcallback",
|
|
38467
|
+
"sendEventToHTMLSource",
|
|
38468
|
+
"uigridlayout",
|
|
38389
38469
|
"subplot",
|
|
38390
38470
|
"tiledlayout",
|
|
38391
38471
|
"nexttile",
|
|
@@ -39735,15 +39815,68 @@ function registerSpecialBuiltins(rt) {
|
|
|
39735
39815
|
return rt.ishold();
|
|
39736
39816
|
});
|
|
39737
39817
|
registerSpecial("figure", (nargout, args) => {
|
|
39738
|
-
const handle = args.length > 0 ? args[0] : 1;
|
|
39739
39818
|
dispatchPlotBuiltin(
|
|
39740
39819
|
"figure",
|
|
39741
39820
|
args.map(ensureRuntimeValue),
|
|
39742
39821
|
rt.plotInstructions,
|
|
39743
39822
|
rt
|
|
39744
39823
|
);
|
|
39745
|
-
return nargout >= 1 ? RTV.num(
|
|
39824
|
+
return nargout >= 1 ? RTV.num(rt.currentFigureHandle) : void 0;
|
|
39746
39825
|
});
|
|
39826
|
+
let uihtmlSeq = 0;
|
|
39827
|
+
registerSpecial("drawuihtml", (nargout, args) => {
|
|
39828
|
+
const html = args.length > 0 ? toString(ensureRuntimeValue(args[0])) : "";
|
|
39829
|
+
const data = args.length > 1 ? toString(ensureRuntimeValue(args[1])) : void 0;
|
|
39830
|
+
uihtmlSeq += 1;
|
|
39831
|
+
const id = "uh" + uihtmlSeq;
|
|
39832
|
+
rt.plotInstructions.push({
|
|
39833
|
+
type: "uihtml",
|
|
39834
|
+
id,
|
|
39835
|
+
html,
|
|
39836
|
+
...data !== void 0 ? { data } : {}
|
|
39837
|
+
});
|
|
39838
|
+
return nargout >= 1 ? RTV.char(id) : void 0;
|
|
39839
|
+
});
|
|
39840
|
+
registerSpecial("registeruihtmlcallback", (_nargout, args) => {
|
|
39841
|
+
if (args.length < 3) return void 0;
|
|
39842
|
+
const compId = toString(ensureRuntimeValue(args[0]));
|
|
39843
|
+
const eventType = toString(ensureRuntimeValue(args[1]));
|
|
39844
|
+
const fcn = ensureRuntimeValue(args[2]);
|
|
39845
|
+
if (eventType !== "HTMLEventReceived" && eventType !== "DataChanged") {
|
|
39846
|
+
return void 0;
|
|
39847
|
+
}
|
|
39848
|
+
if (!isRuntimeFunction(fcn)) return void 0;
|
|
39849
|
+
const entry = rt.uihtmlCallbacks.get(compId) ?? {};
|
|
39850
|
+
const prev = entry[eventType];
|
|
39851
|
+
if (prev) decref(rt, prev);
|
|
39852
|
+
incref(fcn);
|
|
39853
|
+
entry[eventType] = fcn;
|
|
39854
|
+
rt.uihtmlCallbacks.set(compId, entry);
|
|
39855
|
+
return void 0;
|
|
39856
|
+
});
|
|
39857
|
+
registerSpecial("sendEventToHTMLSource", (_nargout, args) => {
|
|
39858
|
+
if (args.length < 2) {
|
|
39859
|
+
throw new RuntimeError("sendEventToHTMLSource requires src and name");
|
|
39860
|
+
}
|
|
39861
|
+
const src = ensureRuntimeValue(args[0]);
|
|
39862
|
+
if (!isRuntimeStruct(src) || !src.fields.has("ComponentId")) {
|
|
39863
|
+
throw new RuntimeError(
|
|
39864
|
+
"sendEventToHTMLSource: src must be the component passed to the callback"
|
|
39865
|
+
);
|
|
39866
|
+
}
|
|
39867
|
+
const compId = toString(src.fields.get("ComponentId"));
|
|
39868
|
+
const name = toString(ensureRuntimeValue(args[1]));
|
|
39869
|
+
const dataArg = args.length > 2 ? ensureRuntimeValue(args[2]) : RTV.num(0);
|
|
39870
|
+
const dataJson = toString(
|
|
39871
|
+
ensureRuntimeValue(rt.dispatch("jsonencode", 1, [dataArg]))
|
|
39872
|
+
);
|
|
39873
|
+
rt.onHtmlSourceEvent?.(compId, name, dataJson);
|
|
39874
|
+
return void 0;
|
|
39875
|
+
});
|
|
39876
|
+
registerSpecial(
|
|
39877
|
+
"uigridlayout",
|
|
39878
|
+
(nargout) => nargout >= 1 ? RTV.num(0) : void 0
|
|
39879
|
+
);
|
|
39747
39880
|
const PLOT_RETURNS_ZERO = ["subplot", "tiledlayout", "nexttile", "legend"];
|
|
39748
39881
|
for (const name of PLOT_RETURNS_ZERO) {
|
|
39749
39882
|
registerSpecial(name, (nargout, args) => {
|
|
@@ -41159,6 +41292,19 @@ var figuresReducer = (state, action) => {
|
|
|
41159
41292
|
switch (action.type) {
|
|
41160
41293
|
case "set_figure_handle":
|
|
41161
41294
|
return { ...state, currentHandle: action.handle };
|
|
41295
|
+
case "uihtml": {
|
|
41296
|
+
const fig = ensureFig(state);
|
|
41297
|
+
return {
|
|
41298
|
+
...state,
|
|
41299
|
+
figs: {
|
|
41300
|
+
...state.figs,
|
|
41301
|
+
[state.currentHandle]: {
|
|
41302
|
+
...fig,
|
|
41303
|
+
uihtml: { id: action.id, html: action.html, data: action.data }
|
|
41304
|
+
}
|
|
41305
|
+
}
|
|
41306
|
+
};
|
|
41307
|
+
}
|
|
41162
41308
|
case "set_hold":
|
|
41163
41309
|
return updateAxes(state, { holdOn: action.value });
|
|
41164
41310
|
case "plot": {
|
|
@@ -41599,6 +41745,7 @@ var Runtime = class _Runtime {
|
|
|
41599
41745
|
this.profilingEnabled = !!options.profile;
|
|
41600
41746
|
this.fileIO = options.fileIO;
|
|
41601
41747
|
this.system = options.system;
|
|
41748
|
+
this.onHtmlSourceEvent = options.onHtmlSourceEvent;
|
|
41602
41749
|
if (options.initialHoldState) {
|
|
41603
41750
|
this.holdState = options.initialHoldState;
|
|
41604
41751
|
}
|
|
@@ -41617,9 +41764,24 @@ var Runtime = class _Runtime {
|
|
|
41617
41764
|
plotInstructions = [];
|
|
41618
41765
|
variableValues = {};
|
|
41619
41766
|
holdState = false;
|
|
41767
|
+
/** Figure-handle tracking for MATLAB-style `figure` allocation: `figure`
|
|
41768
|
+
* with no argument creates a new figure (`maxFigureHandle + 1`). */
|
|
41769
|
+
currentFigureHandle = 0;
|
|
41770
|
+
maxFigureHandle = 0;
|
|
41620
41771
|
/** Monotonic id source for graphics handles whose trace can be live-updated
|
|
41621
41772
|
* via `set` / `update_trace` (e.g. lines returned by `line`). */
|
|
41622
41773
|
graphicsIdCounter = 1;
|
|
41774
|
+
/** Reverse channel for `uihtml` components (HTML → MATLAB). Maps a component
|
|
41775
|
+
* id (the `uihtml` instruction's `id`) to the callback handles registered
|
|
41776
|
+
* for it. When non-empty after a run, the host keeps this runtime alive so
|
|
41777
|
+
* iframe events can re-enter the interpreter and invoke these handles.
|
|
41778
|
+
* Handles are incref'd while stored (see registeruihtmlcallback). */
|
|
41779
|
+
uihtmlCallbacks = /* @__PURE__ */ new Map();
|
|
41780
|
+
/** Hook invoked by `sendEventToHTMLSource(src, name, data)` to push an event
|
|
41781
|
+
* from MATLAB back to a uihtml component's page. `dataJson` is the data
|
|
41782
|
+
* already `jsonencode`d. Set from ExecOptions; the host forwards it to the
|
|
41783
|
+
* iframe. */
|
|
41784
|
+
onHtmlSourceEvent;
|
|
41623
41785
|
// tiledlayout/nexttile state. Reset by tiledlayout, advanced by nexttile.
|
|
41624
41786
|
// mode: "fixed" uses the rows/cols verbatim; "flow"/"vertical"/"horizontal"
|
|
41625
41787
|
// grow the grid as tiles are added.
|
|
@@ -41752,6 +41914,15 @@ var Runtime = class _Runtime {
|
|
|
41752
41914
|
}
|
|
41753
41915
|
}
|
|
41754
41916
|
// ── Builtin initialization ──────────────────────────────────────────
|
|
41917
|
+
/** Register the implicit default figure (handle 1) the first time a graphics
|
|
41918
|
+
* op runs without an explicit `figure`, so a later no-arg `figure` allocates
|
|
41919
|
+
* a new handle instead of reusing 1 (MATLAB semantics). */
|
|
41920
|
+
ensureCurrentFigure() {
|
|
41921
|
+
if (!this.currentFigureHandle) {
|
|
41922
|
+
this.currentFigureHandle = 1;
|
|
41923
|
+
this.maxFigureHandle = Math.max(this.maxFigureHandle, 1);
|
|
41924
|
+
}
|
|
41925
|
+
}
|
|
41755
41926
|
initBuiltins() {
|
|
41756
41927
|
registerSpecialBuiltins(this);
|
|
41757
41928
|
for (const name of PLOT_DISPATCH_NAMES) {
|
|
@@ -41897,6 +42068,22 @@ var Runtime = class _Runtime {
|
|
|
41897
42068
|
this.builtins["stream2"] = (_nargout, args) => {
|
|
41898
42069
|
return stream2Call(args.map(ensureRuntimeValue));
|
|
41899
42070
|
};
|
|
42071
|
+
const NO_AUTO_FIGURE = /* @__PURE__ */ new Set(["figure", "close", "clf", "cla", "axis"]);
|
|
42072
|
+
for (const name of [
|
|
42073
|
+
...PLOT_DISPATCH_NAMES,
|
|
42074
|
+
"fplot",
|
|
42075
|
+
"fplot3",
|
|
42076
|
+
"streamline",
|
|
42077
|
+
"stream2"
|
|
42078
|
+
]) {
|
|
42079
|
+
if (NO_AUTO_FIGURE.has(name)) continue;
|
|
42080
|
+
const orig = this.builtins[name];
|
|
42081
|
+
if (!orig) continue;
|
|
42082
|
+
this.builtins[name] = (n, a) => {
|
|
42083
|
+
this.ensureCurrentFigure();
|
|
42084
|
+
return orig(n, a);
|
|
42085
|
+
};
|
|
42086
|
+
}
|
|
41900
42087
|
}
|
|
41901
42088
|
profileEnter(key) {
|
|
41902
42089
|
if (!this.profilingEnabled) return;
|
|
@@ -54476,6 +54663,149 @@ for (const { name, min: min2, max: max2 } of INT_RANGES) {
|
|
|
54476
54663
|
]
|
|
54477
54664
|
});
|
|
54478
54665
|
}
|
|
54666
|
+
var TYPECAST_VIEWS = {
|
|
54667
|
+
uint8: (b) => new Uint8Array(b),
|
|
54668
|
+
int8: (b) => new Int8Array(b),
|
|
54669
|
+
uint16: (b) => new Uint16Array(b),
|
|
54670
|
+
int16: (b) => new Int16Array(b),
|
|
54671
|
+
uint32: (b) => new Uint32Array(b),
|
|
54672
|
+
int32: (b) => new Int32Array(b),
|
|
54673
|
+
single: (b) => new Float32Array(b),
|
|
54674
|
+
double: (b) => new Float64Array(b)
|
|
54675
|
+
};
|
|
54676
|
+
defineBuiltin({
|
|
54677
|
+
name: "typecast",
|
|
54678
|
+
help: {
|
|
54679
|
+
signatures: ["B = typecast(X, CLASS)"],
|
|
54680
|
+
description: "Reinterpret the raw bytes of numeric array X as CLASS (e.g. 'uint8', 'single', 'int32'). numbl stores all numerics as double, so X is treated as double-precision; this is mainly for serializing numeric data to bytes, e.g. typecast(double(x), 'uint8')."
|
|
54681
|
+
},
|
|
54682
|
+
cases: [
|
|
54683
|
+
{
|
|
54684
|
+
match: (argTypes) => {
|
|
54685
|
+
if (argTypes.length !== 2) return null;
|
|
54686
|
+
const a = argTypes[0];
|
|
54687
|
+
if (a.kind === "number" || a.kind === "boolean" || a.kind === "tensor" || a.kind === "complex_or_number")
|
|
54688
|
+
return [{ kind: "tensor", isComplex: false }];
|
|
54689
|
+
return null;
|
|
54690
|
+
},
|
|
54691
|
+
apply: (args) => {
|
|
54692
|
+
const X = args[0];
|
|
54693
|
+
const cls = toString(args[1]).toLowerCase();
|
|
54694
|
+
const makeView = TYPECAST_VIEWS[cls];
|
|
54695
|
+
if (!makeView)
|
|
54696
|
+
throw new RuntimeError(`typecast: unsupported class '${cls}'`);
|
|
54697
|
+
let src;
|
|
54698
|
+
if (isRuntimeNumber(X)) src = Float64Array.of(X);
|
|
54699
|
+
else if (isRuntimeLogical(X)) src = Float64Array.of(X ? 1 : 0);
|
|
54700
|
+
else if (isRuntimeTensor(X)) {
|
|
54701
|
+
if (X.imag)
|
|
54702
|
+
throw new RuntimeError("typecast: complex input not supported");
|
|
54703
|
+
src = X.data;
|
|
54704
|
+
} else {
|
|
54705
|
+
throw new RuntimeError("typecast: X must be a numeric array");
|
|
54706
|
+
}
|
|
54707
|
+
const buffer = src.buffer.slice(
|
|
54708
|
+
src.byteOffset,
|
|
54709
|
+
src.byteOffset + src.byteLength
|
|
54710
|
+
);
|
|
54711
|
+
const view = makeView(buffer);
|
|
54712
|
+
const out = allocFloat64Array(view.length);
|
|
54713
|
+
for (let i = 0; i < view.length; i++) out[i] = view[i];
|
|
54714
|
+
return RTV.tensor(out, [1, view.length]);
|
|
54715
|
+
}
|
|
54716
|
+
}
|
|
54717
|
+
]
|
|
54718
|
+
});
|
|
54719
|
+
function jsonEncodeNumber(x) {
|
|
54720
|
+
return Number.isFinite(x) ? String(x) : "null";
|
|
54721
|
+
}
|
|
54722
|
+
function jsonEncodeTensor(v) {
|
|
54723
|
+
if (v.imag && !imagAllZero(v.imag))
|
|
54724
|
+
throw new RuntimeError("jsonencode: complex values are not supported");
|
|
54725
|
+
const data = v.data;
|
|
54726
|
+
const n = data.length;
|
|
54727
|
+
const enc = v._isLogical ? (x) => x ? "true" : "false" : jsonEncodeNumber;
|
|
54728
|
+
const shape = v.shape ?? [n];
|
|
54729
|
+
if (n === 1 && shape.every((s) => s === 1)) return enc(data[0]);
|
|
54730
|
+
if (n === 0) return "[]";
|
|
54731
|
+
const nonSingleton = shape.filter((s) => s > 1).length;
|
|
54732
|
+
if (shape.length <= 1 || nonSingleton <= 1) {
|
|
54733
|
+
const parts2 = [];
|
|
54734
|
+
for (let i = 0; i < n; i++) parts2.push(enc(data[i]));
|
|
54735
|
+
return "[" + parts2.join(",") + "]";
|
|
54736
|
+
}
|
|
54737
|
+
if (shape.length === 2) {
|
|
54738
|
+
const [m, cols] = shape;
|
|
54739
|
+
const rows = [];
|
|
54740
|
+
for (let i = 0; i < m; i++) {
|
|
54741
|
+
const rowParts = [];
|
|
54742
|
+
for (let j = 0; j < cols; j++) rowParts.push(enc(data[j * m + i]));
|
|
54743
|
+
rows.push("[" + rowParts.join(",") + "]");
|
|
54744
|
+
}
|
|
54745
|
+
return "[" + rows.join(",") + "]";
|
|
54746
|
+
}
|
|
54747
|
+
const parts = [];
|
|
54748
|
+
for (let i = 0; i < n; i++) parts.push(enc(data[i]));
|
|
54749
|
+
return "[" + parts.join(",") + "]";
|
|
54750
|
+
}
|
|
54751
|
+
function jsonEncodeCell(v) {
|
|
54752
|
+
if (v.data.length === 0) return "[]";
|
|
54753
|
+
return "[" + v.data.map(jsonEncodeValue).join(",") + "]";
|
|
54754
|
+
}
|
|
54755
|
+
function jsonEncodeStruct(v) {
|
|
54756
|
+
const parts = [];
|
|
54757
|
+
for (const [key, val] of v.fields) {
|
|
54758
|
+
parts.push(JSON.stringify(key) + ":" + jsonEncodeValue(val));
|
|
54759
|
+
}
|
|
54760
|
+
return "{" + parts.join(",") + "}";
|
|
54761
|
+
}
|
|
54762
|
+
function jsonEncodeValue(v) {
|
|
54763
|
+
if (v === void 0 || v === null) return "null";
|
|
54764
|
+
if (isRuntimeNumber(v)) return jsonEncodeNumber(v);
|
|
54765
|
+
if (isRuntimeLogical(v)) return v ? "true" : "false";
|
|
54766
|
+
if (isRuntimeString(v)) return JSON.stringify(v);
|
|
54767
|
+
if (isRuntimeChar(v)) {
|
|
54768
|
+
if (v.shape && v.shape.length === 2 && v.shape[0] > 1) {
|
|
54769
|
+
const rows = v.shape[0];
|
|
54770
|
+
const cols = v.shape[1];
|
|
54771
|
+
const out = [];
|
|
54772
|
+
for (let i = 0; i < rows; i++) {
|
|
54773
|
+
let row = "";
|
|
54774
|
+
for (let j = 0; j < cols; j++) row += v.value[j * rows + i] ?? "";
|
|
54775
|
+
out.push(JSON.stringify(row));
|
|
54776
|
+
}
|
|
54777
|
+
return "[" + out.join(",") + "]";
|
|
54778
|
+
}
|
|
54779
|
+
return JSON.stringify(v.value);
|
|
54780
|
+
}
|
|
54781
|
+
if (isRuntimeComplexNumber(v)) {
|
|
54782
|
+
if (v.im !== 0)
|
|
54783
|
+
throw new RuntimeError("jsonencode: complex values are not supported");
|
|
54784
|
+
return jsonEncodeNumber(v.re);
|
|
54785
|
+
}
|
|
54786
|
+
if (isRuntimeTensor(v)) return jsonEncodeTensor(v);
|
|
54787
|
+
if (isRuntimeCell(v)) return jsonEncodeCell(v);
|
|
54788
|
+
if (isRuntimeStruct(v)) return jsonEncodeStruct(v);
|
|
54789
|
+
if (isRuntimeStructArray(v))
|
|
54790
|
+
return "[" + v.elements.map(jsonEncodeStruct).join(",") + "]";
|
|
54791
|
+
throw new RuntimeError("jsonencode: unsupported value type");
|
|
54792
|
+
}
|
|
54793
|
+
defineBuiltin({
|
|
54794
|
+
name: "jsonencode",
|
|
54795
|
+
help: {
|
|
54796
|
+
signatures: ["txt = jsonencode(V)"],
|
|
54797
|
+
description: "Encode value V (struct, cell, char/string, logical, or numeric array) as a JSON-formatted char row vector."
|
|
54798
|
+
},
|
|
54799
|
+
cases: [
|
|
54800
|
+
{
|
|
54801
|
+
match: (argTypes) => {
|
|
54802
|
+
if (argTypes.length < 1) return null;
|
|
54803
|
+
return [{ kind: "char" }];
|
|
54804
|
+
},
|
|
54805
|
+
apply: (args) => RTV.char(jsonEncodeValue(args[0]))
|
|
54806
|
+
}
|
|
54807
|
+
]
|
|
54808
|
+
});
|
|
54479
54809
|
function idivideMode(args) {
|
|
54480
54810
|
if (args.length < 3) return "fix";
|
|
54481
54811
|
const m = args[2];
|
|
@@ -58942,7 +59272,7 @@ function getSourceLine(getSource, file, line) {
|
|
|
58942
59272
|
}
|
|
58943
59273
|
|
|
58944
59274
|
// src/numbl-core/version.ts
|
|
58945
|
-
var NUMBL_VERSION = "0.4.
|
|
59275
|
+
var NUMBL_VERSION = "0.4.5";
|
|
58946
59276
|
|
|
58947
59277
|
// src/cli-repl.ts
|
|
58948
59278
|
import { createInterface } from "readline";
|
|
@@ -64361,6 +64691,10 @@ end
|
|
|
64361
64691
|
{
|
|
64362
64692
|
name: "isIllConditioned.m",
|
|
64363
64693
|
source: "function result = isIllConditioned(dA)\n result = dA.isIllCond;\nend\n"
|
|
64694
|
+
},
|
|
64695
|
+
{
|
|
64696
|
+
name: "uihtml.m",
|
|
64697
|
+
source: "classdef uihtml < handle\n %UIHTML Create an HTML UI component (numbl subset).\n % H = UIHTML('HTMLSource', HTML) renders the self-contained HTML string\n % HTML in the figure pane. An optional leading parent argument (e.g. a\n % figure) is accepted and ignored. Supported name-value options:\n % HTMLSource, Data, Position.\n %\n % H = UIHTML('HTMLSource', HTML, 'Data', X) also sends X into the page.\n % Mirroring MATLAB, X is encoded with jsonencode, parsed in the page with\n % JSON.parse, and set on the JavaScript `htmlComponent.Data` object,\n % firing any \"DataChanged\" listener registered in the page's\n % `function setup(htmlComponent)`. To update the data after construction,\n % set H.Data and call show(H) (numbl re-renders the component):\n %\n % h = uihtml('HTMLSource', html, 'Data', struct('n', 1));\n % h.Data = struct('n', 2);\n % show(h);\n %\n % numbl currently supports HTMLSource given as an HTML markup string (a\n % single self-contained document). HTML file paths and supporting files\n % are not yet supported.\n %\n % The reverse channel (page -> MATLAB) is supported for the IDE:\n % HTMLEventReceivedFcn fires when JS calls\n % htmlComponent.sendEventToMATLAB(name,data); inside the callback use\n % sendEventToHTMLSource(src,name,data) to send back to the page.\n % DataChangedFcn fires when JS sets htmlComponent.Data. Register callbacks\n % at construction (name-value) since numbl renders at construction.\n properties\n HTMLSource = ''\n Data = []\n Position = [100 100 100 100]\n HTMLEventReceivedFcn = []\n DataChangedFcn = []\n end\n methods\n function obj = uihtml(varargin)\n args = varargin;\n % Ignore an optional leading parent argument (anything that is not\n % a name-value name string).\n if numel(args) >= 1 && ~(ischar(args{1}) || isstring(args{1}))\n args = args(2:end);\n end\n for i = 1:2:numel(args) - 1\n name = args{i};\n val = args{i + 1};\n if strcmpi(name, 'HTMLSource')\n obj.HTMLSource = val;\n elseif strcmpi(name, 'Data')\n obj.Data = val;\n elseif strcmpi(name, 'Position')\n obj.Position = val;\n elseif strcmpi(name, 'HTMLEventReceivedFcn')\n obj.HTMLEventReceivedFcn = val;\n elseif strcmpi(name, 'DataChangedFcn')\n obj.DataChangedFcn = val;\n end\n end\n render(obj);\n end\n\n function show(obj)\n render(obj);\n end\n\n function render(obj)\n if isempty(obj.HTMLSource)\n return;\n end\n if isempty(obj.Data)\n id = drawuihtml(obj.HTMLSource);\n else\n id = drawuihtml(obj.HTMLSource, jsonencode(obj.Data));\n end\n if ~isempty(obj.HTMLEventReceivedFcn)\n registeruihtmlcallback(id, 'HTMLEventReceived', ...\n obj.HTMLEventReceivedFcn);\n end\n if ~isempty(obj.DataChangedFcn)\n registeruihtmlcallback(id, 'DataChanged', obj.DataChangedFcn);\n end\n end\n end\nend\n"
|
|
64364
64698
|
}
|
|
64365
64699
|
];
|
|
64366
64700
|
|
|
@@ -92326,6 +92660,64 @@ function registerExecutorsForOpt(registry3, opt) {
|
|
|
92326
92660
|
}
|
|
92327
92661
|
}
|
|
92328
92662
|
|
|
92663
|
+
// src/numbl-core/runtime/uihtmlSession.ts
|
|
92664
|
+
function createUihtmlSession(rt, activeSpecials, onDrawnow) {
|
|
92665
|
+
function invokeHandle(fn, args) {
|
|
92666
|
+
if (fn.jsFn) {
|
|
92667
|
+
if (fn.jsFnExpectsNargout) fn.jsFn(0, ...args);
|
|
92668
|
+
else fn.jsFn(...args);
|
|
92669
|
+
return;
|
|
92670
|
+
}
|
|
92671
|
+
rt.dispatch(fn.name, 0, args);
|
|
92672
|
+
}
|
|
92673
|
+
function dispatchEvent(compId, eventType, payload) {
|
|
92674
|
+
const entry = rt.uihtmlCallbacks.get(compId);
|
|
92675
|
+
const fn = entry?.[eventType];
|
|
92676
|
+
if (!fn || !isRuntimeFunction(fn)) return;
|
|
92677
|
+
const saved = /* @__PURE__ */ new Map();
|
|
92678
|
+
for (const name of SPECIAL_BUILTIN_NAMES) {
|
|
92679
|
+
const ex = getIBuiltin(name);
|
|
92680
|
+
if (ex) saved.set(name, ex);
|
|
92681
|
+
}
|
|
92682
|
+
for (const ib of activeSpecials.values()) registerDynamicIBuiltin(ib);
|
|
92683
|
+
pushCurrentRuntime(rt);
|
|
92684
|
+
const before = rt.plotInstructions.length;
|
|
92685
|
+
try {
|
|
92686
|
+
const src = RTV.struct({ ComponentId: RTV.char(compId) });
|
|
92687
|
+
const dataRV = convertJsonValue(payload.data);
|
|
92688
|
+
const event = eventType === "HTMLEventReceived" ? RTV.struct({
|
|
92689
|
+
HTMLEventName: RTV.char(payload.name ?? ""),
|
|
92690
|
+
HTMLEventData: dataRV,
|
|
92691
|
+
Source: src,
|
|
92692
|
+
EventName: RTV.char("HTMLEventReceived")
|
|
92693
|
+
}) : RTV.struct({
|
|
92694
|
+
Data: dataRV,
|
|
92695
|
+
PreviousData: RTV.tensor(new Float64Array(0), [0, 0]),
|
|
92696
|
+
Source: src,
|
|
92697
|
+
EventName: RTV.char("DataChanged")
|
|
92698
|
+
});
|
|
92699
|
+
invokeHandle(fn, [src, event]);
|
|
92700
|
+
} finally {
|
|
92701
|
+
popCurrentRuntime(rt);
|
|
92702
|
+
for (const ib of saved.values()) registerDynamicIBuiltin(ib);
|
|
92703
|
+
}
|
|
92704
|
+
const newInstrs = rt.plotInstructions.slice(before);
|
|
92705
|
+
if (newInstrs.length && onDrawnow) onDrawnow(newInstrs);
|
|
92706
|
+
}
|
|
92707
|
+
function dispose() {
|
|
92708
|
+
for (const entry of rt.uihtmlCallbacks.values()) {
|
|
92709
|
+
if (entry.HTMLEventReceived) decref(rt, entry.HTMLEventReceived);
|
|
92710
|
+
if (entry.DataChanged) decref(rt, entry.DataChanged);
|
|
92711
|
+
}
|
|
92712
|
+
rt.uihtmlCallbacks.clear();
|
|
92713
|
+
}
|
|
92714
|
+
return {
|
|
92715
|
+
hasCallbacks: () => rt.uihtmlCallbacks.size > 0,
|
|
92716
|
+
dispatchEvent,
|
|
92717
|
+
dispose
|
|
92718
|
+
};
|
|
92719
|
+
}
|
|
92720
|
+
|
|
92329
92721
|
// src/numbl-core/executeCode.ts
|
|
92330
92722
|
globalThis.FloatXArray = Float64Array;
|
|
92331
92723
|
var SHIM_SEARCH_PATH = "__numbl_shims__";
|
|
@@ -92777,6 +93169,18 @@ ${sections.join("\n\n")}` : `// No ${label} generated`;
|
|
|
92777
93169
|
variableValues: interpreter.getVariableValues(),
|
|
92778
93170
|
holdState: rt.holdState
|
|
92779
93171
|
};
|
|
93172
|
+
if (rt.uihtmlCallbacks.size > 0) {
|
|
93173
|
+
const activeSpecials = /* @__PURE__ */ new Map();
|
|
93174
|
+
for (const name of SPECIAL_BUILTIN_NAMES) {
|
|
93175
|
+
const ex = getIBuiltin(name);
|
|
93176
|
+
if (ex) activeSpecials.set(name, ex);
|
|
93177
|
+
}
|
|
93178
|
+
result.uihtmlSession = createUihtmlSession(
|
|
93179
|
+
rt,
|
|
93180
|
+
activeSpecials,
|
|
93181
|
+
options.onDrawnow
|
|
93182
|
+
);
|
|
93183
|
+
}
|
|
92780
93184
|
if (options.profile) {
|
|
92781
93185
|
result.profileData = {
|
|
92782
93186
|
executionTimeMs,
|
|
@@ -94717,9 +95121,7 @@ ${code2}
|
|
|
94717
95121
|
process.exit(0);
|
|
94718
95122
|
} else if (opts.verbose) {
|
|
94719
95123
|
const log3 = (msg) => console.error(`[verbose] ${msg}`);
|
|
94720
|
-
const
|
|
94721
|
-
!opts.plot || opts.stream
|
|
94722
|
-
);
|
|
95124
|
+
const plot = createPlotHandler(!opts.plot || opts.stream);
|
|
94723
95125
|
const result = executeCode(
|
|
94724
95126
|
code,
|
|
94725
95127
|
{
|
|
@@ -94728,7 +95130,8 @@ ${code2}
|
|
|
94728
95130
|
onOutput: (text) => {
|
|
94729
95131
|
process.stdout.write(text);
|
|
94730
95132
|
},
|
|
94731
|
-
onDrawnow,
|
|
95133
|
+
onDrawnow: plot.onDrawnow,
|
|
95134
|
+
onHtmlSourceEvent: plot.sendUihtmlEvent,
|
|
94732
95135
|
log: log3,
|
|
94733
95136
|
onJitCompile,
|
|
94734
95137
|
onJitBail,
|
|
@@ -94744,13 +95147,11 @@ ${code2}
|
|
|
94744
95147
|
);
|
|
94745
95148
|
writeProfileIfNeeded(result);
|
|
94746
95149
|
finalizeDumps();
|
|
94747
|
-
|
|
95150
|
+
plot.setUihtmlSession(result.uihtmlSession ?? null);
|
|
95151
|
+
await plot.flushAndWait(result.plotInstructions);
|
|
94748
95152
|
} else {
|
|
94749
95153
|
const asyncPlotOpts = opts.plotPort !== void 0 ? { port: opts.plotPort, host: "0.0.0.0" } : void 0;
|
|
94750
|
-
const
|
|
94751
|
-
!opts.plot,
|
|
94752
|
-
asyncPlotOpts
|
|
94753
|
-
);
|
|
95154
|
+
const plot = createPlotHandler(!opts.plot, asyncPlotOpts);
|
|
94754
95155
|
const result = executeCode(
|
|
94755
95156
|
code,
|
|
94756
95157
|
{
|
|
@@ -94759,7 +95160,8 @@ ${code2}
|
|
|
94759
95160
|
onOutput: (text) => {
|
|
94760
95161
|
process.stdout.write(text);
|
|
94761
95162
|
},
|
|
94762
|
-
onDrawnow,
|
|
95163
|
+
onDrawnow: plot.onDrawnow,
|
|
95164
|
+
onHtmlSourceEvent: plot.sendUihtmlEvent,
|
|
94763
95165
|
onJitCompile,
|
|
94764
95166
|
onJitBail,
|
|
94765
95167
|
fileIO,
|
|
@@ -94774,7 +95176,8 @@ ${code2}
|
|
|
94774
95176
|
);
|
|
94775
95177
|
writeProfileIfNeeded(result);
|
|
94776
95178
|
finalizeDumps();
|
|
94777
|
-
|
|
95179
|
+
plot.setUihtmlSession(result.uihtmlSession ?? null);
|
|
95180
|
+
await plot.flushAndWait(result.plotInstructions);
|
|
94778
95181
|
}
|
|
94779
95182
|
process.exit(0);
|
|
94780
95183
|
} catch (error) {
|
|
@@ -62,6 +62,15 @@ export type FigureState = {
|
|
|
62
62
|
axes: {
|
|
63
63
|
[index: number]: AxesState;
|
|
64
64
|
};
|
|
65
|
+
/** When set, this figure is an HTML UI component (MATLAB `uihtml`): the
|
|
66
|
+
* `html` string is rendered in an iframe instead of the axes/trace canvas.
|
|
67
|
+
* Takes precedence over `axes`. `data` is the JSON-encoded `Data` property
|
|
68
|
+
* (from `jsonencode`), pushed into the page's `htmlComponent`. */
|
|
69
|
+
uihtml?: {
|
|
70
|
+
id: string;
|
|
71
|
+
html: string;
|
|
72
|
+
data?: string;
|
|
73
|
+
};
|
|
65
74
|
};
|
|
66
75
|
export type FiguresState = {
|
|
67
76
|
currentHandle: number;
|