/ concept-collection / jupyterlite-numbl-kernel
Sign in
concept-collection / jupyterlite-numbl-kernel
Restructure demo into a full guided tour with OOP and namespaces
Flatten the advanced/ folder into a numbered top-level walkthrough: intro (now with plotting), the systematic language tour, then advanced MATLAB features, then mip packages last. Two new notebooks showcase numbl's object model (value/handle classes, inheritance, abstract methods, polymorphism, operator overloading, static methods, @class folders) and packages (nested +namespaces, import, package classes, private/ folders), shipping the class/package .m files alongside the notebooks. Make the kernel's workspace .m sync recurse into subfolders so +pkg/, @class/, and private/ layouts reach the session. Every code cell was verified through the REPL path and smoke-tested in a browser, including the OOP and namespace notebooks that depend on the recursive sync.
Jeremy Magland <jmagland@flatironinstitute.org> committed commit 9ff4e5b3ee0c parent e5c5427 Browse files
32 changed files+518−254
README.mdmodified+7−4View file
@@ -57,10 +57,13 @@ jupyter lite build --contents my-notebooks --output-dir dist
5757
5858 The `demo/` directory in this repo contains the demo site sources
5959 (notebooks + requirements); `.github/workflows/deploy.yml` builds and
60-deploys it to GitHub Pages. The `demo/content/advanced/` folder holds a
61-systematic seven-notebook tour of the language (data types, matrices,
62-control flow, linear algebra, data structures, numerical methods, and
63-plotting).
60+deploys it to GitHub Pages. `demo/content/` is a numbered walkthrough: an
61+intro (with plotting), a systematic language tour (data types, matrices,
62+control flow, linear algebra, data structures, numerical methods,
63+plotting), the advanced MATLAB object model (classes and OOP, namespaces
64+and packages), and finally installing packages with `mip`. The class and
65+package `.m` files live next to the notebooks (e.g. `Vec2.m`, `+geom/`,
66+`@Poly/`) and are synced into the session recursively.
6467
6568 ### Always-fresh content (demo choice)
6669
demo/content/+geom/+util/deg2rad.madded+3−0View file
@@ -0,0 +1,3 @@
1+function r = deg2rad(d)
2+ r = d * pi / 180;
3+end
demo/content/+geom/Point.madded+15−0View file
@@ -0,0 +1,15 @@
1+classdef Point
2+ % A class that lives inside the +geom package: geom.Point(...).
3+ properties
4+ x = 0
5+ y = 0
6+ end
7+ methods
8+ function obj = Point(x, y)
9+ if nargin > 0, obj.x = x; obj.y = y; end
10+ end
11+ function d = dist(obj)
12+ d = sqrt(obj.x^2 + obj.y^2);
13+ end
14+ end
15+end
demo/content/+geom/circle_area.madded+3−0View file
@@ -0,0 +1,3 @@
1+function a = circle_area(r)
2+ a = pi * r^2;
3+end
demo/content/01-intro.ipynbmodified+23−51View file
@@ -2,86 +2,57 @@
22 "cells": [
33 {
44 "cell_type": "markdown",
5- "id": "intro-title",
5+ "id": "in-0",
66 "metadata": {},
7- "source": "# numbl: MATLAB syntax, entirely in your browser\n\nThis notebook runs on the **numbl kernel for JupyterLite**. Every cell executes\nin a Web Worker in *your* browser tab, with no server and no kernel process\nbehind this page, and nothing to install.\n\n[numbl](https://numbl.org) is an open-source numerical-computing engine\nwritten in TypeScript. It uses MATLAB syntax, so `.m` code runs unchanged.\n\nRun the cells below with **Shift+Enter**."
7+ "source": "# numbl in the browser\n\nThis is **numbl** running as a JupyterLite kernel: MATLAB-syntax numerical\ncomputing that executes entirely in your browser tab. There is no server, no\nkernel process, and nothing to install.\n\nRun each cell with **Shift+Enter**. Variables persist from one cell to the\nnext, just like a MATLAB session."
88 },
99 {
10- "cell_type": "code",
11- "execution_count": null,
12- "id": "intro-matrix",
10+ "cell_type": "markdown",
11+ "id": "in-1",
1312 "metadata": {},
14- "outputs": [],
15- "source": [
16- "A = [4 2 1; 2 5 3; 1 3 6]"
17- ]
13+ "source": "## Compute with matrices"
1814 },
1915 {
2016 "cell_type": "code",
21- "execution_count": null,
22- "id": "intro-solve",
17+ "id": "in-2",
2318 "metadata": {},
24- "outputs": [],
25- "source": [
26- "% Variables persist across cells; solve a linear system with backslash\n",
27- "b = [1; 2; 3];\n",
28- "x = A \\ b\n",
29- "residual = norm(A*x - b)"
30- ]
31- },
32- {
33- "cell_type": "code",
3419 "execution_count": null,
35- "id": "intro-indexing",
36- "metadata": {},
3720 "outputs": [],
38- "source": "% MATLAB-style indexing: rows, and logical masks\nA(2, :)\nA(A > 3)'"
21+ "source": "A = magic(4)\nb = (1:4)';\nx = A \\ b;\nfprintf('solved a 4x4 system, residual = %.2e\\n', norm(A * x - b));"
3922 },
4023 {
41- "cell_type": "code",
42- "execution_count": null,
43- "id": "intro-loop",
24+ "cell_type": "markdown",
25+ "id": "in-3",
4426 "metadata": {},
45- "outputs": [],
46- "source": [
47- "total = 0;\n",
48- "for k = 1:10\n",
49- " total = total + k^2;\n",
50- "end\n",
51- "fprintf('sum of squares 1..10 = %d\\n', total);"
52- ]
27+ "source": "## Plot, right in the notebook"
5328 },
5429 {
5530 "cell_type": "code",
56- "execution_count": null,
57- "id": "intro-anon",
31+ "id": "in-4",
5832 "metadata": {},
33+ "execution_count": null,
5934 "outputs": [],
60- "source": [
61- "% Anonymous functions work in cells (named functions belong in .m files)\n",
62- "f = @(t) exp(-t) .* cos(2*pi*t);\n",
63- "vals = arrayfun(f, 0:0.5:2)"
64- ]
35+ "source": "x = linspace(0, 2 * pi, 400);\nplot(x, sin(x), 'b-');\nhold on;\nplot(x, sin(2 * x), 'r--');\nhold off;\nlegend('sin(x)', 'sin(2x)');\ntitle('plotting works client-side');\nxlabel('x'); ylabel('y');"
6536 },
6637 {
6738 "cell_type": "markdown",
68- "id": "1e814d6c",
69- "source": "## Named functions live in `.m` files\n\nnumbl's cells work like the MATLAB console: you can't define a *named*\nfunction directly in a cell. Instead, put it in a `.m` file next to the\nnotebook. This kernel syncs `.m` files from the file browser into the\nsession before every cell runs, so functions defined there are callable\nimmediately, and edits take effect the next time you run a cell.\n\nThis notebook ships with [statsutils.m](./statsutils.m); open it and try\nediting it (e.g. add a `median` field) while the cell below is still there.",
70- "metadata": {}
39+ "id": "in-5",
40+ "metadata": {},
41+ "source": "## 3-D figures are interactive (drag to rotate)"
7142 },
7243 {
7344 "cell_type": "code",
74- "id": "55d6bb19",
75- "source": "data = [2 4 4 4 5 5 7 9];\ns = statsutils(data);\nfprintf('mean=%.4f std=%.4f range=%.4f\\n', s.mean, s.std, s.range);",
45+ "id": "in-6",
7646 "metadata": {},
7747 "execution_count": null,
78- "outputs": []
48+ "outputs": [],
49+ "source": "surf(peaks(40));\ntitle('peaks');"
7950 },
8051 {
8152 "cell_type": "markdown",
82- "id": "intro-next",
53+ "id": "in-7",
8354 "metadata": {},
84- "source": "Next: [plotting](./02-plotting.ipynb) and\n[installing packages](./03-packages.ipynb), both also fully client-side.\n\nFor a systematic tour of the language, see the **`advanced/`** folder:\n[data types and operators](./advanced/01-data-types-and-operators.ipynb),\n[matrices and indexing](./advanced/02-matrices-and-indexing.ipynb),\n[control flow and functions](./advanced/03-control-flow-and-functions.ipynb),\n[linear algebra](./advanced/04-linear-algebra.ipynb),\n[data structures and strings](./advanced/05-data-structures-and-strings.ipynb),\n[numerical methods](./advanced/06-numerical-methods.ipynb), and the\n[plotting gallery](./advanced/07-plotting-gallery.ipynb)."
55+ "source": "## What's in this demo\n\nA guided tour of the language:\n\n1. [Data types and operators](./02-data-types-and-operators.ipynb)\n2. [Matrices and indexing](./03-matrices-and-indexing.ipynb)\n3. [Control flow and functions](./04-control-flow-and-functions.ipynb)\n4. [Linear algebra](./05-linear-algebra.ipynb)\n5. [Data structures and strings](./06-data-structures-and-strings.ipynb)\n6. [Numerical methods](./07-numerical-methods.ipynb)\n7. [Plotting gallery](./08-plotting.ipynb)\n\nAdvanced MATLAB features:\n\n8. [Classes and object-oriented programming](./09-classes-and-oop.ipynb)\n9. [Namespaces and packages](./10-namespaces-and-packages.ipynb)\n\nAnd finally:\n\n10. [Installing packages with mip](./11-mip-packages.ipynb)"
8556 }
8657 ],
8758 "metadata": {
@@ -95,9 +66,10 @@
9566 "file_extension": ".m",
9667 "mimetype": "text/x-octave",
9768 "name": "numbl",
69+ "nbconvert_exporter": "script",
9870 "pygments_lexer": "matlab"
9971 }
10072 },
10173 "nbformat": 4,
10274 "nbformat_minor": 5
103-}
\ No newline at end of file
75+}
demo/content/advanced/01-data-types-and-operators.ipynb →demo/content/02-data-types-and-operators.ipynbrenamed+10−24View file
@@ -4,7 +4,7 @@
44 "cell_type": "markdown",
55 "id": "dt-0",
66 "metadata": {},
7- "source": "# Data types and operators\n\nA tour of numbl's scalar types and operators. numbl uses MATLAB syntax, so\neverything here matches what you would type in MATLAB. Run each cell with\n**Shift+Enter**; unsuppressed expressions print their value."
7+ "source": "# Data types and operators\n\nA tour of numbl's scalar types and operators."
88 },
99 {
1010 "cell_type": "markdown",
@@ -24,25 +24,11 @@
2424 "cell_type": "markdown",
2525 "id": "dt-3",
2626 "metadata": {},
27- "source": "## Integer types saturate"
28- },
29- {
30- "cell_type": "code",
31- "id": "dt-4",
32- "metadata": {},
33- "execution_count": null,
34- "outputs": [],
35- "source": "a = int8(200) % clamps to 127\nb = uint8(-5) % clamps to 0\nclass(a)\ndouble(a) + 0.5"
36- },
37- {
38- "cell_type": "markdown",
39- "id": "dt-5",
40- "metadata": {},
4127 "source": "## Logical values and comparisons"
4228 },
4329 {
4430 "cell_type": "code",
45- "id": "dt-6",
31+ "id": "dt-4",
4632 "metadata": {},
4733 "execution_count": null,
4834 "outputs": [],
@@ -50,7 +36,7 @@
5036 },
5137 {
5238 "cell_type": "code",
53- "id": "dt-7",
39+ "id": "dt-5",
5440 "metadata": {},
5541 "execution_count": null,
5642 "outputs": [],
@@ -58,13 +44,13 @@
5844 },
5945 {
6046 "cell_type": "markdown",
61- "id": "dt-8",
47+ "id": "dt-6",
6248 "metadata": {},
6349 "source": "## Complex numbers"
6450 },
6551 {
6652 "cell_type": "code",
67- "id": "dt-9",
53+ "id": "dt-7",
6854 "metadata": {},
6955 "execution_count": null,
7056 "outputs": [],
@@ -72,21 +58,21 @@
7258 },
7359 {
7460 "cell_type": "markdown",
75- "id": "dt-10",
61+ "id": "dt-8",
7662 "metadata": {},
77- "source": "## Special values and rounding"
63+ "source": "## Constants, special values, and rounding"
7864 },
7965 {
8066 "cell_type": "code",
81- "id": "dt-11",
67+ "id": "dt-9",
8268 "metadata": {},
8369 "execution_count": null,
8470 "outputs": [],
85- "source": "[1/0, -1/0, 0/0]\n[isnan(0/0), isinf(1/0)]\n[pi, eps, Inf]"
71+ "source": "[pi, exp(1), eps]\n[1/0, -1/0, 0/0]\n[isnan(0/0), isinf(1/0)]"
8672 },
8773 {
8874 "cell_type": "code",
89- "id": "dt-12",
75+ "id": "dt-10",
9076 "metadata": {},
9177 "execution_count": null,
9278 "outputs": [],
demo/content/02-plotting.ipynbdeleted+0−75View file
@@ -1,75 +0,0 @@
1-{
2- "cells": [
3- {
4- "cell_type": "markdown",
5- "id": "plot-title",
6- "metadata": {},
7- "source": "# Plotting\n\nnumbl's plotting commands render through its figure renderer, straight\ninto the cell output, with no display server involved. 3-D figures are\ninteractive (drag to rotate).\n\nFigures are per-cell, like inline matplotlib: each cell renders the figures\nits own commands produce."
8- },
9- {
10- "cell_type": "code",
11- "execution_count": null,
12- "id": "plot-lines",
13- "metadata": {},
14- "outputs": [],
15- "source": [
16- "x = linspace(0, 2*pi, 200);\n",
17- "plot(x, sin(x), 'b-');\n",
18- "hold on;\n",
19- "plot(x, cos(x), 'r--');\n",
20- "hold off;\n",
21- "title('sin and cos');\n",
22- "xlabel('x');\n",
23- "legend('sin', 'cos');"
24- ]
25- },
26- {
27- "cell_type": "code",
28- "execution_count": null,
29- "id": "plot-subplot",
30- "metadata": {},
31- "outputs": [],
32- "source": [
33- "subplot(1, 2, 1); plot(x, sin(x)); title('sin(x)');\n",
34- "subplot(1, 2, 2); plot(x, sin(2*x)); title('sin(2x)');"
35- ]
36- },
37- {
38- "cell_type": "code",
39- "execution_count": null,
40- "id": "plot-imagesc",
41- "metadata": {},
42- "outputs": [],
43- "source": [
44- "Z = peaks(80);\n",
45- "imagesc(Z);\n",
46- "colorbar;\n",
47- "title('peaks');"
48- ]
49- },
50- {
51- "cell_type": "code",
52- "execution_count": null,
53- "id": "plot-surf",
54- "metadata": {},
55- "outputs": [],
56- "source": "surf(peaks(40));\ntitle('peaks surface (drag to rotate)');"
57- }
58- ],
59- "metadata": {
60- "kernelspec": {
61- "display_name": "numbl (MATLAB syntax)",
62- "language": "numbl",
63- "name": "numbl"
64- },
65- "language_info": {
66- "codemirror_mode": "octave",
67- "file_extension": ".m",
68- "mimetype": "text/x-octave",
69- "name": "numbl",
70- "pygments_lexer": "matlab"
71- }
72- },
73- "nbformat": 4,
74- "nbformat_minor": 5
75-}
\ No newline at end of file
demo/content/advanced/02-matrices-and-indexing.ipynb →demo/content/03-matrices-and-indexing.ipynbrenamed+1−1View file
@@ -4,7 +4,7 @@
44 "cell_type": "markdown",
55 "id": "mx-0",
66 "metadata": {},
7- "source": "# Matrices and indexing\n\nMatrices are the core numbl data type. This notebook covers construction,\nreshaping, and MATLAB-style indexing."
7+ "source": "# Matrices and indexing\n\nMatrices are the core numbl data type: construction, reshaping, and\nMATLAB-style indexing."
88 },
99 {
1010 "cell_type": "markdown",
demo/content/03-packages.ipynbdeleted+0−58View file
@@ -1,58 +0,0 @@
1-{
2- "cells": [
3- {
4- "cell_type": "markdown",
5- "id": "pkg-title",
6- "metadata": {},
7- "source": "# Installing packages (still no server)\n\nnumbl ships with `mip`, a package manager for numbl packages. Packages are\nfetched from GitHub releases and cached in your browser's IndexedDB, so the\ndownload happens once.\n\nThe first cell you run in a session also bootstraps the engine, so expect a\nshort delay (progress is printed)."
8- },
9- {
10- "cell_type": "code",
11- "execution_count": null,
12- "id": "pkg-install",
13- "metadata": {},
14- "outputs": [],
15- "source": [
16- "mip load --install chebfun"
17- ]
18- },
19- {
20- "cell_type": "code",
21- "execution_count": null,
22- "id": "pkg-roots",
23- "metadata": {},
24- "outputs": [],
25- "source": [
26- "% Chebfun: numerical computing with functions\n",
27- "x = chebfun('x', [0 4]);\n",
28- "f = sin(x.^2);\n",
29- "r = roots(f);\n",
30- "fprintf('sin(x^2) has %d roots in [0, 4]\\n', numel(r));"
31- ]
32- },
33- {
34- "cell_type": "code",
35- "execution_count": null,
36- "id": "pkg-plot",
37- "metadata": {},
38- "outputs": [],
39- "source": "% Evaluate the chebfun on a grid and mark its roots\nxx = linspace(0, 4, 400);\nplot(xx, f(xx));\nhold on;\nplot(r, f(r), 'ro');\nhold off;\ntitle('sin(x^2) and its roots');"
40- }
41- ],
42- "metadata": {
43- "kernelspec": {
44- "display_name": "numbl (MATLAB syntax)",
45- "language": "numbl",
46- "name": "numbl"
47- },
48- "language_info": {
49- "codemirror_mode": "octave",
50- "file_extension": ".m",
51- "mimetype": "text/x-octave",
52- "name": "numbl",
53- "pygments_lexer": "matlab"
54- }
55- },
56- "nbformat": 4,
57- "nbformat_minor": 5
58-}
\ No newline at end of file
demo/content/advanced/03-control-flow-and-functions.ipynb →demo/content/04-control-flow-and-functions.ipynbrenamed+1−1View file
@@ -74,7 +74,7 @@
7474 "cell_type": "markdown",
7575 "id": "cf-10",
7676 "metadata": {},
77- "source": "## Named functions in `.m` files\n\nThis notebook ships with [fib.m](./fib.m) and [minmax.m](./minmax.m). The\nkernel syncs `.m` files next to the notebook into the session, so you can\ncall them from cells (and edit them in the file browser)."
77+ "source": "## Named functions in `.m` files\n\nCells work like the MATLAB console, so named functions live in `.m` files\nnext to the notebook. This one ships with [fib.m](./fib.m) and\n[minmax.m](./minmax.m); the kernel syncs them into the session so you can\ncall them from cells (and edit them in the file browser)."
7878 },
7979 {
8080 "cell_type": "code",
demo/content/advanced/04-linear-algebra.ipynb →demo/content/05-linear-algebra.ipynbrenamed+1−1View file
@@ -4,7 +4,7 @@
44 "cell_type": "markdown",
55 "id": "la-0",
66 "metadata": {},
7- "source": "# Linear algebra\n\nnumbl uses LAPACK (native on the CLI, WebAssembly in the browser) for its\nmatrix factorizations."
7+ "source": "# Linear algebra\n\nnumbl uses LAPACK for its factorizations (native on the command line,\nWebAssembly in the browser)."
88 },
99 {
1010 "cell_type": "markdown",
demo/content/advanced/05-data-structures-and-strings.ipynb →demo/content/06-data-structures-and-strings.ipynbrenamed+1−1View file
@@ -4,7 +4,7 @@
44 "cell_type": "markdown",
55 "id": "ds-0",
66 "metadata": {},
7- "source": "# Data structures and strings\n\nCell arrays, structs, dictionaries, and text handling."
7+ "source": "# Data structures and strings\n\nCell arrays, structs, key/value maps, and text handling."
88 },
99 {
1010 "cell_type": "markdown",
demo/content/advanced/06-numerical-methods.ipynb →demo/content/07-numerical-methods.ipynbrenamed+0−0View file
No changes to the file's content.
demo/content/advanced/07-plotting-gallery.ipynb →demo/content/08-plotting.ipynbrenamed+0−0View file
No changes to the file's content.
demo/content/09-classes-and-oop.ipynbadded+105−0View file
@@ -0,0 +1,105 @@
1+{
2+ "cells": [
3+ {
4+ "cell_type": "markdown",
5+ "id": "oo-0",
6+ "metadata": {},
7+ "source": "# Classes and object-oriented programming\n\nnumbl supports MATLAB's `classdef` object model: value and handle classes,\nconstructors, methods, inheritance, abstract methods, operator overloading,\nand static methods. Classes are defined in `.m` files next to the notebook\n(you can open and edit them in the file browser)."
8+ },
9+ {
10+ "cell_type": "markdown",
11+ "id": "oo-1",
12+ "metadata": {},
13+ "source": "## A value class with operator overloading\n\n[Vec2.m](./Vec2.m) overloads `+`, `-`, and `*` and defines `norm`."
14+ },
15+ {
16+ "cell_type": "code",
17+ "id": "oo-2",
18+ "metadata": {},
19+ "execution_count": null,
20+ "outputs": [],
21+ "source": "a = Vec2(3, 4);\nb = Vec2(1, 2);\nfprintf('a + b = %s\\n', char(a + b));\nfprintf('a - b = %s\\n', char(a - b));\nfprintf('3 * a = %s\\n', char(3 * a));\nfprintf('norm(a) = %g\\n', norm(a));"
22+ },
23+ {
24+ "cell_type": "markdown",
25+ "id": "oo-3",
26+ "metadata": {},
27+ "source": "## A handle class has reference semantics\n\n[Account.m](./Account.m) is a `handle` class, so assigning it does not copy:\nboth names refer to the same object."
28+ },
29+ {
30+ "cell_type": "code",
31+ "id": "oo-4",
32+ "metadata": {},
33+ "execution_count": null,
34+ "outputs": [],
35+ "source": "acct = Account(100);\ndeposit(acct, 50);\nalias = acct;\ndeposit(alias, 25);\nacct.Balance % 175: updates are seen through both names"
36+ },
37+ {
38+ "cell_type": "markdown",
39+ "id": "oo-5",
40+ "metadata": {},
41+ "source": "## Inheritance, abstract methods, and polymorphism\n\n[Shape.m](./Shape.m) declares an abstract `area()` and a concrete\n`describe()`. [Circle.m](./Circle.m) and [Square.m](./Square.m) implement\n`area()`."
42+ },
43+ {
44+ "cell_type": "code",
45+ "id": "oo-6",
46+ "metadata": {},
47+ "execution_count": null,
48+ "outputs": [],
49+ "source": "shapes = {Circle(2), Square(3)};\nareas = cellfun(@(s) s.area(), shapes)\ncellfun(@(s) isa(s, 'Shape'), shapes)"
50+ },
51+ {
52+ "cell_type": "code",
53+ "id": "oo-7",
54+ "metadata": {},
55+ "execution_count": null,
56+ "outputs": [],
57+ "source": "for i = 1:numel(shapes)\n shapes{i}.describe();\nend"
58+ },
59+ {
60+ "cell_type": "markdown",
61+ "id": "oo-8",
62+ "metadata": {},
63+ "source": "## Static methods and methods across files\n\n[MathX.m](./MathX.m) has a static method (called on the class, not an\ninstance). [@Poly/](./@Poly/Poly.m) is a class whose methods live in\nseparate files in its `@Poly` folder."
64+ },
65+ {
66+ "cell_type": "code",
67+ "id": "oo-9",
68+ "metadata": {},
69+ "execution_count": null,
70+ "outputs": [],
71+ "source": "MathX.hypot3(3, 4, 12)\np = Poly([1 -3 2]); % x^2 - 3x + 2\np.evalAt(5) % method from @Poly/evalAt.m\nroots(p.coeffs)'"
72+ },
73+ {
74+ "cell_type": "markdown",
75+ "id": "oo-10",
76+ "metadata": {},
77+ "source": "## Private helper methods\n\n[Temperature.m](./Temperature.m) keeps its conversion in a\n`methods (Access = private)` block, used internally by `fahrenheit`."
78+ },
79+ {
80+ "cell_type": "code",
81+ "id": "oo-11",
82+ "metadata": {},
83+ "execution_count": null,
84+ "outputs": [],
85+ "source": "t = Temperature(100);\nt.fahrenheit()"
86+ }
87+ ],
88+ "metadata": {
89+ "kernelspec": {
90+ "display_name": "numbl (MATLAB syntax)",
91+ "language": "numbl",
92+ "name": "numbl"
93+ },
94+ "language_info": {
95+ "codemirror_mode": "octave",
96+ "file_extension": ".m",
97+ "mimetype": "text/x-octave",
98+ "name": "numbl",
99+ "nbconvert_exporter": "script",
100+ "pygments_lexer": "matlab"
101+ }
102+ },
103+ "nbformat": 4,
104+ "nbformat_minor": 5
105+}
demo/content/10-namespaces-and-packages.ipynbadded+91−0View file
@@ -0,0 +1,91 @@
1+{
2+ "cells": [
3+ {
4+ "cell_type": "markdown",
5+ "id": "ns-0",
6+ "metadata": {},
7+ "source": "# Namespaces and packages\n\nMATLAB organizes code into packages using `+folder` directories, referenced\nwith dotted names. numbl supports these, including nested packages, classes\ninside packages, and `import`."
8+ },
9+ {
10+ "cell_type": "markdown",
11+ "id": "ns-1",
12+ "metadata": {},
13+ "source": "## Calling package functions\n\n[+geom/circle_area.m](./+geom/circle_area.m) is reached as\n`geom.circle_area`. Packages nest: [+geom/+util/deg2rad.m](./+geom/+util/deg2rad.m)\nis `geom.util.deg2rad`."
14+ },
15+ {
16+ "cell_type": "code",
17+ "id": "ns-2",
18+ "metadata": {},
19+ "execution_count": null,
20+ "outputs": [],
21+ "source": "geom.circle_area(2)\ngeom.util.deg2rad(180)"
22+ },
23+ {
24+ "cell_type": "markdown",
25+ "id": "ns-3",
26+ "metadata": {},
27+ "source": "## import brings names into scope"
28+ },
29+ {
30+ "cell_type": "code",
31+ "id": "ns-4",
32+ "metadata": {},
33+ "execution_count": null,
34+ "outputs": [],
35+ "source": "import geom.util.deg2rad\ndeg2rad(90)"
36+ },
37+ {
38+ "cell_type": "code",
39+ "id": "ns-5",
40+ "metadata": {},
41+ "execution_count": null,
42+ "outputs": [],
43+ "source": "import geom.*\ncircle_area(3)"
44+ },
45+ {
46+ "cell_type": "markdown",
47+ "id": "ns-6",
48+ "metadata": {},
49+ "source": "## Classes can live inside packages\n\n[+geom/Point.m](./+geom/Point.m) is the class `geom.Point`."
50+ },
51+ {
52+ "cell_type": "code",
53+ "id": "ns-7",
54+ "metadata": {},
55+ "execution_count": null,
56+ "outputs": [],
57+ "source": "pt = geom.Point(3, 4);\npt.dist()"
58+ },
59+ {
60+ "cell_type": "markdown",
61+ "id": "ns-8",
62+ "metadata": {},
63+ "source": "## Organizing internal helpers with `private/`\n\nA `private` subfolder holds helpers used by the functions in its parent\nfolder. [toolbox/robust_center.m](./toolbox/robust_center.m) delegates to a\nhelper kept in `toolbox/private/`."
64+ },
65+ {
66+ "cell_type": "code",
67+ "id": "ns-9",
68+ "metadata": {},
69+ "execution_count": null,
70+ "outputs": [],
71+ "source": "addpath('toolbox');\nrobust_center([1 2 3 4 100]) % trims the extremes, then averages"
72+ }
73+ ],
74+ "metadata": {
75+ "kernelspec": {
76+ "display_name": "numbl (MATLAB syntax)",
77+ "language": "numbl",
78+ "name": "numbl"
79+ },
80+ "language_info": {
81+ "codemirror_mode": "octave",
82+ "file_extension": ".m",
83+ "mimetype": "text/x-octave",
84+ "name": "numbl",
85+ "nbconvert_exporter": "script",
86+ "pygments_lexer": "matlab"
87+ }
88+ },
89+ "nbformat": 4,
90+ "nbformat_minor": 5
91+}
demo/content/11-mip-packages.ipynbadded+63−0View file
@@ -0,0 +1,63 @@
1+{
2+ "cells": [
3+ {
4+ "cell_type": "markdown",
5+ "id": "mp-0",
6+ "metadata": {},
7+ "source": "# Installing packages with mip\n\nnumbl ships with `mip`, a package manager for numbl packages. Packages are\nfetched from GitHub releases and cached in your browser's IndexedDB, so the\ndownload happens once.\n\nThe first cell you run in a session also bootstraps the engine, so expect a\nshort delay (progress is printed). These cells reach the network, so they are\nnot part of the offline verification suite."
8+ },
9+ {
10+ "cell_type": "code",
11+ "id": "mp-1",
12+ "metadata": {},
13+ "execution_count": null,
14+ "outputs": [],
15+ "source": "mip load --install chebfun"
16+ },
17+ {
18+ "cell_type": "markdown",
19+ "id": "mp-2",
20+ "metadata": {},
21+ "source": "## Chebfun: numerical computing with functions"
22+ },
23+ {
24+ "cell_type": "code",
25+ "id": "mp-3",
26+ "metadata": {},
27+ "execution_count": null,
28+ "outputs": [],
29+ "source": "x = chebfun('x', [0 4]);\nf = sin(x.^2);\nr = roots(f);\nfprintf('sin(x^2) has %d roots in [0, 4]\\n', numel(r));"
30+ },
31+ {
32+ "cell_type": "markdown",
33+ "id": "mp-4",
34+ "metadata": {},
35+ "source": "## Plot the chebfun and its roots"
36+ },
37+ {
38+ "cell_type": "code",
39+ "id": "mp-5",
40+ "metadata": {},
41+ "execution_count": null,
42+ "outputs": [],
43+ "source": "xx = linspace(0, 4, 400);\nplot(xx, f(xx));\nhold on;\nplot(r, f(r), 'ro');\nhold off;\ntitle('sin(x^2) and its roots');"
44+ }
45+ ],
46+ "metadata": {
47+ "kernelspec": {
48+ "display_name": "numbl (MATLAB syntax)",
49+ "language": "numbl",
50+ "name": "numbl"
51+ },
52+ "language_info": {
53+ "codemirror_mode": "octave",
54+ "file_extension": ".m",
55+ "mimetype": "text/x-octave",
56+ "name": "numbl",
57+ "nbconvert_exporter": "script",
58+ "pygments_lexer": "matlab"
59+ }
60+ },
61+ "nbformat": 4,
62+ "nbformat_minor": 5
63+}
demo/content/@Poly/Poly.madded+12−0View file
@@ -0,0 +1,12 @@
1+classdef Poly
2+ % A class whose methods live in separate files in the @Poly/ folder.
3+ properties
4+ coeffs = []
5+ end
6+ methods
7+ function obj = Poly(c)
8+ if nargin > 0, obj.coeffs = c; end
9+ end
10+ y = evalAt(obj, x) % implemented in @Poly/evalAt.m
11+ end
12+end
demo/content/@Poly/evalAt.madded+3−0View file
@@ -0,0 +1,3 @@
1+function y = evalAt(obj, x)
2+ y = polyval(obj.coeffs, x);
3+end
demo/content/Account.madded+22−0View file
@@ -0,0 +1,22 @@
1+classdef Account < handle
2+ % A handle class: instances have reference semantics.
3+ properties
4+ Balance = 0
5+ end
6+ methods
7+ function obj = Account(initial)
8+ if nargin > 0
9+ obj.Balance = initial;
10+ end
11+ end
12+ function deposit(obj, amount)
13+ obj.Balance = obj.Balance + amount;
14+ end
15+ function withdraw(obj, amount)
16+ if amount > obj.Balance
17+ error('Account:insufficientFunds', 'Insufficient funds');
18+ end
19+ obj.Balance = obj.Balance - amount;
20+ end
21+ end
22+end
demo/content/Circle.madded+13−0View file
@@ -0,0 +1,13 @@
1+classdef Circle < Shape
2+ properties
3+ r = 1
4+ end
5+ methods
6+ function obj = Circle(r)
7+ if nargin > 0, obj.r = r; end
8+ end
9+ function a = area(obj)
10+ a = pi * obj.r^2;
11+ end
12+ end
13+end
demo/content/MathX.madded+8−0View file
@@ -0,0 +1,8 @@
1+classdef MathX
2+ % Static methods are called as Class.method(...) with no instance.
3+ methods (Static)
4+ function d = hypot3(a, b, c)
5+ d = sqrt(a^2 + b^2 + c^2);
6+ end
7+ end
8+end
demo/content/Shape.madded+11−0View file
@@ -0,0 +1,11 @@
1+classdef Shape
2+ % Abstract base class. Subclasses must implement area().
3+ methods (Abstract)
4+ a = area(obj)
5+ end
6+ methods
7+ function describe(obj)
8+ fprintf('%s has area %.3f\n', class(obj), obj.area());
9+ end
10+ end
11+end
demo/content/Square.madded+13−0View file
@@ -0,0 +1,13 @@
1+classdef Square < Shape
2+ properties
3+ s = 1
4+ end
5+ methods
6+ function obj = Square(s)
7+ if nargin > 0, obj.s = s; end
8+ end
9+ function a = area(obj)
10+ a = obj.s^2;
11+ end
12+ end
13+end
demo/content/Temperature.madded+19−0View file
@@ -0,0 +1,19 @@
1+classdef Temperature
2+ properties
3+ celsius = 0
4+ end
5+ methods
6+ function obj = Temperature(c)
7+ if nargin > 0, obj.celsius = c; end
8+ end
9+ function f = fahrenheit(obj)
10+ % Uses a private helper method to do the conversion.
11+ f = obj.toF(obj.celsius);
12+ end
13+ end
14+ methods (Access = private)
15+ function f = toF(~, c)
16+ f = c * 9 / 5 + 32;
17+ end
18+ end
19+end
demo/content/Vec2.madded+35−0View file
@@ -0,0 +1,35 @@
1+classdef Vec2
2+ % A value class with operator overloading.
3+ properties
4+ x = 0
5+ y = 0
6+ end
7+ methods
8+ function obj = Vec2(x, y)
9+ if nargin > 0
10+ obj.x = x;
11+ obj.y = y;
12+ end
13+ end
14+ function r = plus(a, b)
15+ r = Vec2(a.x + b.x, a.y + b.y);
16+ end
17+ function r = minus(a, b)
18+ r = Vec2(a.x - b.x, a.y - b.y);
19+ end
20+ function r = mtimes(a, b)
21+ % Scalar multiply, with the scalar on either side.
22+ if isa(a, 'Vec2')
23+ r = Vec2(a.x * b, a.y * b);
24+ else
25+ r = Vec2(b.x * a, b.y * a);
26+ end
27+ end
28+ function n = norm(obj)
29+ n = sqrt(obj.x^2 + obj.y^2);
30+ end
31+ function s = char(obj)
32+ s = sprintf('(%g, %g)', obj.x, obj.y);
33+ end
34+ end
35+end
demo/content/advanced/fib.m →demo/content/fib.mrenamed+0−0View file
No changes to the file's content.
demo/content/advanced/minmax.m →demo/content/minmax.mrenamed+0−0View file
No changes to the file's content.
demo/content/statsutils.mdeleted+0−8View file
@@ -1,8 +0,0 @@
1-function s = statsutils(x)
2-% Workspace helper: named functions like this live in a plain .m file next
3-% to the notebook (numbl's REPL cells can't define named functions
4-% directly). Edit this file and rerun a cell; numbl picks up the change.
5-s.mean = mean(x);
6-s.std = std(x);
7-s.range = max(x) - min(x);
8-end
demo/content/toolbox/private/trimmed_mean.madded+6−0View file
@@ -0,0 +1,6 @@
1+function m = trimmed_mean(v, k)
2+ % Internal helper, kept in a private/ subfolder.
3+ s = sort(v);
4+ s = s(1 + k:end - k);
5+ m = mean(s);
6+end
demo/content/toolbox/robust_center.madded+4−0View file
@@ -0,0 +1,4 @@
1+function m = robust_center(v)
2+ % Public entry point; delegates to a helper kept in toolbox/private.
3+ m = trimmed_mean(v, 1);
4+end
src/kernel.tsmodified+48−30View file
@@ -193,44 +193,62 @@ export class NumblKernel extends BaseKernel {
193193 }
194194
195195 /**
196- * Sync `.m` files from the notebook's directory into the session VFS.
197- * numbl rescans its working directory on every execution, so a file
198- * written here becomes callable on this same execute() call, and an
199- * edit made in the Jupyter editor takes effect the next time a cell runs.
200- * Best-effort: a contents-manager error (e.g. no browser drive mounted)
201- * just skips the sync rather than failing the cell.
196+ * Sync `.m` files from the notebook's directory (recursively) into the
197+ * session VFS, preserving the relative layout. Recursing matters for
198+ * MATLAB's folder-based constructs: `+namespace/`, `@class/`, and
199+ * `private/` folders all live in subdirectories and must reach the
200+ * session at the right paths. numbl rescans its working directory on
201+ * every execution, so a file written here is callable on this same
202+ * execute() call, and an edit in the Jupyter editor takes effect on the
203+ * next run. Best-effort: a contents-manager error (e.g. no browser drive
204+ * mounted) just skips the sync rather than failing the cell.
202205 */
203206 private async _syncWorkspaceFiles(session: NumblSession): Promise<void> {
204207 if (!this._contents) {
205208 return;
206209 }
207- const dir = this.location;
208- let listing: Contents.IModel;
209- try {
210- listing = await this._contents.get(dir, { content: true });
211- } catch {
212- return;
213- }
214- const files = Array.isArray(listing.content) ? listing.content : [];
215- for (const entry of files as Contents.IModel[]) {
216- if (entry.type !== 'file' || !entry.name.endsWith('.m')) {
217- continue;
218- }
219- if (this._syncedMTimes.get(entry.path) === entry.last_modified) {
220- continue;
221- }
210+ const root = this.location;
211+ const rootPrefix = root ? root.replace(/\/$/, '') + '/' : '';
212+
213+ const syncDir = async (dir: string): Promise<void> => {
214+ let listing: Contents.IModel;
222215 try {
223- const file = await this._contents.get(entry.path, {
224- content: true,
225- type: 'file',
226- format: 'text'
227- });
228- session.writeFile(entry.name, String(file.content));
229- this._syncedMTimes.set(entry.path, entry.last_modified);
216+ listing = await this._contents!.get(dir, { content: true });
230217 } catch {
231- // Skip this file; other workspace files still sync.
218+ return;
232219 }
233- }
220+ const entries = Array.isArray(listing.content) ? listing.content : [];
221+ for (const entry of entries as Contents.IModel[]) {
222+ if (entry.type === 'directory') {
223+ await syncDir(entry.path);
224+ continue;
225+ }
226+ if (entry.type !== 'file' || !entry.name.endsWith('.m')) {
227+ continue;
228+ }
229+ if (this._syncedMTimes.get(entry.path) === entry.last_modified) {
230+ continue;
231+ }
232+ try {
233+ const file = await this._contents!.get(entry.path, {
234+ content: true,
235+ type: 'file',
236+ format: 'text'
237+ });
238+ // Write at the path relative to the notebook directory so that
239+ // +pkg/@class/private layouts land correctly under the session root.
240+ const rel = entry.path.startsWith(rootPrefix)
241+ ? entry.path.slice(rootPrefix.length)
242+ : entry.name;
243+ session.writeFile(rel, String(file.content));
244+ this._syncedMTimes.set(entry.path, entry.last_modified);
245+ } catch {
246+ // Skip this file; other workspace files still sync.
247+ }
248+ }
249+ };
250+
251+ await syncDir(root);
234252 }
235253
236254 /**
moveopenescclose