From 14905d12c1ea793135d76b68d73035ba311648dc Mon Sep 17 00:00:00 2001 From: Niklas Korz Date: Mon, 11 Sep 2023 11:04:23 +0200 Subject: [PATCH] Add Python mathdsl to web example --- apps/example/package.json | 5 +- apps/example/public/examples/example.py | 27 +- apps/example/public/examples/mathdsl.py | 14 + apps/example/src/App.svelte | 51 +- apps/example/src/py/Editor.svelte | 120 +++++ apps/example/src/py/code.ts | 6 + .../src/py/projections/EquationEditor.svelte | 82 +++ .../src/py/projections/MathProjection.svelte | 27 + .../py/projections/compileMathProjection.ts | 24 + apps/example/src/py/projections/context.ts | 7 + .../py/projections/evaluateMathProjection.ts | 24 + apps/example/src/py/projections/helpers.ts | 22 + apps/example/src/py/projections/index.ts | 12 + apps/example/src/py/projections/parser.ts | 3 + apps/example/src/py/pyodide/api.d.ts | 271 ++++++++++ apps/example/src/py/pyodide/index.ts | 18 + apps/example/src/py/pyodide/load-pyodide.d.ts | 57 ++ apps/example/src/py/pyodide/pyodide.d.ts | 65 +++ apps/example/src/py/pyodide/pyproxy.gen.d.ts | 491 ++++++++++++++++++ apps/example/src/{ => ts}/Editor.svelte | 0 apps/example/src/{ => ts}/code.ts | 3 + apps/example/src/{ => ts}/dsl/api.ts | 0 apps/example/src/{ => ts}/dsl/db.ts | 0 apps/example/src/{ => ts}/dsl/demo.ts | 0 apps/example/src/{ => ts}/dsl/index.ts | 0 apps/example/src/{ => ts}/dsl/proxy.ts | 0 .../projections/ChangeProjection.svelte | 0 .../projections/ReplaceProjection.svelte | 0 .../projections/TrimProjection.svelte | 0 .../{ => ts}/projections/changeProjection.ts | 0 .../src/{ => ts}/projections/context.ts | 0 .../example/src/{ => ts}/projections/index.ts | 0 .../src/{ => ts}/projections/logProjection.ts | 0 .../src/{ => ts}/projections/parser.ts | 0 .../{ => ts}/projections/replaceProjection.ts | 0 .../{ => ts}/projections/trimProjection.ts | 0 .../src/projections/changeProjection.ts | 6 +- package-lock.json | 47 +- 38 files changed, 1357 insertions(+), 25 deletions(-) create mode 100644 apps/example/public/examples/mathdsl.py create mode 100644 apps/example/src/py/Editor.svelte create mode 100644 apps/example/src/py/code.ts create mode 100644 apps/example/src/py/projections/EquationEditor.svelte create mode 100644 apps/example/src/py/projections/MathProjection.svelte create mode 100644 apps/example/src/py/projections/compileMathProjection.ts create mode 100644 apps/example/src/py/projections/context.ts create mode 100644 apps/example/src/py/projections/evaluateMathProjection.ts create mode 100644 apps/example/src/py/projections/helpers.ts create mode 100644 apps/example/src/py/projections/index.ts create mode 100644 apps/example/src/py/projections/parser.ts create mode 100644 apps/example/src/py/pyodide/api.d.ts create mode 100644 apps/example/src/py/pyodide/index.ts create mode 100644 apps/example/src/py/pyodide/load-pyodide.d.ts create mode 100644 apps/example/src/py/pyodide/pyodide.d.ts create mode 100644 apps/example/src/py/pyodide/pyproxy.gen.d.ts rename apps/example/src/{ => ts}/Editor.svelte (100%) rename apps/example/src/{ => ts}/code.ts (99%) rename apps/example/src/{ => ts}/dsl/api.ts (100%) rename apps/example/src/{ => ts}/dsl/db.ts (100%) rename apps/example/src/{ => ts}/dsl/demo.ts (100%) rename apps/example/src/{ => ts}/dsl/index.ts (100%) rename apps/example/src/{ => ts}/dsl/proxy.ts (100%) rename apps/example/src/{ => ts}/projections/ChangeProjection.svelte (100%) rename apps/example/src/{ => ts}/projections/ReplaceProjection.svelte (100%) rename apps/example/src/{ => ts}/projections/TrimProjection.svelte (100%) rename apps/example/src/{ => ts}/projections/changeProjection.ts (100%) rename apps/example/src/{ => ts}/projections/context.ts (100%) rename apps/example/src/{ => ts}/projections/index.ts (100%) rename apps/example/src/{ => ts}/projections/logProjection.ts (100%) rename apps/example/src/{ => ts}/projections/parser.ts (100%) rename apps/example/src/{ => ts}/projections/replaceProjection.ts (100%) rename apps/example/src/{ => ts}/projections/trimProjection.ts (100%) diff --git a/apps/example/package.json b/apps/example/package.json index 239e2412..442e8439 100644 --- a/apps/example/package.json +++ b/apps/example/package.json @@ -7,13 +7,15 @@ "prebuild": "npm run generate-dsl-declarations", "build": "vite build", "dev": "npm run prebuild && vite", - "generate-dsl-declarations": "dts-bundle-generator -o public/dsl.d.ts src/dsl/index.ts", + "generate-dsl-declarations": "dts-bundle-generator -o public/dsl.d.ts src/ts/dsl/index.ts", "lint": "svelte-check --tsconfig ./tsconfig.json", "preview": "vite preview" }, "dependencies": { "@codemirror/autocomplete": "6", "@codemirror/commands": "6", + "@codemirror/lang-python": "6", + "@codemirror/language": "6", "@codemirror/lint": "6", "@codemirror/state": "6", "@codemirror/theme-one-dark": "6", @@ -25,6 +27,7 @@ "@puredit/utils": "*", "@replit/codemirror-indentation-markers": "6", "codemirror": "6", + "mathlive": "^0.95.5", "normalize.css": "^8.0.1", "svelte-dark-mode": "^2.1.0" }, diff --git a/apps/example/public/examples/example.py b/apps/example/public/examples/example.py index ee500b44..a3254c3a 100644 --- a/apps/example/public/examples/example.py +++ b/apps/example/public/examples/example.py @@ -1,19 +1,14 @@ -from dsl import db +import mathdsl -def x(input, name): - print("Hello world!") - if True: - while 2 < 1: - with db.change("rooms") as table: - table.column("lastName").replace("", "") - table.column("firstName").trim("both") +# Math term transformed into function +f, args = mathdsl.compile("20\\pi^2+\\sin x") +print("f(x):", f(x=90)) -with db.change("students") as table: - table.column("name").replace("Mister", "Mr."); - table.column("lastName").trim("right"); +# Math term with a matrix +rotation, args = mathdsl.compile("\\begin{pmatrix}\\cos\\theta & -\\sin\\theta \\\\ \\sin\\theta & \\cos\\theta\\end{pmatrix}") +print("rotation(theta):", rotation(theta=0.5)) -y = 42 -y + 10 - -if y > 42: - x = 3 +# Math term evaluated immediately, using variables from local scope +r = 5 +x = mathdsl.evaluate("r^r", locals()) +print("x:", x) diff --git a/apps/example/public/examples/mathdsl.py b/apps/example/public/examples/mathdsl.py new file mode 100644 index 00000000..bc519ffb --- /dev/null +++ b/apps/example/public/examples/mathdsl.py @@ -0,0 +1,14 @@ +import sympy +import latex2sympy2 + + +def compile(latex): + symbolic = latex2sympy2.latex2sympy(latex) + simplified = sympy.simplify(symbolic) + args = tuple(simplified.free_symbols) + return sympy.lambdify(args, simplified, "numpy"), args + + +def evaluate(latex, scope): + f, args = compile(latex) + return f(**{symbol.name: scope[symbol.name] for symbol in args}) diff --git a/apps/example/src/App.svelte b/apps/example/src/App.svelte index 4321cb36..45d74ea4 100644 --- a/apps/example/src/App.svelte +++ b/apps/example/src/App.svelte @@ -1,9 +1,32 @@
- +
    +
  • + TypeScript +
  • +
  • + Python +
  • +
+ {#if activeTab === "ts"} + + {:else if activeTab === "py"} + + {/if}
diff --git a/apps/example/src/py/Editor.svelte b/apps/example/src/py/Editor.svelte new file mode 100644 index 00000000..aa15be9d --- /dev/null +++ b/apps/example/src/py/Editor.svelte @@ -0,0 +1,120 @@ + + + + +
+ + diff --git a/apps/example/src/py/code.ts b/apps/example/src/py/code.ts new file mode 100644 index 00000000..ae5d10ea --- /dev/null +++ b/apps/example/src/py/code.ts @@ -0,0 +1,6 @@ +async function fetchText(url: string): Promise { + const resp = await fetch(url); + return resp.text(); +} + +export const example = await fetchText("/examples/example.py"); diff --git a/apps/example/src/py/projections/EquationEditor.svelte b/apps/example/src/py/projections/EquationEditor.svelte new file mode 100644 index 00000000..aee65649 --- /dev/null +++ b/apps/example/src/py/projections/EquationEditor.svelte @@ -0,0 +1,82 @@ + + + + + + diff --git a/apps/example/src/py/projections/MathProjection.svelte b/apps/example/src/py/projections/MathProjection.svelte new file mode 100644 index 00000000..cbfa281c --- /dev/null +++ b/apps/example/src/py/projections/MathProjection.svelte @@ -0,0 +1,27 @@ + + + diff --git a/apps/example/src/py/projections/compileMathProjection.ts b/apps/example/src/py/projections/compileMathProjection.ts new file mode 100644 index 00000000..e78367ac --- /dev/null +++ b/apps/example/src/py/projections/compileMathProjection.ts @@ -0,0 +1,24 @@ +import { arg, contextVariable } from "@puredit/parser"; +import { svelteProjection } from "@puredit/projections/svelte"; +import type { Projection } from "@puredit/projections/types"; +import MathProjection from "./MathProjection.svelte"; +import { pythonParser } from "./parser"; + +const dsl = contextVariable("mathdsl"); +const latex = arg("latex", "string"); + +export const [pattern, draft] = pythonParser.expressionPattern` +${dsl}.compile(${latex}) +`; + +export const widget = svelteProjection(MathProjection); + +export const compileMathProjection: Projection = { + name: "compile math", + description: + "Transforms an expression in mathematical notation into a reusable functions, using free symbols as named parameters.", + pattern, + draft, + requiredContextVariables: [], + widgets: [widget], +}; diff --git a/apps/example/src/py/projections/context.ts b/apps/example/src/py/projections/context.ts new file mode 100644 index 00000000..172822a6 --- /dev/null +++ b/apps/example/src/py/projections/context.ts @@ -0,0 +1,7 @@ +import type { Context } from "@puredit/parser"; + +export const globalContextVariables: Context = { + mathdsl: "mathdsl", +}; + +export const globalContextValues = {}; diff --git a/apps/example/src/py/projections/evaluateMathProjection.ts b/apps/example/src/py/projections/evaluateMathProjection.ts new file mode 100644 index 00000000..d80a1524 --- /dev/null +++ b/apps/example/src/py/projections/evaluateMathProjection.ts @@ -0,0 +1,24 @@ +import { arg, contextVariable } from "@puredit/parser"; +import { svelteProjection } from "@puredit/projections/svelte"; +import type { Projection } from "@puredit/projections/types"; +import MathProjection from "./MathProjection.svelte"; +import { pythonParser } from "./parser"; + +const dsl = contextVariable("mathdsl"); +const latex = arg("latex", "string"); + +export const [pattern, draft] = pythonParser.expressionPattern` +${dsl}.evaluate(${latex}, locals()) +`; + +export const widget = svelteProjection(MathProjection); + +export const evaluateMathProjection: Projection = { + name: "evaluate math", + description: + "Evaluates an expression in mathematical notation using the variables from the current local scope.", + pattern, + draft, + requiredContextVariables: [], + widgets: [widget], +}; diff --git a/apps/example/src/py/projections/helpers.ts b/apps/example/src/py/projections/helpers.ts new file mode 100644 index 00000000..e48f47d6 --- /dev/null +++ b/apps/example/src/py/projections/helpers.ts @@ -0,0 +1,22 @@ +export function arrayCodeToValue(code: string) { + if (code === "") { + return ""; + } + return listCodeToValue(code.slice(1, code.length - 1)); +} + +export function arrayValueToCode(value: string) { + return "[" + listValueToCode(value) + "]"; +} + +export function listCodeToValue(code: string) { + return code; +} + +export function listValueToCode(value: string) { + return value + .split(",") + .map((v) => v.trim()) + .filter((v) => v) + .join(", "); +} diff --git a/apps/example/src/py/projections/index.ts b/apps/example/src/py/projections/index.ts new file mode 100644 index 00000000..9639f93a --- /dev/null +++ b/apps/example/src/py/projections/index.ts @@ -0,0 +1,12 @@ +import type { ProjectionPluginConfig } from "@puredit/projections"; +import { pythonParser } from "./parser"; +import { evaluateMathProjection } from "./evaluateMathProjection"; +import { compileMathProjection } from "./compileMathProjection"; +import { globalContextValues, globalContextVariables } from "./context"; + +export const projectionPluginConfig: ProjectionPluginConfig = { + parser: pythonParser, + projections: [evaluateMathProjection, compileMathProjection], + globalContextVariables, + globalContextValues, +}; diff --git a/apps/example/src/py/projections/parser.ts b/apps/example/src/py/projections/parser.ts new file mode 100644 index 00000000..b4a4020d --- /dev/null +++ b/apps/example/src/py/projections/parser.ts @@ -0,0 +1,3 @@ +import { Parser, Target } from "@puredit/parser"; + +export const pythonParser = await Parser.load(Target.Python); diff --git a/apps/example/src/py/pyodide/api.d.ts b/apps/example/src/py/pyodide/api.d.ts new file mode 100644 index 00000000..1517956e --- /dev/null +++ b/apps/example/src/py/pyodide/api.d.ts @@ -0,0 +1,271 @@ +/* eslint-disable */ + +/** + * Runs a string of Python code from JavaScript. + * + * The last part of the string may be an expression, in which case, its value + * is returned. + * + * @param {string} code Python code to evaluate + * @param {PyProxy=} globals An optional Python dictionary to use as the globals. + * Defaults to :any:`pyodide.globals`. Uses the Python API + * :any:`pyodide.eval_code` to evaluate the code. + * @returns {Py2JsResult} The result of the Python code translated to JavaScript. See the + * documentation for :any:`pyodide.eval_code` for more info. + */ +export function runPython( + code: string, + globals?: PyProxy | undefined +): Py2JsResult; +/** + * @callback LogFn + * @param {string} msg + * @returns {void} + * @private + */ +/** + * Inspect a Python code chunk and use :js:func:`pyodide.loadPackage` to install + * any known packages that the code chunk imports. Uses the Python API + * :func:`pyodide.find\_imports` to inspect the code. + * + * For example, given the following code as input + * + * .. code-block:: python + * + * import numpy as np x = np.array([1, 2, 3]) + * + * :js:func:`loadPackagesFromImports` will call + * ``pyodide.loadPackage(['numpy'])``. + * + * @param {string} code The code to inspect. + * @param {LogFn=} messageCallback The ``messageCallback`` argument of + * :any:`pyodide.loadPackage` (optional). + * @param {LogFn=} errorCallback The ``errorCallback`` argument of + * :any:`pyodide.loadPackage` (optional). + * @async + */ +export function loadPackagesFromImports( + code: string, + messageCallback?: LogFn | undefined, + errorCallback?: LogFn | undefined +): Promise; +/** + * Runs Python code using `PyCF_ALLOW_TOP_LEVEL_AWAIT + * `_. + * + * .. admonition:: Python imports + * :class: warning + * + * Since pyodide 0.18.0, you must call :js:func:`loadPackagesFromImports` to + * import any python packages referenced via `import` statements in your code. + * This function will no longer do it for you. + * + * For example: + * + * .. code-block:: pyodide + * + * let result = await pyodide.runPythonAsync(` + * from js import fetch + * response = await fetch("./packages.json") + * packages = await response.json() + * # If final statement is an expression, its value is returned to JavaScript + * len(packages.packages.object_keys()) + * `); + * console.log(result); // 79 + * + * @param {string} code Python code to evaluate + * @param {PyProxy=} globals An optional Python dictionary to use as the globals. + * Defaults to :any:`pyodide.globals`. Uses the Python API + * :any:`pyodide.eval_code_async` to evaluate the code. + * @returns {Py2JsResult} The result of the Python code translated to JavaScript. + * @async + */ +export function runPythonAsync( + code: string, + globals?: PyProxy | undefined +): Promise; +/** + * Registers the JavaScript object ``module`` as a JavaScript module named + * ``name``. This module can then be imported from Python using the standard + * Python import system. If another module by the same name has already been + * imported, this won't have much effect unless you also delete the imported + * module from ``sys.modules``. This calls the ``pyodide_py`` API + * :func:`pyodide.register_js_module`. + * + * @param {string} name Name of the JavaScript module to add + * @param {object} module JavaScript object backing the module + */ +export function registerJsModule(name: string, module: object): void; +/** + * Tell Pyodide about Comlink. + * Necessary to enable importing Comlink proxies into Python. + */ +export function registerComlink(Comlink: any): void; +/** + * Unregisters a JavaScript module with given name that has been previously + * registered with :js:func:`pyodide.registerJsModule` or + * :func:`pyodide.register_js_module`. If a JavaScript module with that name + * does not already exist, will throw an error. Note that if the module has + * already been imported, this won't have much effect unless you also delete + * the imported module from ``sys.modules``. This calls the ``pyodide_py`` API + * :func:`pyodide.unregister_js_module`. + * + * @param {string} name Name of the JavaScript module to remove + */ +export function unregisterJsModule(name: string): void; +/** + * Convert the JavaScript object to a Python object as best as possible. + * + * This is similar to :any:`JsProxy.to_py` but for use from JavaScript. If the + * object is immutable or a :any:`PyProxy`, it will be returned unchanged. If + * the object cannot be converted into Python, it will be returned unchanged. + * + * See :ref:`type-translations-jsproxy-to-py` for more information. + * + * @param {*} obj + * @param {object} options + * @param {number=} options.depth Optional argument to limit the depth of the + * conversion. + * @returns {PyProxy} The object converted to Python. + */ +export function toPy( + obj: any, + { + depth, + }?: { + depth?: number | undefined; + } +): PyProxy; +/** + * Imports a module and returns it. + * + * .. admonition:: Warning + * :class: warning + * + * This function has a completely different behavior than the old removed pyimport function! + * + * ``pyimport`` is roughly equivalent to: + * + * .. code-block:: js + * + * pyodide.runPython(`import ${pkgname}; ${pkgname}`); + * + * except that the global namespace will not change. + * + * Example: + * + * .. code-block:: js + * + * let sysmodule = pyodide.pyimport("sys"); + * let recursionLimit = sys.getrecursionlimit(); + * + * @param {string} mod_name The name of the module to import + * @returns A PyProxy for the imported module + */ +export function pyimport(mod_name: string): any; +/** + * Unpack an archive into a target directory. + * + * @param {ArrayBuffer} buffer The archive as an ArrayBuffer (it's also fine to pass a TypedArray). + * @param {string} format The format of the archive. Should be one of the formats recognized by `shutil.unpack_archive`. + * By default the options are 'bztar', 'gztar', 'tar', 'zip', and 'wheel'. Several synonyms are accepted for each format, e.g., + * for 'gztar' any of '.gztar', '.tar.gz', '.tgz', 'tar.gz' or 'tgz' are considered to be synonyms. + * + * @param {string=} extract_dir The directory to unpack the archive into. Defaults to the working directory. + */ +export function unpackArchive( + buffer: ArrayBuffer, + format: string, + extract_dir?: string | undefined +): void; +/** + * Sets the interrupt buffer to be `interrupt_buffer`. This is only useful when + * Pyodide is used in a webworker. The buffer should be a `SharedArrayBuffer` + * shared with the main browser thread (or another worker). To request an + * interrupt, a `2` should be written into `interrupt_buffer` (2 is the posix + * constant for SIGINT). + * + * @param {TypedArray} interrupt_buffer + */ +export function setInterruptBuffer(interrupt_buffer: TypedArray): void; +/** + * Throws a KeyboardInterrupt error if a KeyboardInterrupt has been requested + * via the interrupt buffer. + * + * This can be used to enable keyboard interrupts during execution of JavaScript + * code, just as `PyErr_CheckSignals` is used to enable keyboard interrupts + * during execution of C code. + */ +export function checkInterrupt(): void; +export function makePublicAPI(): { + globals: import("./pyproxy.gen.js").PyProxy; + FS: any; + pyodide_py: import("./pyproxy.gen.js").PyProxy; + version: string; + loadPackage: typeof loadPackage; + loadPackagesFromImports: typeof loadPackagesFromImports; + loadedPackages: any; + isPyProxy: typeof isPyProxy; + runPython: typeof runPython; + runPythonAsync: typeof runPythonAsync; + registerJsModule: typeof registerJsModule; + unregisterJsModule: typeof unregisterJsModule; + setInterruptBuffer: typeof setInterruptBuffer; + checkInterrupt: typeof checkInterrupt; + toPy: typeof toPy; + pyimport: typeof pyimport; + unpackArchive: typeof unpackArchive; + registerComlink: typeof registerComlink; + PythonError: typeof PythonError; + PyBuffer: typeof PyBuffer; +}; +/** + * A JavaScript error caused by a Python exception. + * + * In order to reduce the risk of large memory leaks, the ``PythonError`` + * contains no reference to the Python exception that caused it. You can find + * the actual Python exception that caused this error as `sys.last_value + * `_. + * + * See :ref:`type-translations-errors` for more information. + * + * .. admonition:: Avoid Stack Frames + * :class: warning + * + * If you make a :any:`PyProxy` of ``sys.last_value``, you should be + * especially careful to :any:`destroy() ` it when you are + * done. You may leak a large amount of memory including the local + * variables of all the stack frames in the traceback if you don't. The + * easiest way is to only handle the exception in Python. + * + * @class + */ +export class PythonError { + /** + * The Python traceback. + * @type {string} + */ + message: string; +} +/** + * + * The Pyodide version. + * + * It can be either the exact release version (e.g. ``0.1.0``), or + * the latest release version followed by the number of commits since, and + * the git hash of the current commit (e.g. ``0.1.0-1-bd84646``). + * + * @type {string} + */ +export let version: string; +export type LogFn = (msg: string) => void; +export type Py2JsResult = import("./pyproxy.gen").Py2JsResult; +export type PyProxy = import("./pyproxy.gen").PyProxy; +export type TypedArray = import("./pyproxy.gen").TypedArray; +export type Emscripten = any; +export type FS = any; +import { loadPackage } from "./load-pyodide.js"; +import { isPyProxy } from "./pyproxy.gen.js"; +import { PyBuffer } from "./pyproxy.gen.js"; +import { loadedPackages } from "./load-pyodide.js"; +export { loadPackage, loadedPackages, isPyProxy }; diff --git a/apps/example/src/py/pyodide/index.ts b/apps/example/src/py/pyodide/index.ts new file mode 100644 index 00000000..309dc720 --- /dev/null +++ b/apps/example/src/py/pyodide/index.ts @@ -0,0 +1,18 @@ +import type * as py from "./pyodide"; + +declare global { + const loadPyodide: typeof py.loadPyodide; +} + +const indexURL = "https://cdn.jsdelivr.net/pyodide/v0.19.1/full/"; + +let pendingPyodide: ReturnType | undefined; + +export async function runPython(source: string) { + if (!pendingPyodide) { + pendingPyodide = loadPyodide({ indexURL }); + } + const pyodide = await pendingPyodide; + await pyodide.loadPackagesFromImports(source); + await pyodide.runPythonAsync(source, pyodide.toPy({})); +} diff --git a/apps/example/src/py/pyodide/load-pyodide.d.ts b/apps/example/src/py/pyodide/load-pyodide.d.ts new file mode 100644 index 00000000..c3a4047b --- /dev/null +++ b/apps/example/src/py/pyodide/load-pyodide.d.ts @@ -0,0 +1,57 @@ +/* eslint-disable */ + +/** + * @param {string} indexURL + * @private + */ +export function initializePackageIndex(indexURL: string): Promise; +export function _fetchBinaryFile( + indexURL: any, + path: any +): Promise; +/** + * @callback LogFn + * @param {string} msg + * @returns {void} + * @private + */ +/** + * Load a package or a list of packages over the network. This installs the + * package in the virtual filesystem. The package needs to be imported from + * Python before it can be used. + * + * @param {string | string[] | PyProxy} names Either a single package name or + * URL or a list of them. URLs can be absolute or relative. The URLs must have + * file name ``.js`` and there must be a file called + * ``.data`` in the same directory. The argument can be a + * ``PyProxy`` of a list, in which case the list will be converted to JavaScript + * and the ``PyProxy`` will be destroyed. + * @param {LogFn=} messageCallback A callback, called with progress messages + * (optional) + * @param {LogFn=} errorCallback A callback, called with error/warning messages + * (optional) + * @async + */ +export function loadPackage( + names: string | string[] | PyProxy, + messageCallback?: LogFn | undefined, + errorCallback?: LogFn | undefined +): Promise; +/** + * @param {string) url + * @async + * @private + */ +export let loadScript: any; +/** + * + * The list of packages that Pyodide has loaded. + * Use ``Object.keys(pyodide.loadedPackages)`` to get the list of names of + * loaded packages, and ``pyodide.loadedPackages[package_name]`` to access + * install location for a particular ``package_name``. + * + * @type {object} + */ +export let loadedPackages: object; +export type LogFn = (msg: string) => void; +export type PyProxy = any; diff --git a/apps/example/src/py/pyodide/pyodide.d.ts b/apps/example/src/py/pyodide/pyodide.d.ts new file mode 100644 index 00000000..668fa220 --- /dev/null +++ b/apps/example/src/py/pyodide/pyodide.d.ts @@ -0,0 +1,65 @@ +/* eslint-disable */ + +/** + * Load the main Pyodide wasm module and initialize it. + * + * Only one copy of Pyodide can be loaded in a given JavaScript global scope + * because Pyodide uses global variables to load packages. If an attempt is made + * to load a second copy of Pyodide, :any:`loadPyodide` will throw an error. + * (This can be fixed once `Firefox adopts support for ES6 modules in webworkers + * `_.) + * + * @param {string} config.indexURL - The URL from which Pyodide will load + * packages + * @param {string} config.homedir - The home directory which Pyodide will use inside virtual file system + * Default: /home/pyodide + * @param {boolean} config.fullStdLib - Load the full Python standard library. + * Setting this to false excludes following modules: distutils. + * Default: true + * @param {undefined | function(): string} config.stdin - Override the standard input callback. Should ask the user for one line of input. + * Default: undefined + * @param {undefined | function(string)} config.stdout - Override the standard output callback. + * Default: undefined + * @param {undefined | function(string)} config.stderr - Override the standard error output callback. + * Default: undefined + * @returns The :ref:`js-api-pyodide` module. + * @memberof globalThis + * @async + */ +export function loadPyodide(config: any): Promise<{ + globals: import("./pyproxy.gen.js").PyProxy; + FS: any; + pyodide_py: import("./pyproxy.gen.js").PyProxy; + version: string; + loadPackage: typeof loadPackage; + loadPackagesFromImports: typeof import("./api.js").loadPackagesFromImports; + loadedPackages: any; + isPyProxy: typeof import("./pyproxy.gen.js").isPyProxy; + runPython: typeof import("./api.js").runPython; + runPythonAsync: typeof import("./api.js").runPythonAsync; + registerJsModule: typeof registerJsModule; + unregisterJsModule: typeof import("./api.js").unregisterJsModule; + setInterruptBuffer: typeof import("./api.js").setInterruptBuffer; + checkInterrupt: typeof import("./api.js").checkInterrupt; + toPy: typeof import("./api.js").toPy; + pyimport: typeof import("./api.js").pyimport; + unpackArchive: typeof import("./api.js").unpackArchive; + registerComlink: typeof import("./api.js").registerComlink; + PythonError: typeof import("./api.js").PythonError; + PyBuffer: typeof import("./pyproxy.gen.js").PyBuffer; +}>; +export type PyProxy = import("./pyproxy.gen").PyProxy; +export type PyProxyWithLength = import("./pyproxy.gen").PyProxyWithLength; +export type PyProxyWithGet = import("./pyproxy.gen").PyProxyWithGet; +export type PyProxyWithSet = import("./pyproxy.gen").PyProxyWithSet; +export type PyProxyWithHas = import("./pyproxy.gen").PyProxyWithHas; +export type PyProxyIterable = import("./pyproxy.gen").PyProxyIterable; +export type PyProxyIterator = import("./pyproxy.gen").PyProxyIterator; +export type PyProxyAwaitable = import("./pyproxy.gen").PyProxyAwaitable; +export type PyProxyBuffer = import("./pyproxy.gen").PyProxyBuffer; +export type PyProxyCallable = import("./pyproxy.gen").PyProxyCallable; +export type Py2JsResult = import("./pyproxy.gen").Py2JsResult; +export type TypedArray = import("./pyproxy.gen").TypedArray; +export type PyBuffer = import("./pyproxy.gen").PyBuffer; +import { loadPackage } from "./load-pyodide.js"; +import { registerJsModule } from "./api.js"; diff --git a/apps/example/src/py/pyodide/pyproxy.gen.d.ts b/apps/example/src/py/pyodide/pyproxy.gen.d.ts new file mode 100644 index 00000000..95193837 --- /dev/null +++ b/apps/example/src/py/pyodide/pyproxy.gen.d.ts @@ -0,0 +1,491 @@ +/* eslint-disable */ + +/** + * Is the argument a :any:`PyProxy`? + * @param jsobj {any} Object to test. + * @returns {jsobj is PyProxy} Is ``jsobj`` a :any:`PyProxy`? + */ +export function isPyProxy(jsobj: any): jsobj is PyProxy; +/** + * @typedef {Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array} TypedArray; + */ +/** + * A class to allow access to a Python data buffers from JavaScript. These are + * produced by :any:`PyProxy.getBuffer` and cannot be constructed directly. + * When you are done, release it with the :any:`release ` + * method. See + * `Python buffer protocol documentation + * `_ for more information. + * + * To find the element ``x[a_1, ..., a_n]``, you could use the following code: + * + * .. code-block:: js + * + * function multiIndexToIndex(pybuff, multiIndex){ + * if(multindex.length !==pybuff.ndim){ + * throw new Error("Wrong length index"); + * } + * let idx = pybuff.offset; + * for(let i = 0; i < pybuff.ndim; i++){ + * if(multiIndex[i] < 0){ + * multiIndex[i] = pybuff.shape[i] - multiIndex[i]; + * } + * if(multiIndex[i] < 0 || multiIndex[i] >= pybuff.shape[i]){ + * throw new Error("Index out of range"); + * } + * idx += multiIndex[i] * pybuff.stride[i]; + * } + * return idx; + * } + * console.log("entry is", pybuff.data[multiIndexToIndex(pybuff, [2, 0, -1])]); + * + * .. admonition:: Contiguity + * :class: warning + * + * If the buffer is not contiguous, the ``data`` TypedArray will contain + * data that is not part of the buffer. Modifying this data may lead to + * undefined behavior. + * + * .. admonition:: Readonly buffers + * :class: warning + * + * If ``buffer.readonly`` is ``true``, you should not modify the buffer. + * Modifying a readonly buffer may lead to undefined behavior. + * + * .. admonition:: Converting between TypedArray types + * :class: warning + * + * The following naive code to change the type of a typed array does not + * work: + * + * .. code-block:: js + * + * // Incorrectly convert a TypedArray. + * // Produces a Uint16Array that points to the entire WASM memory! + * let myarray = new Uint16Array(buffer.data.buffer); + * + * Instead, if you want to convert the output TypedArray, you need to say: + * + * .. code-block:: js + * + * // Correctly convert a TypedArray. + * let myarray = new Uint16Array( + * buffer.data.buffer, + * buffer.data.byteOffset, + * buffer.data.byteLength + * ); + */ +export class PyBuffer { + /** + * The offset of the first entry of the array. For instance if our array + * is 3d, then you will find ``array[0,0,0]`` at + * ``pybuf.data[pybuf.offset]`` + * @type {number} + */ + offset: number; + /** + * If the data is readonly, you should not modify it. There is no way + * for us to enforce this, but it may cause very weird behavior. + * @type {boolean} + */ + readonly: boolean; + /** + * The format string for the buffer. See `the Python documentation on + * format strings + * `_. + * @type {string} + */ + format: string; + /** + * How large is each entry (in bytes)? + * @type {number} + */ + itemsize: number; + /** + * The number of dimensions of the buffer. If ``ndim`` is 0, the buffer + * represents a single scalar or struct. Otherwise, it represents an + * array. + * @type {number} + */ + ndim: number; + /** + * The total number of bytes the buffer takes up. This is equal to + * ``buff.data.byteLength``. + * @type {number} + */ + nbytes: number; + /** + * The shape of the buffer, that is how long it is in each dimension. + * The length will be equal to ``ndim``. For instance, a 2x3x4 array + * would have shape ``[2, 3, 4]``. + * @type {number[]} + */ + shape: number[]; + /** + * An array of of length ``ndim`` giving the number of elements to skip + * to get to a new element in each dimension. See the example definition + * of a ``multiIndexToIndex`` function above. + * @type {number[]} + */ + strides: number[]; + /** + * The actual data. A typed array of an appropriate size backed by a + * segment of the WASM memory. + * + * The ``type`` argument of :any:`PyProxy.getBuffer` + * determines which sort of ``TypedArray`` this is. By default + * :any:`PyProxy.getBuffer` will look at the format string to determine the most + * appropriate option. + * @type {TypedArray} + */ + data: TypedArray; + /** + * Is it C contiguous? + * @type {boolean} + */ + c_contiguous: boolean; + /** + * Is it Fortran contiguous? + * @type {boolean} + */ + f_contiguous: boolean; + /** + * Release the buffer. This allows the memory to be reclaimed. + */ + release(): void; + _released: boolean; +} +export type PyProxy = PyProxyClass & { + [x: string]: Py2JsResult; +}; +export type Py2JsResult = + | PyProxy + | number + | bigint + | string + | boolean + | undefined; +export type PyProxyWithLength = PyProxy & PyProxyLengthMethods; +export type PyProxyWithGet = PyProxy & PyProxyGetItemMethods; +export type PyProxyWithSet = PyProxy & PyProxySetItemMethods; +export type PyProxyWithHas = PyProxy & PyProxyContainsMethods; +export type PyProxyIterable = PyProxy & PyProxyIterableMethods; +export type PyProxyIterator = PyProxy & PyProxyIteratorMethods; +export type PyProxyAwaitable = PyProxy & Promise; +export type PyProxyCallable = PyProxyClass & { + [x: string]: Py2JsResult; +} & PyProxyCallableMethods & + ((...args: any[]) => Py2JsResult); +export type PyProxyBuffer = PyProxy & PyProxyBufferMethods; +/** + * ; + */ +export type TypedArray = + | Int8Array + | Uint8Array + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array; +/** + * @typedef {(PyProxyClass & {[x : string] : Py2JsResult})} PyProxy + * @typedef { PyProxy | number | bigint | string | boolean | undefined } Py2JsResult + */ +declare class PyProxyClass { + /** + * The name of the type of the object. + * + * Usually the value is ``"module.name"`` but for builtins or + * interpreter-defined types it is just ``"name"``. As pseudocode this is: + * + * .. code-block:: python + * + * ty = type(x) + * if ty.__module__ == 'builtins' or ty.__module__ == "__main__": + * return ty.__name__ + * else: + * ty.__module__ + "." + ty.__name__ + * + * @type {string} + */ + get type(): string; + /** + * @returns {string} + */ + toString(): string; + /** + * Destroy the ``PyProxy``. This will release the memory. Any further + * attempt to use the object will raise an error. + * + * In a browser supporting `FinalizationRegistry + * `_ + * Pyodide will automatically destroy the ``PyProxy`` when it is garbage + * collected, however there is no guarantee that the finalizer will be run + * in a timely manner so it is better to ``destroy`` the proxy explicitly. + * + * @param {string} [destroyed_msg] The error message to print if use is + * attempted after destroying. Defaults to "Object has already been + * destroyed". + */ + destroy(destroyed_msg?: string): void; + /** + * Make a new PyProxy pointing to the same Python object. + * Useful if the PyProxy is destroyed somewhere else. + * @returns {PyProxy} + */ + copy(): PyProxy; + /** + * Converts the ``PyProxy`` into a JavaScript object as best as possible. By + * default does a deep conversion, if a shallow conversion is desired, you can + * use ``proxy.toJs({depth : 1})``. See :ref:`Explicit Conversion of PyProxy + * ` for more info. + * + * @param {object} options + * @param {number} [options.depth] How many layers deep to perform the + * conversion. Defaults to infinite. + * @param {array} [options.pyproxies] If provided, ``toJs`` will store all + * PyProxies created in this list. This allows you to easily destroy all the + * PyProxies by iterating the list without having to recurse over the + * generated structure. The most common use case is to create a new empty + * list, pass the list as `pyproxies`, and then later iterate over `pyproxies` + * to destroy all of created proxies. + * @param {boolean} [options.create_pyproxies] If false, ``toJs`` will throw a + * ``ConversionError`` rather than producing a ``PyProxy``. + * @param {boolean} [options.dict_converter] A function to be called on an + * iterable of pairs ``[key, value]``. Convert this iterable of pairs to the + * desired output. For instance, ``Object.fromEntries`` would convert the dict + * to an object, ``Array.from`` converts it to an array of entries, and ``(it) => + * new Map(it)`` converts it to a ``Map`` (which is the default behavior). + * @return {any} The JavaScript object resulting from the conversion. + */ + toJs({ + depth, + pyproxies, + create_pyproxies, + dict_converter, + }?: { + depth?: number; + pyproxies?: any[]; + create_pyproxies?: boolean; + dict_converter?: boolean; + }): any; + /** + * Check whether the :any:`PyProxy.length` getter is available on this PyProxy. A + * Typescript type guard. + * @returns {this is PyProxyWithLength} + */ + supportsLength(): this is PyProxyWithLength; + /** + * Check whether the :any:`PyProxy.get` method is available on this PyProxy. A + * Typescript type guard. + * @returns {this is PyProxyWithGet} + */ + supportsGet(): this is PyProxyWithGet; + /** + * Check whether the :any:`PyProxy.set` method is available on this PyProxy. A + * Typescript type guard. + * @returns {this is PyProxyWithSet} + */ + supportsSet(): this is PyProxyWithSet; + /** + * Check whether the :any:`PyProxy.has` method is available on this PyProxy. A + * Typescript type guard. + * @returns {this is PyProxyWithHas} + */ + supportsHas(): this is PyProxyWithHas; + /** + * Check whether the PyProxy is iterable. A Typescript type guard for + * :any:`PyProxy.[Symbol.iterator]`. + * @returns {this is PyProxyIterable} + */ + isIterable(): this is PyProxyIterable; + /** + * Check whether the PyProxy is iterable. A Typescript type guard for + * :any:`PyProxy.next`. + * @returns {this is PyProxyIterator} + */ + isIterator(): this is PyProxyIterator; + /** + * Check whether the PyProxy is awaitable. A Typescript type guard, if this + * function returns true Typescript considers the PyProxy to be a ``Promise``. + * @returns {this is PyProxyAwaitable} + */ + isAwaitable(): this is PyProxyAwaitable; + /** + * Check whether the PyProxy is a buffer. A Typescript type guard for + * :any:`PyProxy.getBuffer`. + * @returns {this is PyProxyBuffer} + */ + isBuffer(): this is PyProxyBuffer; + /** + * Check whether the PyProxy is a Callable. A Typescript type guard, if this + * returns true then Typescript considers the Proxy to be callable of + * signature ``(args... : any[]) => PyProxy | number | bigint | string | + * boolean | undefined``. + * @returns {this is PyProxyCallable} + */ + isCallable(): this is PyProxyCallable; + get [Symbol.toStringTag](): string; +} +/** + * @typedef { PyProxy & PyProxyLengthMethods } PyProxyWithLength + */ +declare class PyProxyLengthMethods { + /** + * The length of the object. + * + * Present only if the proxied Python object has a ``__len__`` method. + * @returns {number} + */ + get length(): number; +} +/** + * @typedef {PyProxy & PyProxyGetItemMethods} PyProxyWithGet + */ +/** + * @interface + */ +declare class PyProxyGetItemMethods { + /** + * This translates to the Python code ``obj[key]``. + * + * Present only if the proxied Python object has a ``__getitem__`` method. + * + * @param {any} key The key to look up. + * @returns {Py2JsResult} The corresponding value. + */ + get(key: any): Py2JsResult; +} +/** + * @typedef {PyProxy & PyProxySetItemMethods} PyProxyWithSet + */ +declare class PyProxySetItemMethods { + /** + * This translates to the Python code ``obj[key] = value``. + * + * Present only if the proxied Python object has a ``__setitem__`` method. + * + * @param {any} key The key to set. + * @param {any} value The value to set it to. + */ + set(key: any, value: any): void; + /** + * This translates to the Python code ``del obj[key]``. + * + * Present only if the proxied Python object has a ``__delitem__`` method. + * + * @param {any} key The key to delete. + */ + delete(key: any): void; +} +/** + * @typedef {PyProxy & PyProxyContainsMethods} PyProxyWithHas + */ +declare class PyProxyContainsMethods { + /** + * This translates to the Python code ``key in obj``. + * + * Present only if the proxied Python object has a ``__contains__`` method. + * + * @param {*} key The key to check for. + * @returns {boolean} Is ``key`` present? + */ + has(key: any): boolean; +} +/** + * @typedef {PyProxy & PyProxyIterableMethods} PyProxyIterable + */ +declare class PyProxyIterableMethods { + /** + * This translates to the Python code ``iter(obj)``. Return an iterator + * associated to the proxy. See the documentation for `Symbol.iterator + * `_. + * + * Present only if the proxied Python object is iterable (i.e., has an + * ``__iter__`` method). + * + * This will be used implicitly by ``for(let x of proxy){}``. + * + * @returns {Iterator} An iterator for the proxied Python object. + */ + [Symbol.iterator](): Iterator; +} +/** + * @typedef {PyProxy & PyProxyIteratorMethods} PyProxyIterator + */ +declare class PyProxyIteratorMethods { + /** + * This translates to the Python code ``next(obj)``. Returns the next value + * of the generator. See the documentation for `Generator.prototype.next + * `_. + * The argument will be sent to the Python generator. + * + * This will be used implicitly by ``for(let x of proxy){}``. + * + * Present only if the proxied Python object is a generator or iterator + * (i.e., has a ``send`` or ``__next__`` method). + * + * @param {any=} [value] The value to send to the generator. The value will be + * assigned as a result of a yield expression. + * @returns {IteratorResult} An Object with two properties: ``done`` and ``value``. + * When the generator yields ``some_value``, ``next`` returns ``{done : + * false, value : some_value}``. When the generator raises a + * ``StopIteration(result_value)`` exception, ``next`` returns ``{done : + * true, value : result_value}``. + */ + next(arg?: any): IteratorResult; + [Symbol.iterator](): PyProxyIteratorMethods; +} +/** + * @typedef { PyProxy & PyProxyCallableMethods & ((...args : any[]) => Py2JsResult) } PyProxyCallable + */ +declare class PyProxyCallableMethods { + apply(jsthis: any, jsargs: any): any; + call(jsthis: any, ...jsargs: any[]): any; + /** + * Call the function with key word arguments. + * The last argument must be an object with the keyword arguments. + */ + callKwargs(...jsargs: any[]): any; + prototype: Function; +} +/** + * @typedef {PyProxy & PyProxyBufferMethods} PyProxyBuffer + */ +declare class PyProxyBufferMethods { + /** + * Get a view of the buffer data which is usable from JavaScript. No copy is + * ever performed. + * + * Present only if the proxied Python object supports the `Python Buffer + * Protocol `_. + * + * We do not support suboffsets, if the buffer requires suboffsets we will + * throw an error. JavaScript nd array libraries can't handle suboffsets + * anyways. In this case, you should use the :any:`toJs` api or copy the + * buffer to one that doesn't use suboffets (using e.g., + * `numpy.ascontiguousarray + * `_). + * + * If the buffer stores big endian data or half floats, this function will + * fail without an explicit type argument. For big endian data you can use + * ``toJs``. `DataViews + * `_ + * have support for big endian data, so you might want to pass + * ``'dataview'`` as the type argument in that case. + * + * @param {string=} [type] The type of the :any:`PyBuffer.data ` field in the + * output. Should be one of: ``"i8"``, ``"u8"``, ``"u8clamped"``, ``"i16"``, + * ``"u16"``, ``"i32"``, ``"u32"``, ``"i32"``, ``"u32"``, ``"i64"``, + * ``"u64"``, ``"f32"``, ``"f64``, or ``"dataview"``. This argument is + * optional, if absent ``getBuffer`` will try to determine the appropriate + * output type based on the buffer `format string + * `_. + * @returns {PyBuffer} :any:`PyBuffer ` + */ + getBuffer(type?: string | undefined): PyBuffer; +} +export {}; diff --git a/apps/example/src/Editor.svelte b/apps/example/src/ts/Editor.svelte similarity index 100% rename from apps/example/src/Editor.svelte rename to apps/example/src/ts/Editor.svelte diff --git a/apps/example/src/code.ts b/apps/example/src/ts/code.ts similarity index 99% rename from apps/example/src/code.ts rename to apps/example/src/ts/code.ts index f95af36b..fcd462fe 100644 --- a/apps/example/src/code.ts +++ b/apps/example/src/ts/code.ts @@ -13,6 +13,7 @@ ${moduleBody} } const modules = [{ name: "dsl", wrap: true }, { name: "examples/db" }]; + export const typeDeclarations = await Promise.all( modules.map(({ name, wrap }) => fetchText(`/${name}.d.ts`).then((text) => [ @@ -21,6 +22,7 @@ export const typeDeclarations = await Promise.all( ]) ) ); + export const typeDeclarationsMap = typeDeclarations.reduce( (map: FileMap, [content, name]) => { map[`/${name}.d.ts`] = content; @@ -28,4 +30,5 @@ export const typeDeclarationsMap = typeDeclarations.reduce( }, {} ); + export const example = await fetchText("/examples/example.mts"); diff --git a/apps/example/src/dsl/api.ts b/apps/example/src/ts/dsl/api.ts similarity index 100% rename from apps/example/src/dsl/api.ts rename to apps/example/src/ts/dsl/api.ts diff --git a/apps/example/src/dsl/db.ts b/apps/example/src/ts/dsl/db.ts similarity index 100% rename from apps/example/src/dsl/db.ts rename to apps/example/src/ts/dsl/db.ts diff --git a/apps/example/src/dsl/demo.ts b/apps/example/src/ts/dsl/demo.ts similarity index 100% rename from apps/example/src/dsl/demo.ts rename to apps/example/src/ts/dsl/demo.ts diff --git a/apps/example/src/dsl/index.ts b/apps/example/src/ts/dsl/index.ts similarity index 100% rename from apps/example/src/dsl/index.ts rename to apps/example/src/ts/dsl/index.ts diff --git a/apps/example/src/dsl/proxy.ts b/apps/example/src/ts/dsl/proxy.ts similarity index 100% rename from apps/example/src/dsl/proxy.ts rename to apps/example/src/ts/dsl/proxy.ts diff --git a/apps/example/src/projections/ChangeProjection.svelte b/apps/example/src/ts/projections/ChangeProjection.svelte similarity index 100% rename from apps/example/src/projections/ChangeProjection.svelte rename to apps/example/src/ts/projections/ChangeProjection.svelte diff --git a/apps/example/src/projections/ReplaceProjection.svelte b/apps/example/src/ts/projections/ReplaceProjection.svelte similarity index 100% rename from apps/example/src/projections/ReplaceProjection.svelte rename to apps/example/src/ts/projections/ReplaceProjection.svelte diff --git a/apps/example/src/projections/TrimProjection.svelte b/apps/example/src/ts/projections/TrimProjection.svelte similarity index 100% rename from apps/example/src/projections/TrimProjection.svelte rename to apps/example/src/ts/projections/TrimProjection.svelte diff --git a/apps/example/src/projections/changeProjection.ts b/apps/example/src/ts/projections/changeProjection.ts similarity index 100% rename from apps/example/src/projections/changeProjection.ts rename to apps/example/src/ts/projections/changeProjection.ts diff --git a/apps/example/src/projections/context.ts b/apps/example/src/ts/projections/context.ts similarity index 100% rename from apps/example/src/projections/context.ts rename to apps/example/src/ts/projections/context.ts diff --git a/apps/example/src/projections/index.ts b/apps/example/src/ts/projections/index.ts similarity index 100% rename from apps/example/src/projections/index.ts rename to apps/example/src/ts/projections/index.ts diff --git a/apps/example/src/projections/logProjection.ts b/apps/example/src/ts/projections/logProjection.ts similarity index 100% rename from apps/example/src/projections/logProjection.ts rename to apps/example/src/ts/projections/logProjection.ts diff --git a/apps/example/src/projections/parser.ts b/apps/example/src/ts/projections/parser.ts similarity index 100% rename from apps/example/src/projections/parser.ts rename to apps/example/src/ts/projections/parser.ts diff --git a/apps/example/src/projections/replaceProjection.ts b/apps/example/src/ts/projections/replaceProjection.ts similarity index 100% rename from apps/example/src/projections/replaceProjection.ts rename to apps/example/src/ts/projections/replaceProjection.ts diff --git a/apps/example/src/projections/trimProjection.ts b/apps/example/src/ts/projections/trimProjection.ts similarity index 100% rename from apps/example/src/projections/trimProjection.ts rename to apps/example/src/ts/projections/trimProjection.ts diff --git a/apps/python-dsl/src/projections/changeProjection.ts b/apps/python-dsl/src/projections/changeProjection.ts index eadf9bf4..00fcf7e6 100644 --- a/apps/python-dsl/src/projections/changeProjection.ts +++ b/apps/python-dsl/src/projections/changeProjection.ts @@ -1,5 +1,6 @@ import type { Text } from "@codemirror/state"; -import { arg, block, contextVariable, Match } from "@puredit/parser"; +import { arg, block, contextVariable } from "@puredit/parser"; +import type { Match } from "@puredit/parser"; import { stringLiteralValue } from "@puredit/projections/shared"; import { svelteProjection } from "@puredit/projections/svelte"; import type { Projection } from "@puredit/projections/types"; @@ -15,9 +16,6 @@ with ${db}.change(${table}) as table: ${block({ table: "table" })} `; -console.log(pattern); -console.log(draft({})); - export const widget = svelteProjection(ChangeProjection); interface OuterContext { diff --git a/package-lock.json b/package-lock.json index d96aff5a..8b4f060c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,8 @@ "dependencies": { "@codemirror/autocomplete": "6", "@codemirror/commands": "6", + "@codemirror/lang-python": "6", + "@codemirror/language": "6", "@codemirror/lint": "6", "@codemirror/state": "6", "@codemirror/theme-one-dark": "6", @@ -58,6 +60,7 @@ "@puredit/utils": "*", "@replit/codemirror-indentation-markers": "6", "codemirror": "6", + "mathlive": "^0.95.5", "normalize.css": "^8.0.1", "svelte-dark-mode": "^2.1.0" }, @@ -80,6 +83,22 @@ "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==", "dev": true }, + "apps/example/node_modules/mathlive": { + "version": "0.95.5", + "resolved": "https://registry.npmjs.org/mathlive/-/mathlive-0.95.5.tgz", + "integrity": "sha512-FAAyx0m7hcYNIZKdmQ3/x+vk1lwSmQU30aGYRN2c5+Zz4H0z+B8+0h9fZgUN7SRvc1MmoeaOrO2iGvC2/RYDEg==", + "dependencies": { + "@cortex-js/compute-engine": "0.12.3" + }, + "engines": { + "node": ">=16.14.2", + "npm": ">=8.5.0" + }, + "funding": { + "type": "individual", + "url": "https://paypal.me/arnogourdol" + } + }, "apps/generate": { "name": "@puredit/generate", "version": "0.0.0", @@ -1069,6 +1088,19 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@cortex-js/compute-engine": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/@cortex-js/compute-engine/-/compute-engine-0.12.3.tgz", + "integrity": "sha512-LuiSWMSlgsLFcRWm5ifR8ZeE9HXWOrJ+hE6F211eVI+S+w9SQQvZhhCdUCBusyspW0+29R8lksJPH4qFFr3Xag==", + "dependencies": { + "complex.js": "^2.1.1", + "decimal.js": "^10.4.0" + }, + "engines": { + "node": ">=16.14.2", + "npm": ">=8.5.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -4543,6 +4575,18 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "node_modules/complex.js": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz", + "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, "node_modules/compute-gcd": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz", @@ -4750,8 +4794,7 @@ "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" }, "node_modules/dedent": { "version": "0.7.0",