pythonista-jscore-runtime 0.0.1__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.
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 M4nw3l
|
|
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.
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pythonista-jscore-runtime
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Pythonista JSCore Runtime Framework - Execute JavaScript and WebAssembly with seamless interop support natively in Pythonista 3.
|
|
5
|
+
Author-email: M4nw3l <63550247+M4nw3l@users.noreply.github.com>
|
|
6
|
+
Requires-Python: >= 3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: iOS
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Project-URL: Homepage, https://github.com/M4nw3l/pythonista-jscore-runtime
|
|
17
|
+
|
|
18
|
+
# pythonista-jscore-runtime
|
|
19
|
+
## JSCore Runtime Framework - Execute JavaScript and WebAssembly in Pythonista 3 natively on iOS with JavaScriptCore.
|
|
20
|
+
JSCore Runtime Framework is an experiment in pushing the boundaries of the Python environment and language features in the Pythonista 3 IDE and apps developed with it on iOS. It is an extensive Python 3 mapping of the JavaScriptCore Objective-C and C-APIs via objc-util. Implementing closely analogous Python integrations, wrapping and interop for evaluating JavaScript and WebAssembly in independent and composable JavaScriptCore execution environments from Python 3 applications and scripts. Focused also from a point of view of being a serious attempt to extend vanilla Pythonista 3 to ultimately support Python packages and modules with compiled extensions that can be cross-compiled reliably into WebAssembly.
|
|
21
|
+
|
|
22
|
+
The projects overall long term goals aim to offer three core capabilities/features:
|
|
23
|
+
- Evaluate/execute JavaScript and WebAssembly with seamless Python interop as a standalone library for Pythonista 3 based Python 3 apps.
|
|
24
|
+
- Compile, bundle, import and run custom source code and third party components extensibly with WebAssembly and JavaScript.
|
|
25
|
+
- Support Python packages/modules with extensions which can be cross-compiled to WebAssmembly from languages such as C.
|
|
26
|
+
|
|
27
|
+
A (very) simple example:
|
|
28
|
+
```python
|
|
29
|
+
|
|
30
|
+
from jscore_runtime import *
|
|
31
|
+
|
|
32
|
+
with (jscore.runtime() as runtime, runtime.context() as context):
|
|
33
|
+
context.eval('function hello_world () { return "hello world"; }')
|
|
34
|
+
print(context.js.hello_world())
|
|
35
|
+
context.js.value_from_python = ["hello", "from", "python", 1, 2.2, 3.333333, {"object":"value", "nested":{"obj":["array", [], {}]}}]
|
|
36
|
+
print(context.eval('value_from_python[2] = "javascript"; value_from_python;').value)
|
|
37
|
+
|
|
38
|
+
# output:
|
|
39
|
+
# hello world
|
|
40
|
+
# ['hello', 'from', 'javascript', 1, 2.2, 3.333333, {'object': 'value', 'nested': {'obj': ['array', [], {}]}}]
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
Download [jscore_runtime.py](jscore_runtime.py) from the repository and copy to your site-packages folder.
|
|
47
|
+
|
|
48
|
+
<!--
|
|
49
|
+
Install with pip via StaSh
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install pythonista-jscore-runtime
|
|
53
|
+
```
|
|
54
|
+
-->
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
### Javascript Runtime
|
|
58
|
+
JSCore Runtime supports both the context management and explicit create/destroy usage paradigms.
|
|
59
|
+
Alongside also automatically managed (singleton) quick/convenience evaluation and more explicit/multiple virtual machine and contexts instancing for advanced control.
|
|
60
|
+
|
|
61
|
+
A runtime singleton can be obtained from the `jscore` static class.
|
|
62
|
+
```python
|
|
63
|
+
runtime = jscore.runtime()
|
|
64
|
+
```
|
|
65
|
+
By default if no runtime class is specified a `javascript_runtime` with a virtal machine lifetime of the program is returned.
|
|
66
|
+
|
|
67
|
+
A runtime class can also be instantiated independently. On creation it will contain a pointer to its own independent JSVirtualMachine instance.
|
|
68
|
+
```python
|
|
69
|
+
runtime = javascript_runtime()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
A context is required to evaluate code. A context instance is onbtained from an existing runtime instance:
|
|
73
|
+
```python
|
|
74
|
+
context = runtime.context()
|
|
75
|
+
```
|
|
76
|
+
The context type matches the runtime type. e.g `javascript_runtime` returns `javascript_context` instances. Similarly to runtimes, contexts are independent of one another such that the state of one context is distinct to and isolated from another unless it is explictly configured for sharing via context groups.
|
|
77
|
+
|
|
78
|
+
A context may evaluate javascript with several javascript evaluation function variants:
|
|
79
|
+
```python
|
|
80
|
+
# general purpose javascript string evaluation function
|
|
81
|
+
context.eval(jsSourceCode)
|
|
82
|
+
# returns:
|
|
83
|
+
# eval_result {"value": [python js value representation] or None , "exception": exception string or None }
|
|
84
|
+
|
|
85
|
+
# module loader based javascript evaluation functions
|
|
86
|
+
# regular javascript scripts/programs loaded synchronously
|
|
87
|
+
context.eval_source(jsSourceCode)
|
|
88
|
+
context.eval_file(".path/to/js-file.js")
|
|
89
|
+
|
|
90
|
+
# javascript modules loaded asynchronously
|
|
91
|
+
context.eval_module_source(moduleSourceCode, './optional/path/to/virtal-name.js')
|
|
92
|
+
context.eval_module_file("./path/to/module/index.js")
|
|
93
|
+
|
|
94
|
+
# all return:
|
|
95
|
+
# eval_result {"value": [python js value representation] or None , "exception": exception string or None }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### context.js accessor
|
|
99
|
+
A `javascript_context` provides a `js` property which allows access to the javascript contexts global object in a 'python-esque' interface.
|
|
100
|
+
Most simple python values may be retrieved and set through this accessor. It follows JavaScript access rules, and cannot subvert them, e.g. setting a const value fails with an exception. All read/write variables may otherwise be set and manipulated.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
context.js.number = 10
|
|
104
|
+
context.js.double = 1.1
|
|
105
|
+
context.js.array = []
|
|
106
|
+
context.js.object = {}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Python functions can be specified as functions callable from javascript:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
|
|
113
|
+
def python_print(text):
|
|
114
|
+
print(text)
|
|
115
|
+
|
|
116
|
+
context.js.python_print = python_print
|
|
117
|
+
|
|
118
|
+
# call from javascript
|
|
119
|
+
context.eval('python_print("hello from javascript");')
|
|
120
|
+
# call from python as javascript_function calling back to python
|
|
121
|
+
context.js.python_print("hello via javascript")
|
|
122
|
+
|
|
123
|
+
# any callable may be specified
|
|
124
|
+
context.js.python_fn = lambda text: print(text)
|
|
125
|
+
|
|
126
|
+
# values can be returned to javascript
|
|
127
|
+
context.js.python_val = lambda: {"str": "Hello from python", "num":10, "list":[1,2,3]}
|
|
128
|
+
# and back to python
|
|
129
|
+
context.eval('python_print(python_val());')
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
A function may also be created with javascript source:
|
|
133
|
+
```python
|
|
134
|
+
context.js.my_function = javascript_function.from_source('function() { return 1234; }')
|
|
135
|
+
```
|
|
136
|
+
Defined javascript functions may also be called directly from Python:
|
|
137
|
+
```python
|
|
138
|
+
context.js.my_function() # returns 1234
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### WebAssembly Runtime
|
|
142
|
+
The `wasm_runtime` class, and its associated `wasm_context` and `wasm_module` classes allow WebAssembly modules to be loaded
|
|
143
|
+
with files and byte arrays from Python. They efficiently load WebAssembly modules via a direct buffer copy of an NSData objects bytes into a Uint8Arrays backing store in JavaScriptCore. Allowing a module and its instance to then instantiated, and its exports are bridged directly to Python. Interop with JavaScriptCore allows WebAssembly functions to be mapped and called as any other normal Python callable function. WebAssembly methods are exposed from JavaScriptCore as `function() { [Native Code] }` bodied functions. The performance should be close to excuting code natively but is still being interpreted by JavaScriptCore. It is also likely that JavaScriptCore's WebAssembly runtime may be subject to some restrictions imposed by Apple's general security policies.
|
|
144
|
+
|
|
145
|
+
To create a `wasm_context` to instantiate `wasm_module` instances from a `wasm_runtime` instance needs to be created first. This currently works the same way as the `javascript_runtime`.
|
|
146
|
+
|
|
147
|
+
A singleton runtime instance, with a lifetime of the program, may be obtained from the `jscore.runtime` accessor.
|
|
148
|
+
```python
|
|
149
|
+
runtime = jscore.runtime(wasm_runtime)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Alternatively a `wasm_runtime` instance may also be created and managed independently. Similarly to the `javascript_runtime` it will contain a pointer to a separate JSVirtualMachine instance.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
runtime = wasm_runtime()
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
A `wasm_context` may be obtained from runtime instance:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
context = runtime.context()
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Although a `wasm_context` may execute JavaScript and vice versa, `wasm_runtime` and `wasm_context` are designed to integrate Python with WebAssembly as a first class runtime, without need for any JavaScript by default, to load and call WebAssembly modules from Python.
|
|
165
|
+
|
|
166
|
+
WebAssembly modules can be loaded from files or as raw bytes with the `wasm_module` class and `wasm_context.load_module` function.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
module_file = wasm_module.from_file("./path/to/module.wasm")
|
|
170
|
+
module_bytes = wasm_module(b'\0asm\1\0\0\0'+b'[module_body_bytes]', 'optional_module_name_or_path')
|
|
171
|
+
module_data = wasm_module([0, 97, 115, 109, 1, 0, 0, 0, ...], 'optional_module_name_or_path')
|
|
172
|
+
|
|
173
|
+
module_instance = module_file
|
|
174
|
+
context.load_module(module_instance)
|
|
175
|
+
|
|
176
|
+
# once a module has been loaded its instance and exports are available from properties
|
|
177
|
+
print(module_instance.instance)
|
|
178
|
+
print(module_instance.exports)
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
Imports may be specified before loading a `wasm_module` instance obtained from a files or bytes into the context.
|
|
182
|
+
Python functions may be specified as imports directly, they will be converted automatically into a `javascript_callback` instance.
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
module = wasm_module.from_file("./path/to/module.wasm")
|
|
186
|
+
# define python functions for imports
|
|
187
|
+
def imported_func(arg):
|
|
188
|
+
pass
|
|
189
|
+
module.imports.my_namespace.imported_func = imported_func
|
|
190
|
+
|
|
191
|
+
# any callable may be specified as an import, the only requirement is parameters counts match.
|
|
192
|
+
module.imports.my_namespace.imported_func = lambda v: print(v)
|
|
193
|
+
|
|
194
|
+
# a javascript_function may also be specified as an import
|
|
195
|
+
module.imports.my_namespace.imported_func = javascript_function.from_source("function (arg) { }")
|
|
196
|
+
|
|
197
|
+
# an ImportError is raised if load_module is called for modules expecting imports without functions
|
|
198
|
+
# for all of the expected imports specified.
|
|
199
|
+
context.load_module(module)
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Previously loaded module instances may be retrieved from the context:
|
|
204
|
+
```python
|
|
205
|
+
loaded_modules = context.modules # all modules
|
|
206
|
+
|
|
207
|
+
loaded_module = context.module("module_name_or_path")
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
WebAssembly exported functions are invoked as a regular Python function using an underlying `javascript_function` instance.
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
module = wasm_module.from_file("./path/to/module.wasm")
|
|
214
|
+
context.load(module)
|
|
215
|
+
|
|
216
|
+
module.exports.exported_function()
|
|
217
|
+
module.exports.exported_function_with_parameters(convertible, python, args)
|
|
218
|
+
```
|
|
219
|
+
The following function is currently acting as the module loader on JavaScriptCore's side, it is defined in the context by `wasm_context` upon its allocation.
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
const _jscore_wasm_modules = {}
|
|
223
|
+
function _jscore_wasm_load(name, wasm_bin, namespace){
|
|
224
|
+
if(namespace === null) { namespace = {}; }
|
|
225
|
+
const wasm_module = new WebAssembly.Module(wasm_bin);
|
|
226
|
+
const wasm_instance = new WebAssembly.Instance(wasm_module, namespace);
|
|
227
|
+
const wasm_module_instance = {"instance": wasm_instance, "namespace": namespace, "module": wasm_module};
|
|
228
|
+
_jscore_wasm_modules[name] = wasm_module_instance; // ensure module remains in scope
|
|
229
|
+
return wasm_module_instance;
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
Calling `wasm_context.load_module` will call this function to create `WebAssembly.Module` and `WebAssebly.Instance` instances in JavaScript with a WebAssembly binary passed as an `Uint8Array` typed array instance and an imports namespace.
|
|
233
|
+
|
|
234
|
+
#### Example: Loading and calling Mozilla's simple.wasm
|
|
235
|
+
An end to end example of loading and using a WebAssembly from Pythonista can be demonstrated by replicating [Mozilla's Loading Wasm Modules in Javascript](https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Using_the_JavaScript_API#loading_wasm_modules_in_javascript) example.
|
|
236
|
+
|
|
237
|
+
- Firstly, download the [simple.wasm](https://raw.githubusercontent.com/mdn/webassembly-examples/master/js-api-examples/simple.wasm) module from the page.
|
|
238
|
+
- After downloading simple.wasm, the next step is to copy this into Pythonista. To do this, navigate to the simple.wasm file in your Files app, then select the file and open the sharing sheet, then tap "Run Pythonista Script" and then choose the "Import File" option.
|
|
239
|
+
- **Note: This is the only safe way to import .wasm files into Pythonista. The import function from in the Pythonista app itself does not support .wasm files and must not be used to import binary!**
|
|
240
|
+
- Create a folder for your project and copy simple.wasm inside.
|
|
241
|
+
- In your folder with the simple.wasm module create the following script:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
|
|
245
|
+
from jscore_runtime import *
|
|
246
|
+
with (jscore.runtime(wasm_runtime) as runtime, runtime.context() as context):
|
|
247
|
+
# load module file
|
|
248
|
+
module = wasm_module.from_file('./simple.wasm')
|
|
249
|
+
# define imports
|
|
250
|
+
module.imports.my_namespace.imported_func = lambda v: print(v)
|
|
251
|
+
# load module into context
|
|
252
|
+
context.load_module(module)
|
|
253
|
+
# once loaded, a modules exports become available and may be invoked
|
|
254
|
+
module.exports.exported_func() # prints 42
|
|
255
|
+
|
|
256
|
+
# output: 42
|
|
257
|
+
```
|
|
258
|
+
Modules loading has been made to closely align with javascript with a couple of notable differences, firstly Python functions/callables may be used as imports as well as javascript functions. A fixed imports table is defined per `wasm_module` and `wasm_context`, imports must therefore be specified via the `wasm_module.imports` module specific imports or `wasm_context.imports` context-wide imports properties. A modules imports always override context-wide imports.
|
|
259
|
+
|
|
260
|
+
## Known issues
|
|
261
|
+
- Loading javascript files from remote sources / cdns etc is not implemented (yet).
|
|
262
|
+
- Modules and scripts loading may not work correctly for some javascript libraries and they may need manual adjustments to work currently.
|
|
263
|
+
- ModulesLoaderDelegate is using a private protcol / api as there is no other way to access the functionality otherwise.
|
|
264
|
+
- JSScript source code strings are C++ objects which are more awkward structures to read with ctypes. A work around of separately loading a copy of the script source is used at the moment, so any module preprocessing performed when loading a JSScript is lost currently.
|
|
265
|
+
|
|
266
|
+
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# pythonista-jscore-runtime
|
|
2
|
+
## JSCore Runtime Framework - Execute JavaScript and WebAssembly in Pythonista 3 natively on iOS with JavaScriptCore.
|
|
3
|
+
JSCore Runtime Framework is an experiment in pushing the boundaries of the Python environment and language features in the Pythonista 3 IDE and apps developed with it on iOS. It is an extensive Python 3 mapping of the JavaScriptCore Objective-C and C-APIs via objc-util. Implementing closely analogous Python integrations, wrapping and interop for evaluating JavaScript and WebAssembly in independent and composable JavaScriptCore execution environments from Python 3 applications and scripts. Focused also from a point of view of being a serious attempt to extend vanilla Pythonista 3 to ultimately support Python packages and modules with compiled extensions that can be cross-compiled reliably into WebAssembly.
|
|
4
|
+
|
|
5
|
+
The projects overall long term goals aim to offer three core capabilities/features:
|
|
6
|
+
- Evaluate/execute JavaScript and WebAssembly with seamless Python interop as a standalone library for Pythonista 3 based Python 3 apps.
|
|
7
|
+
- Compile, bundle, import and run custom source code and third party components extensibly with WebAssembly and JavaScript.
|
|
8
|
+
- Support Python packages/modules with extensions which can be cross-compiled to WebAssmembly from languages such as C.
|
|
9
|
+
|
|
10
|
+
A (very) simple example:
|
|
11
|
+
```python
|
|
12
|
+
|
|
13
|
+
from jscore_runtime import *
|
|
14
|
+
|
|
15
|
+
with (jscore.runtime() as runtime, runtime.context() as context):
|
|
16
|
+
context.eval('function hello_world () { return "hello world"; }')
|
|
17
|
+
print(context.js.hello_world())
|
|
18
|
+
context.js.value_from_python = ["hello", "from", "python", 1, 2.2, 3.333333, {"object":"value", "nested":{"obj":["array", [], {}]}}]
|
|
19
|
+
print(context.eval('value_from_python[2] = "javascript"; value_from_python;').value)
|
|
20
|
+
|
|
21
|
+
# output:
|
|
22
|
+
# hello world
|
|
23
|
+
# ['hello', 'from', 'javascript', 1, 2.2, 3.333333, {'object': 'value', 'nested': {'obj': ['array', [], {}]}}]
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
Download [jscore_runtime.py](jscore_runtime.py) from the repository and copy to your site-packages folder.
|
|
30
|
+
|
|
31
|
+
<!--
|
|
32
|
+
Install with pip via StaSh
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install pythonista-jscore-runtime
|
|
36
|
+
```
|
|
37
|
+
-->
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
### Javascript Runtime
|
|
41
|
+
JSCore Runtime supports both the context management and explicit create/destroy usage paradigms.
|
|
42
|
+
Alongside also automatically managed (singleton) quick/convenience evaluation and more explicit/multiple virtual machine and contexts instancing for advanced control.
|
|
43
|
+
|
|
44
|
+
A runtime singleton can be obtained from the `jscore` static class.
|
|
45
|
+
```python
|
|
46
|
+
runtime = jscore.runtime()
|
|
47
|
+
```
|
|
48
|
+
By default if no runtime class is specified a `javascript_runtime` with a virtal machine lifetime of the program is returned.
|
|
49
|
+
|
|
50
|
+
A runtime class can also be instantiated independently. On creation it will contain a pointer to its own independent JSVirtualMachine instance.
|
|
51
|
+
```python
|
|
52
|
+
runtime = javascript_runtime()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A context is required to evaluate code. A context instance is onbtained from an existing runtime instance:
|
|
56
|
+
```python
|
|
57
|
+
context = runtime.context()
|
|
58
|
+
```
|
|
59
|
+
The context type matches the runtime type. e.g `javascript_runtime` returns `javascript_context` instances. Similarly to runtimes, contexts are independent of one another such that the state of one context is distinct to and isolated from another unless it is explictly configured for sharing via context groups.
|
|
60
|
+
|
|
61
|
+
A context may evaluate javascript with several javascript evaluation function variants:
|
|
62
|
+
```python
|
|
63
|
+
# general purpose javascript string evaluation function
|
|
64
|
+
context.eval(jsSourceCode)
|
|
65
|
+
# returns:
|
|
66
|
+
# eval_result {"value": [python js value representation] or None , "exception": exception string or None }
|
|
67
|
+
|
|
68
|
+
# module loader based javascript evaluation functions
|
|
69
|
+
# regular javascript scripts/programs loaded synchronously
|
|
70
|
+
context.eval_source(jsSourceCode)
|
|
71
|
+
context.eval_file(".path/to/js-file.js")
|
|
72
|
+
|
|
73
|
+
# javascript modules loaded asynchronously
|
|
74
|
+
context.eval_module_source(moduleSourceCode, './optional/path/to/virtal-name.js')
|
|
75
|
+
context.eval_module_file("./path/to/module/index.js")
|
|
76
|
+
|
|
77
|
+
# all return:
|
|
78
|
+
# eval_result {"value": [python js value representation] or None , "exception": exception string or None }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### context.js accessor
|
|
82
|
+
A `javascript_context` provides a `js` property which allows access to the javascript contexts global object in a 'python-esque' interface.
|
|
83
|
+
Most simple python values may be retrieved and set through this accessor. It follows JavaScript access rules, and cannot subvert them, e.g. setting a const value fails with an exception. All read/write variables may otherwise be set and manipulated.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
context.js.number = 10
|
|
87
|
+
context.js.double = 1.1
|
|
88
|
+
context.js.array = []
|
|
89
|
+
context.js.object = {}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Python functions can be specified as functions callable from javascript:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
|
|
96
|
+
def python_print(text):
|
|
97
|
+
print(text)
|
|
98
|
+
|
|
99
|
+
context.js.python_print = python_print
|
|
100
|
+
|
|
101
|
+
# call from javascript
|
|
102
|
+
context.eval('python_print("hello from javascript");')
|
|
103
|
+
# call from python as javascript_function calling back to python
|
|
104
|
+
context.js.python_print("hello via javascript")
|
|
105
|
+
|
|
106
|
+
# any callable may be specified
|
|
107
|
+
context.js.python_fn = lambda text: print(text)
|
|
108
|
+
|
|
109
|
+
# values can be returned to javascript
|
|
110
|
+
context.js.python_val = lambda: {"str": "Hello from python", "num":10, "list":[1,2,3]}
|
|
111
|
+
# and back to python
|
|
112
|
+
context.eval('python_print(python_val());')
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A function may also be created with javascript source:
|
|
116
|
+
```python
|
|
117
|
+
context.js.my_function = javascript_function.from_source('function() { return 1234; }')
|
|
118
|
+
```
|
|
119
|
+
Defined javascript functions may also be called directly from Python:
|
|
120
|
+
```python
|
|
121
|
+
context.js.my_function() # returns 1234
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### WebAssembly Runtime
|
|
125
|
+
The `wasm_runtime` class, and its associated `wasm_context` and `wasm_module` classes allow WebAssembly modules to be loaded
|
|
126
|
+
with files and byte arrays from Python. They efficiently load WebAssembly modules via a direct buffer copy of an NSData objects bytes into a Uint8Arrays backing store in JavaScriptCore. Allowing a module and its instance to then instantiated, and its exports are bridged directly to Python. Interop with JavaScriptCore allows WebAssembly functions to be mapped and called as any other normal Python callable function. WebAssembly methods are exposed from JavaScriptCore as `function() { [Native Code] }` bodied functions. The performance should be close to excuting code natively but is still being interpreted by JavaScriptCore. It is also likely that JavaScriptCore's WebAssembly runtime may be subject to some restrictions imposed by Apple's general security policies.
|
|
127
|
+
|
|
128
|
+
To create a `wasm_context` to instantiate `wasm_module` instances from a `wasm_runtime` instance needs to be created first. This currently works the same way as the `javascript_runtime`.
|
|
129
|
+
|
|
130
|
+
A singleton runtime instance, with a lifetime of the program, may be obtained from the `jscore.runtime` accessor.
|
|
131
|
+
```python
|
|
132
|
+
runtime = jscore.runtime(wasm_runtime)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Alternatively a `wasm_runtime` instance may also be created and managed independently. Similarly to the `javascript_runtime` it will contain a pointer to a separate JSVirtualMachine instance.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
runtime = wasm_runtime()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
A `wasm_context` may be obtained from runtime instance:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
context = runtime.context()
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Although a `wasm_context` may execute JavaScript and vice versa, `wasm_runtime` and `wasm_context` are designed to integrate Python with WebAssembly as a first class runtime, without need for any JavaScript by default, to load and call WebAssembly modules from Python.
|
|
148
|
+
|
|
149
|
+
WebAssembly modules can be loaded from files or as raw bytes with the `wasm_module` class and `wasm_context.load_module` function.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
module_file = wasm_module.from_file("./path/to/module.wasm")
|
|
153
|
+
module_bytes = wasm_module(b'\0asm\1\0\0\0'+b'[module_body_bytes]', 'optional_module_name_or_path')
|
|
154
|
+
module_data = wasm_module([0, 97, 115, 109, 1, 0, 0, 0, ...], 'optional_module_name_or_path')
|
|
155
|
+
|
|
156
|
+
module_instance = module_file
|
|
157
|
+
context.load_module(module_instance)
|
|
158
|
+
|
|
159
|
+
# once a module has been loaded its instance and exports are available from properties
|
|
160
|
+
print(module_instance.instance)
|
|
161
|
+
print(module_instance.exports)
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
Imports may be specified before loading a `wasm_module` instance obtained from a files or bytes into the context.
|
|
165
|
+
Python functions may be specified as imports directly, they will be converted automatically into a `javascript_callback` instance.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
module = wasm_module.from_file("./path/to/module.wasm")
|
|
169
|
+
# define python functions for imports
|
|
170
|
+
def imported_func(arg):
|
|
171
|
+
pass
|
|
172
|
+
module.imports.my_namespace.imported_func = imported_func
|
|
173
|
+
|
|
174
|
+
# any callable may be specified as an import, the only requirement is parameters counts match.
|
|
175
|
+
module.imports.my_namespace.imported_func = lambda v: print(v)
|
|
176
|
+
|
|
177
|
+
# a javascript_function may also be specified as an import
|
|
178
|
+
module.imports.my_namespace.imported_func = javascript_function.from_source("function (arg) { }")
|
|
179
|
+
|
|
180
|
+
# an ImportError is raised if load_module is called for modules expecting imports without functions
|
|
181
|
+
# for all of the expected imports specified.
|
|
182
|
+
context.load_module(module)
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Previously loaded module instances may be retrieved from the context:
|
|
187
|
+
```python
|
|
188
|
+
loaded_modules = context.modules # all modules
|
|
189
|
+
|
|
190
|
+
loaded_module = context.module("module_name_or_path")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
WebAssembly exported functions are invoked as a regular Python function using an underlying `javascript_function` instance.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
module = wasm_module.from_file("./path/to/module.wasm")
|
|
197
|
+
context.load(module)
|
|
198
|
+
|
|
199
|
+
module.exports.exported_function()
|
|
200
|
+
module.exports.exported_function_with_parameters(convertible, python, args)
|
|
201
|
+
```
|
|
202
|
+
The following function is currently acting as the module loader on JavaScriptCore's side, it is defined in the context by `wasm_context` upon its allocation.
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
const _jscore_wasm_modules = {}
|
|
206
|
+
function _jscore_wasm_load(name, wasm_bin, namespace){
|
|
207
|
+
if(namespace === null) { namespace = {}; }
|
|
208
|
+
const wasm_module = new WebAssembly.Module(wasm_bin);
|
|
209
|
+
const wasm_instance = new WebAssembly.Instance(wasm_module, namespace);
|
|
210
|
+
const wasm_module_instance = {"instance": wasm_instance, "namespace": namespace, "module": wasm_module};
|
|
211
|
+
_jscore_wasm_modules[name] = wasm_module_instance; // ensure module remains in scope
|
|
212
|
+
return wasm_module_instance;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
Calling `wasm_context.load_module` will call this function to create `WebAssembly.Module` and `WebAssebly.Instance` instances in JavaScript with a WebAssembly binary passed as an `Uint8Array` typed array instance and an imports namespace.
|
|
216
|
+
|
|
217
|
+
#### Example: Loading and calling Mozilla's simple.wasm
|
|
218
|
+
An end to end example of loading and using a WebAssembly from Pythonista can be demonstrated by replicating [Mozilla's Loading Wasm Modules in Javascript](https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Using_the_JavaScript_API#loading_wasm_modules_in_javascript) example.
|
|
219
|
+
|
|
220
|
+
- Firstly, download the [simple.wasm](https://raw.githubusercontent.com/mdn/webassembly-examples/master/js-api-examples/simple.wasm) module from the page.
|
|
221
|
+
- After downloading simple.wasm, the next step is to copy this into Pythonista. To do this, navigate to the simple.wasm file in your Files app, then select the file and open the sharing sheet, then tap "Run Pythonista Script" and then choose the "Import File" option.
|
|
222
|
+
- **Note: This is the only safe way to import .wasm files into Pythonista. The import function from in the Pythonista app itself does not support .wasm files and must not be used to import binary!**
|
|
223
|
+
- Create a folder for your project and copy simple.wasm inside.
|
|
224
|
+
- In your folder with the simple.wasm module create the following script:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
|
|
228
|
+
from jscore_runtime import *
|
|
229
|
+
with (jscore.runtime(wasm_runtime) as runtime, runtime.context() as context):
|
|
230
|
+
# load module file
|
|
231
|
+
module = wasm_module.from_file('./simple.wasm')
|
|
232
|
+
# define imports
|
|
233
|
+
module.imports.my_namespace.imported_func = lambda v: print(v)
|
|
234
|
+
# load module into context
|
|
235
|
+
context.load_module(module)
|
|
236
|
+
# once loaded, a modules exports become available and may be invoked
|
|
237
|
+
module.exports.exported_func() # prints 42
|
|
238
|
+
|
|
239
|
+
# output: 42
|
|
240
|
+
```
|
|
241
|
+
Modules loading has been made to closely align with javascript with a couple of notable differences, firstly Python functions/callables may be used as imports as well as javascript functions. A fixed imports table is defined per `wasm_module` and `wasm_context`, imports must therefore be specified via the `wasm_module.imports` module specific imports or `wasm_context.imports` context-wide imports properties. A modules imports always override context-wide imports.
|
|
242
|
+
|
|
243
|
+
## Known issues
|
|
244
|
+
- Loading javascript files from remote sources / cdns etc is not implemented (yet).
|
|
245
|
+
- Modules and scripts loading may not work correctly for some javascript libraries and they may need manual adjustments to work currently.
|
|
246
|
+
- ModulesLoaderDelegate is using a private protcol / api as there is no other way to access the functionality otherwise.
|
|
247
|
+
- JSScript source code strings are C++ objects which are more awkward structures to read with ctypes. A work around of separately loading a copy of the script source is used at the moment, so any module preprocessing performed when loading a JSScript is lost currently.
|
|
248
|
+
|