myjs 0.0.2__tar.gz
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.
- myjs-0.0.2/LICENSE +21 -0
- myjs-0.0.2/MANIFEST.in +1 -0
- myjs-0.0.2/PKG-INFO +90 -0
- myjs-0.0.2/README.md +59 -0
- myjs-0.0.2/VERSION +1 -0
- myjs-0.0.2/pyproject.toml +65 -0
- myjs-0.0.2/setup.cfg +4 -0
- myjs-0.0.2/src/myjs/__init__.py +65 -0
- myjs-0.0.2/src/myjs/__main__.py +3 -0
- myjs-0.0.2/src/myjs/_engine.py +125 -0
- myjs-0.0.2/src/myjs/cli.py +87 -0
- myjs-0.0.2/src/myjs/ffi.py +238 -0
- myjs-0.0.2/src/myjs/host.py +568 -0
- myjs-0.0.2/src/myjs/repl.py +90 -0
- myjs-0.0.2/src/myjs.egg-info/PKG-INFO +90 -0
- myjs-0.0.2/src/myjs.egg-info/SOURCES.txt +18 -0
- myjs-0.0.2/src/myjs.egg-info/dependency_links.txt +1 -0
- myjs-0.0.2/src/myjs.egg-info/entry_points.txt +2 -0
- myjs-0.0.2/src/myjs.egg-info/requires.txt +4 -0
- myjs-0.0.2/src/myjs.egg-info/top_level.txt +1 -0
myjs-0.0.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 byteface
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
myjs-0.0.2/MANIFEST.in
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
global-exclude __pycache__ *.py[cod]
|
myjs-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: myjs
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: A JavaScript interpreter for Python -- run .js against a DOM, call native C via ffi, reach the Python ecosystem
|
|
5
|
+
Author-email: byteface <byteface@googlemail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/byteface/domonic-libs/tree/master/src/myjs
|
|
8
|
+
Project-URL: Source, https://github.com/byteface/domonic-libs
|
|
9
|
+
Project-URL: Documentation, https://github.com/byteface/domonic-libs/blob/master/docs/myjs.md
|
|
10
|
+
Project-URL: Tracker, https://github.com/byteface/domonic-libs/issues
|
|
11
|
+
Keywords: javascript,interpreter,ecmascript,acorn,ffi,ctypes,dom,repl,domonic
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: JavaScript
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Interpreters
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: domonic-libs>=0.0.2
|
|
28
|
+
Provides-Extra: gui
|
|
29
|
+
Requires-Dist: pywebview>=5.0; extra == "gui"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# myjs
|
|
33
|
+
|
|
34
|
+
A JavaScript interpreter for Python. It runs a practical subset of ECMAScript against a server-side DOM, hands scripts an `ffi` bridge to native C libraries, and lets JavaScript reach the entire Python ecosystem.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install myjs
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
myjs # start a REPL
|
|
42
|
+
myjs script.js # execute a file
|
|
43
|
+
myjs -e "6 * 7" # evaluate a snippet
|
|
44
|
+
myjs --gui app.js # run, then show the DOM the script built in a native window
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import myjs
|
|
49
|
+
|
|
50
|
+
myjs.eval("1 + 2 * 3") # -> 7
|
|
51
|
+
myjs.run("script.js")
|
|
52
|
+
|
|
53
|
+
s = myjs.Session() # an isolated global scope
|
|
54
|
+
s.eval("const x = 21;")
|
|
55
|
+
s.eval("x * 2") # -> 42
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## What a script can reach
|
|
59
|
+
|
|
60
|
+
- **The DOM** — `document`, `window`, and ~190 constructors (`URL`, `Headers`, `Event`, `DOMRect`, …). `document.createElement(...).appendChild(...)` builds a real object tree.
|
|
61
|
+
- **An event loop** — `Promise` (with `all` / `allSettled` / `race` / `any`), `async` / `await`, `setTimeout` / `setInterval`, `queueMicrotask`. Microtasks run before timers.
|
|
62
|
+
- **ES modules** — `import` / `export` from `.js` files; a bare specifier resolves to a Python module.
|
|
63
|
+
- **`ffi`** — call native C libraries through `ctypes`, no compiler:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
const libc = ffi.loadLibrary("c");
|
|
67
|
+
libc.abs.argtypes = [ffi.types.int];
|
|
68
|
+
libc.abs.restype = ffi.types.int;
|
|
69
|
+
libc.abs(-42); // 42
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- **Host bindings** — `fs`, `path`, `sh` (shell), `http` + an async `fetch`, `os` (Node-flavoured), `process`, `WebSocket` (a real RFC 6455 client), `say` / `notify` / `open`, `hash`, `atob` / `btoa`.
|
|
73
|
+
- **`py`** — `py.import("numpy")`, `py.eval(...)`, `py.list(iter)` → the whole Python package index from JavaScript.
|
|
74
|
+
|
|
75
|
+
The language layer passes a curated test262-style battery: closures, classes (extends / super / fields / getters-setters / private / static), destructuring, generators, labelled break, `async` / `await`, and the `Array` / `String` / `Object` / `Number` / `Math` / `JSON` / `RegExp` built-ins. Not covered: real prototype chains, `Proxy` / `Symbol`, `with`.
|
|
76
|
+
|
|
77
|
+
## Language embedding
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
s = myjs.Session(scope={"answer": 42})
|
|
81
|
+
s.eval("answer * 2") # -> 84
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
A JavaScript exception that escapes becomes `myjs.JSError` with `js_name`, `js_line`, and `js_trace` (the call stack).
|
|
85
|
+
|
|
86
|
+
## How it is built
|
|
87
|
+
|
|
88
|
+
`myjs` is the runtime layer on top of the [`domonic-libs`](https://pypi.org/project/domonic-libs/) `acorn` port — a faithful Python port of the [acorn](https://github.com/acornjs/acorn) parser plus a tree-walking evaluator — which in turn runs on [`domonic`](https://pypi.org/project/domonic/)'s DOM. Full reference: [docs/myjs.md](https://github.com/byteface/domonic-libs/blob/master/docs/myjs.md).
|
|
89
|
+
|
|
90
|
+
MIT licensed.
|
myjs-0.0.2/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# myjs
|
|
2
|
+
|
|
3
|
+
A JavaScript interpreter for Python. It runs a practical subset of ECMAScript against a server-side DOM, hands scripts an `ffi` bridge to native C libraries, and lets JavaScript reach the entire Python ecosystem.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install myjs
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
myjs # start a REPL
|
|
11
|
+
myjs script.js # execute a file
|
|
12
|
+
myjs -e "6 * 7" # evaluate a snippet
|
|
13
|
+
myjs --gui app.js # run, then show the DOM the script built in a native window
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
import myjs
|
|
18
|
+
|
|
19
|
+
myjs.eval("1 + 2 * 3") # -> 7
|
|
20
|
+
myjs.run("script.js")
|
|
21
|
+
|
|
22
|
+
s = myjs.Session() # an isolated global scope
|
|
23
|
+
s.eval("const x = 21;")
|
|
24
|
+
s.eval("x * 2") # -> 42
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## What a script can reach
|
|
28
|
+
|
|
29
|
+
- **The DOM** — `document`, `window`, and ~190 constructors (`URL`, `Headers`, `Event`, `DOMRect`, …). `document.createElement(...).appendChild(...)` builds a real object tree.
|
|
30
|
+
- **An event loop** — `Promise` (with `all` / `allSettled` / `race` / `any`), `async` / `await`, `setTimeout` / `setInterval`, `queueMicrotask`. Microtasks run before timers.
|
|
31
|
+
- **ES modules** — `import` / `export` from `.js` files; a bare specifier resolves to a Python module.
|
|
32
|
+
- **`ffi`** — call native C libraries through `ctypes`, no compiler:
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
const libc = ffi.loadLibrary("c");
|
|
36
|
+
libc.abs.argtypes = [ffi.types.int];
|
|
37
|
+
libc.abs.restype = ffi.types.int;
|
|
38
|
+
libc.abs(-42); // 42
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- **Host bindings** — `fs`, `path`, `sh` (shell), `http` + an async `fetch`, `os` (Node-flavoured), `process`, `WebSocket` (a real RFC 6455 client), `say` / `notify` / `open`, `hash`, `atob` / `btoa`.
|
|
42
|
+
- **`py`** — `py.import("numpy")`, `py.eval(...)`, `py.list(iter)` → the whole Python package index from JavaScript.
|
|
43
|
+
|
|
44
|
+
The language layer passes a curated test262-style battery: closures, classes (extends / super / fields / getters-setters / private / static), destructuring, generators, labelled break, `async` / `await`, and the `Array` / `String` / `Object` / `Number` / `Math` / `JSON` / `RegExp` built-ins. Not covered: real prototype chains, `Proxy` / `Symbol`, `with`.
|
|
45
|
+
|
|
46
|
+
## Language embedding
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
s = myjs.Session(scope={"answer": 42})
|
|
50
|
+
s.eval("answer * 2") # -> 84
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A JavaScript exception that escapes becomes `myjs.JSError` with `js_name`, `js_line`, and `js_trace` (the call stack).
|
|
54
|
+
|
|
55
|
+
## How it is built
|
|
56
|
+
|
|
57
|
+
`myjs` is the runtime layer on top of the [`domonic-libs`](https://pypi.org/project/domonic-libs/) `acorn` port — a faithful Python port of the [acorn](https://github.com/acornjs/acorn) parser plus a tree-walking evaluator — which in turn runs on [`domonic`](https://pypi.org/project/domonic/)'s DOM. Full reference: [docs/myjs.md](https://github.com/byteface/domonic-libs/blob/master/docs/myjs.md).
|
|
58
|
+
|
|
59
|
+
MIT licensed.
|
myjs-0.0.2/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.2
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Build config for the standalone `myjs` distribution.
|
|
2
|
+
#
|
|
3
|
+
# `src` here is a symlink to ../../src, so src/myjs is the single source of
|
|
4
|
+
# truth and this project packages only `myjs*`. Build with `make myjs-build`
|
|
5
|
+
# (or `python -m build packaging/myjs`); dev-install with `make myjs-install`.
|
|
6
|
+
# The package depends on `domonic-libs` for the acorn parser + tree-walking
|
|
7
|
+
# interpreter (`domonic_libs.acorn`) and, transitively, on `domonic`.
|
|
8
|
+
|
|
9
|
+
[build-system]
|
|
10
|
+
requires = ["setuptools>=77"]
|
|
11
|
+
build-backend = "setuptools.build_meta"
|
|
12
|
+
|
|
13
|
+
[project]
|
|
14
|
+
name = "myjs"
|
|
15
|
+
dynamic = ["version"] # shares the repo-root VERSION (symlinked here as ./VERSION)
|
|
16
|
+
description = "A JavaScript interpreter for Python -- run .js against a DOM, call native C via ffi, reach the Python ecosystem"
|
|
17
|
+
readme = "README.md"
|
|
18
|
+
license = "MIT"
|
|
19
|
+
license-files = ["LICENSE"]
|
|
20
|
+
authors = [
|
|
21
|
+
{ name = "byteface", email = "byteface@googlemail.com" },
|
|
22
|
+
]
|
|
23
|
+
requires-python = ">=3.10"
|
|
24
|
+
keywords = ["javascript", "interpreter", "ecmascript", "acorn", "ffi", "ctypes", "dom", "repl", "domonic"]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 3 - Alpha",
|
|
27
|
+
"Environment :: Console",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Programming Language :: JavaScript",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Topic :: Software Development :: Interpreters",
|
|
37
|
+
"Topic :: Software Development :: Libraries",
|
|
38
|
+
]
|
|
39
|
+
dependencies = [
|
|
40
|
+
# myjs and domonic-libs are released in lockstep from the repo-root VERSION;
|
|
41
|
+
# `make bump` keeps this floor in step.
|
|
42
|
+
"domonic-libs>=0.0.2",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
gui = ["pywebview>=5.0"]
|
|
47
|
+
|
|
48
|
+
[project.scripts]
|
|
49
|
+
myjs = "myjs.cli:main"
|
|
50
|
+
|
|
51
|
+
[project.urls]
|
|
52
|
+
Homepage = "https://github.com/byteface/domonic-libs/tree/master/src/myjs"
|
|
53
|
+
Source = "https://github.com/byteface/domonic-libs"
|
|
54
|
+
Documentation = "https://github.com/byteface/domonic-libs/blob/master/docs/myjs.md"
|
|
55
|
+
Tracker = "https://github.com/byteface/domonic-libs/issues"
|
|
56
|
+
|
|
57
|
+
[tool.setuptools]
|
|
58
|
+
package-dir = { "" = "src" }
|
|
59
|
+
|
|
60
|
+
[tool.setuptools.packages.find]
|
|
61
|
+
where = ["src"]
|
|
62
|
+
include = ["myjs*"]
|
|
63
|
+
|
|
64
|
+
[tool.setuptools.dynamic]
|
|
65
|
+
version = { file = "VERSION" }
|
myjs-0.0.2/setup.cfg
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""myjs -- a small JavaScript interpreter for Python.
|
|
2
|
+
|
|
3
|
+
It runs a practical subset of ECMAScript (via the ``domonic_libs.acorn`` port and
|
|
4
|
+
its tree-walking evaluator) against domonic's server-side DOM, and hands scripts
|
|
5
|
+
an ``ffi`` global for calling native C libraries through ``ctypes``.
|
|
6
|
+
|
|
7
|
+
import myjs
|
|
8
|
+
|
|
9
|
+
myjs.eval("window.console.log('hi from myjs')")
|
|
10
|
+
myjs.run("script.js")
|
|
11
|
+
|
|
12
|
+
s = myjs.Session()
|
|
13
|
+
s.eval("const x = 21;")
|
|
14
|
+
s.eval("x * 2") # -> 42
|
|
15
|
+
|
|
16
|
+
Command line: ``myjs`` starts a REPL, ``myjs file.js`` executes a file.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def _read_version() -> str:
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
f = Path(__file__).resolve().parents[2] / "VERSION" # repo-root source checkout
|
|
22
|
+
if f.is_file():
|
|
23
|
+
return f.read_text().strip()
|
|
24
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
25
|
+
try:
|
|
26
|
+
return version("myjs")
|
|
27
|
+
except PackageNotFoundError:
|
|
28
|
+
return "0+unknown"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__version__ = _read_version()
|
|
32
|
+
|
|
33
|
+
from ._engine import JSError, Session
|
|
34
|
+
|
|
35
|
+
__all__ = ["Session", "JSError", "eval", "run", "repl", "__version__"]
|
|
36
|
+
|
|
37
|
+
_shared: Session | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _session() -> Session:
|
|
41
|
+
global _shared
|
|
42
|
+
if _shared is None:
|
|
43
|
+
_shared = Session()
|
|
44
|
+
return _shared
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def eval(src: str, *, scope: dict | None = None):
|
|
48
|
+
"""Evaluate a string of JavaScript and return its completion value.
|
|
49
|
+
|
|
50
|
+
With ``scope`` a throwaway :class:`Session` is used; otherwise a process-wide
|
|
51
|
+
session persists definitions between calls.
|
|
52
|
+
"""
|
|
53
|
+
return (Session(scope=scope) if scope else _session()).eval(src)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def run(path: str, *, scope: dict | None = None):
|
|
57
|
+
"""Execute a ``.js`` file. Returns its completion value."""
|
|
58
|
+
return Session(scope=scope).run_file(path)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def repl(**kwargs) -> int:
|
|
62
|
+
"""Start the interactive REPL. Returns a process exit code."""
|
|
63
|
+
from .repl import repl as _repl
|
|
64
|
+
|
|
65
|
+
return _repl(**kwargs)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""The myjs session: a persistent JS interpreter over domonic's DOM + ``ffi``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from domonic_libs.acorn.interpret import (
|
|
8
|
+
UNDEFINED,
|
|
9
|
+
JSThrow,
|
|
10
|
+
_Console,
|
|
11
|
+
_stringify,
|
|
12
|
+
make_interpreter,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .ffi import scope as _ffi_scope
|
|
16
|
+
from .host import async_fetch as _async_fetch
|
|
17
|
+
from .host import scope as _host_scope
|
|
18
|
+
from .host import websocket_ctor as _websocket_ctor
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PrintConsole(_Console):
|
|
22
|
+
"""Captures like the base console, and echoes each line to stdout."""
|
|
23
|
+
|
|
24
|
+
def log(self, *a):
|
|
25
|
+
super().log(*a)
|
|
26
|
+
print(self.lines[-1])
|
|
27
|
+
|
|
28
|
+
warn = error = info = debug = trace = log
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class JSError(Exception):
|
|
32
|
+
"""A JavaScript exception (or syntax error) that escaped the script."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, message, *, name="Error", line=None, trace=None):
|
|
35
|
+
super().__init__(message)
|
|
36
|
+
self.js_name = name
|
|
37
|
+
self.js_line = line
|
|
38
|
+
self.js_trace = trace
|
|
39
|
+
|
|
40
|
+
def __str__(self):
|
|
41
|
+
base = super().__str__()
|
|
42
|
+
where = f" (line {self.js_line})" if self.js_line else ""
|
|
43
|
+
stack = f"\n at {self.js_trace}" if self.js_trace else ""
|
|
44
|
+
return f"{self.js_name}: {base}{where}{stack}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _reraise(exc):
|
|
48
|
+
val = getattr(exc, "value", None)
|
|
49
|
+
if isinstance(val, dict):
|
|
50
|
+
raise JSError(
|
|
51
|
+
val.get("message", ""),
|
|
52
|
+
name=val.get("name", "Error"),
|
|
53
|
+
line=getattr(exc, "js_line", None),
|
|
54
|
+
trace=getattr(exc, "js_trace", None),
|
|
55
|
+
) from None
|
|
56
|
+
raise JSError(
|
|
57
|
+
str(exc),
|
|
58
|
+
name=type(exc).__name__,
|
|
59
|
+
line=getattr(exc, "js_line", None),
|
|
60
|
+
trace=getattr(exc, "js_trace", None),
|
|
61
|
+
) from None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Session:
|
|
65
|
+
"""One JS global scope. Feed it source with :meth:`eval`; state persists."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, scope=None, console=None):
|
|
68
|
+
from . import __version__
|
|
69
|
+
|
|
70
|
+
extra = {**_ffi_scope(), **_host_scope(__version__)}
|
|
71
|
+
if scope:
|
|
72
|
+
extra.update(scope)
|
|
73
|
+
self.interp, self.console, self.document, self.window = make_interpreter(
|
|
74
|
+
extra_globals=extra, console=console
|
|
75
|
+
)
|
|
76
|
+
for name, value in (
|
|
77
|
+
("fetch", _async_fetch(self.interp.loop)),
|
|
78
|
+
("WebSocket", _websocket_ctor(self.interp.loop)),
|
|
79
|
+
):
|
|
80
|
+
self.interp.global_env.declare(name, value)
|
|
81
|
+
self.window._own[name] = value
|
|
82
|
+
|
|
83
|
+
def eval(self, src, ecma_version=2022):
|
|
84
|
+
"""Run ``src`` and return its completion value (``None`` for undefined)."""
|
|
85
|
+
try:
|
|
86
|
+
result = self.interp.run(src, ecma_version=ecma_version)
|
|
87
|
+
except (JSThrow, SyntaxError) as exc:
|
|
88
|
+
_reraise(exc)
|
|
89
|
+
return None if result is UNDEFINED else result
|
|
90
|
+
|
|
91
|
+
def run_file(self, path, ecma_version=2022):
|
|
92
|
+
p = Path(path).resolve()
|
|
93
|
+
self.interp.module_base = str(p.parent)
|
|
94
|
+
self.interp._cur_module["dir"] = str(p.parent)
|
|
95
|
+
return self.eval(p.read_text(), ecma_version=ecma_version)
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def console_lines(self):
|
|
99
|
+
return list(getattr(self.console, "lines", []))
|
|
100
|
+
|
|
101
|
+
def render_html(self):
|
|
102
|
+
"""Serialise the DOM the script built into a standalone HTML document."""
|
|
103
|
+
try:
|
|
104
|
+
title = self.document.title
|
|
105
|
+
except Exception:
|
|
106
|
+
title = None
|
|
107
|
+
try:
|
|
108
|
+
root = str(self.document.documentElement)
|
|
109
|
+
except Exception:
|
|
110
|
+
root = f"<html><head></head><body>{self.document.body}</body></html>"
|
|
111
|
+
if title and isinstance(title, str) and "<title>" not in root:
|
|
112
|
+
root = root.replace("<head>", f"<head><title>{title}</title>", 1)
|
|
113
|
+
return "<!doctype html>\n" + root
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def display(value):
|
|
117
|
+
"""Human-readable form of a completion value, for a REPL."""
|
|
118
|
+
if value is None or value is UNDEFINED:
|
|
119
|
+
return "undefined"
|
|
120
|
+
if isinstance(value, str):
|
|
121
|
+
return repr(value)
|
|
122
|
+
try:
|
|
123
|
+
return _stringify(value)
|
|
124
|
+
except Exception:
|
|
125
|
+
return repr(value)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""``myjs`` command line: run a script, evaluate a snippet, or start a REPL."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from . import __version__
|
|
9
|
+
from ._engine import JSError, PrintConsole, Session
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
13
|
+
p = argparse.ArgumentParser(
|
|
14
|
+
prog="myjs",
|
|
15
|
+
description="A small JavaScript interpreter on domonic's DOM, with an ffi bridge to native C.",
|
|
16
|
+
)
|
|
17
|
+
p.add_argument("script", nargs="?", help="a .js file to execute (omit for a REPL)")
|
|
18
|
+
p.add_argument("args", nargs="*", help="arguments passed to the script as globalThis.argv")
|
|
19
|
+
p.add_argument("-e", "--eval", dest="code", metavar="JS", help="evaluate a string and print the result")
|
|
20
|
+
p.add_argument("-i", "--interactive", action="store_true", help="enter the REPL after running")
|
|
21
|
+
p.add_argument("--gui", action="store_true",
|
|
22
|
+
help="after running, show the DOM the script built in a native window")
|
|
23
|
+
p.add_argument("--version", action="version", version=f"myjs {__version__}")
|
|
24
|
+
return p
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _open_gui(session: Session) -> int:
|
|
28
|
+
try:
|
|
29
|
+
import webview
|
|
30
|
+
except ImportError:
|
|
31
|
+
print("myjs --gui needs pywebview: pip install 'domonic-libs[app]'", file=sys.stderr)
|
|
32
|
+
return 1
|
|
33
|
+
try:
|
|
34
|
+
title = session.eval("document.title") or "myjs"
|
|
35
|
+
except JSError:
|
|
36
|
+
title = "myjs"
|
|
37
|
+
webview.create_window(str(title) or "myjs", html=session.render_html(),
|
|
38
|
+
width=960, height=720)
|
|
39
|
+
webview.start()
|
|
40
|
+
return 0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _report(exc: JSError) -> None:
|
|
44
|
+
print(exc, file=sys.stderr)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def main(argv=None) -> int:
|
|
48
|
+
args = build_parser().parse_args(argv)
|
|
49
|
+
|
|
50
|
+
if args.code is not None:
|
|
51
|
+
session = Session(console=PrintConsole())
|
|
52
|
+
try:
|
|
53
|
+
result = session.eval(args.code)
|
|
54
|
+
except JSError as exc:
|
|
55
|
+
_report(exc)
|
|
56
|
+
return 1
|
|
57
|
+
if result is not None:
|
|
58
|
+
print(result if isinstance(result, str) else session.display(result))
|
|
59
|
+
if args.interactive:
|
|
60
|
+
from .repl import repl
|
|
61
|
+
return repl(session)
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
if args.script:
|
|
65
|
+
session = Session(console=PrintConsole())
|
|
66
|
+
session.interp.global_env.declare("argv", list(args.args))
|
|
67
|
+
try:
|
|
68
|
+
session.run_file(args.script)
|
|
69
|
+
except FileNotFoundError:
|
|
70
|
+
print(f"myjs: cannot open {args.script}", file=sys.stderr)
|
|
71
|
+
return 1
|
|
72
|
+
except JSError as exc:
|
|
73
|
+
_report(exc)
|
|
74
|
+
return 1
|
|
75
|
+
if args.gui:
|
|
76
|
+
return _open_gui(session)
|
|
77
|
+
if args.interactive:
|
|
78
|
+
from .repl import repl
|
|
79
|
+
return repl(session)
|
|
80
|
+
return 0
|
|
81
|
+
|
|
82
|
+
from .repl import repl
|
|
83
|
+
return repl()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
raise SystemExit(main())
|