Skip to content
This repository was archived by the owner on Sep 21, 2023. It is now read-only.

Commit 8ae7533

Browse files
committed
Extensive refactoring for <py-repl> related tag and addition of filesystem support.
1 parent dc91089 commit 8ae7533

File tree

15 files changed

+2028
-232
lines changed

15 files changed

+2028
-232
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mpbuild
22
pybuild
33
micropython
4+
micropython-ulab
45
cpython
56
pyodide
67
emsdk

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ all:
1212
setup:
1313
git clone https://github.com/emscripten-core/emsdk.git
1414
git clone https://github.com/micropython/micropython.git
15+
git clone https://github.com/v923z/micropython-ulab.git
1516

1617
update:
1718
cd emsdk && git pull && ./emsdk install latest && ./emsdk activate latest
@@ -20,7 +21,7 @@ mp:
2021
rm -rf mpbuild
2122
rm -rf micropython/ports/webassembly/build
2223
$(MAKE) -C micropython/mpy-cross
23-
./emsdk/emsdk activate latest && source emsdk/emsdk_env.sh && $(MAKE) -C micropython/ports/webassembly
24+
./emsdk/emsdk activate latest && source emsdk/emsdk_env.sh && $(MAKE) -C micropython/ports/webassembly USER_C_MODULES=../../../micropython-ulab/
2425
cp -r micropython/ports/webassembly/build mpbuild
2526

2627
serve:

README.md

Lines changed: 73 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# MicroPyScript 🔬 🐍
22

3-
A small, simple kernel of PyScript, made for testing purposes.
3+
A small, simple kernel of PyScript, made for testing purposes in the spirit of
4+
a [code spike](https://en.wikipedia.org/wiki/Spike_(software_development)).
45

56
This is the way:
67

@@ -10,15 +11,15 @@ This is the way:
1011
* Vanilla JavaScript.
1112
* Pluggable.
1213
* Comments.
13-
* Tests.
14+
* (Some) tests.
1415
* Build for change.
1516

16-
This is a solid foundation for lightweight testing of Python runtimes that
17+
This is a foundation for lightweight testing of Python interpreters that
1718
target WASM. Inspired by code in the "real" PyScript website and our plans for
1819
plugins and simple event based coordination.
1920

2021
Complexity, edge cases and customization is (hopefully) confined to plugins and
21-
bespoke runtimes.
22+
bespoke interpreters.
2223

2324
That is all.
2425

@@ -56,26 +57,16 @@ To check things are working:
5657
$ make serve
5758
```
5859

59-
Then point your browser to http://0.0.0.0:8000/ to see a "Hello World" from
60-
MicroPython.
61-
62-
### What's in the files?
63-
64-
* **README.md** - this file, containing project documentation.
65-
* **Makefile** - common tasks scripted into convenient targets. See above.
66-
* **hello.py** - a simple "hello world" Python script for PyScript to run.
67-
* **index.html** - a small web page that uses PyScript.
68-
* **pyscript.js** - the simple, single file implementation of PyScript.
69-
* **SpecRunner.html** - a web page to run the test specifications with Jasmine.
70-
* **spec** - a directory containing the test specifications for Jasmine.
71-
* **lib** - a directory containing the Jasmine test library.
72-
73-
To change the configuration of PyScript take a look at the JSON object
74-
defined in the `<py-config>` tag in `index.html`. Currently valid runtimes are
75-
`micropython` or `pyodide`.
60+
Then point your browser to http://0.0.0.0:8000/ to see the first page of an
61+
interactive technical report about using MicroPython. You should be able to
62+
change the interpreter from `micropython` to `pyodide` and things should just
63+
work as before, but with a different interpreter at the bottom of the PyScript
64+
stack.
7665

7766
## Running the tests
7867

68+
**TESTS ARE CURRENTLY BROKEN**
69+
7970
For the sake of simplicity (and familiarity) we use the
8071
[Jasmine test framework](https://jasmine.github.io/index.html) to exercise the
8172
JavaScript aspects of our code.
@@ -86,43 +77,56 @@ and run the Jasmine based test suite.
8677

8778
## How it works
8879

89-
The PyScript core only loads configuration, starts the Python runtime and
90-
allows the registration of plugins. All other logic, capabilities and features
91-
are contained in the plugins.
80+
The PyScript core only loads configuration, starts the Python interpreter,
81+
allows the registration of plugins and adds files to the interpreter's
82+
filesystem. All other logic, capabilities and features are contained in the
83+
plugins.
84+
85+
Currently, only two plugins are provided:
9286

93-
Currently, only a single plugin is provided by PyScript: the one that
94-
implements the core `<py-script>` tag.
87+
* One built into PyScript that implements the core `<py-script>` tag.
88+
* The other (in `customtags.js`) implements the `<py-repl>` tag to demonstrate
89+
a "third party" plugin.
9590

9691
The story of PyScript's execution is roughly as follows:
9792

9893
1. Configuration is loaded from the `<py-config>` tag. Once complete the
9994
`py-configured` event is dispatched, containing the `config` object based
10095
upon default values overridden by the content of the `<py-config>` tag.
101-
2. When the `py-configured` event is dispatched two things happen:
102-
* The runtime is loaded via injecting a `<script>` tag that references the
103-
runtime's URL. Once loaded the `py-runtime-loaded` event is dispatched.
96+
2. When the `py-configured` event is dispatched three things happen:
97+
* The interpreter is loaded via injecting a `<script>` tag that references the
98+
interpreter's URL. Once loaded the `py-interpreter-loaded` event is dispatched.
10499
* Plugins are registered and have their `configure` function called. For each
105100
plugin registered a `py-plugin-registered` event is dispatched, containing
106101
the (potentially changed) `config`, and a reference to the newly registered
107102
plugin.
108-
3. When `py-runtime-loaded` is dispatched two things happen:
109-
* The runtime is instantiated / started. Once complete the `py-runtime-ready`
103+
* The content of the files to be added to the interpreter's filesystem are
104+
fetched. Once downloaded each file causes a `py-file-fetched` event to be
105+
dispatched with the path and content of the file attached to it.
106+
3. When `py-interpreter-loaded` is dispatched two things happen:
107+
* The interpreter is instantiated / started. Once complete the `py-interpreter-ready`
110108
event is dispatched.
111109
* All registered plugins have their `start` function called and a
112110
`py-plugin-started` event is dispatched for each plugin.
113-
4. When the `py-runtime-ready` event is dispatched all plugins have their
114-
`onRuntimeReady` function called with the `config` and `runtime` objects.
115-
5. Any plugins registered after the runtime is ready immediately have their
116-
`configure`, `start` and `onRuntimeReady` functions called, with the
111+
4. When the `py-interpreter-ready` event is dispatched all plugins have their
112+
`onInterpreterReady` function called with the `config` and `interpreter`
113+
objects. At this point all files are copied onto the interpreter's
114+
filesystem. When all the files are copied the `py-files-loaded` event is
115+
dispatched.
116+
5. When both the interpreter and filesystem are finished setting up and in a
117+
ready state, the `py-finished-setup` event is dispatched to signal PyScript
118+
is ready to evaluate user's code.
119+
6. Any plugins registered after the interpreter is ready immediately have their
120+
`configure`, `start` and `onInterpreterReady` functions called, with the
117121
`py-plugin-registered` and `py-plugin-started` events being dispatched.
118122

119123
That's it!
120124

121125
When `pyscript.js` is run, it creates a `window.PyScript` object that contains
122126
read-only references to the `config`, registered `plugins`,
123-
`availableRuntimes`, the `runtime` used on the page, an `isRuntimeReady` flag,
124-
a `registerPlugin` function (see below) and a `runPython(code)` function that
125-
takes a string of Python.
127+
`availableInterpreters`, the `interpreter` used on the page, a
128+
n `isInterpreterReady` flag, a `registerPlugin` function (see below) and a
129+
`runPython(code)` function that takes a string of Python.
126130

127131
There are copious comments in the `pyscript.js` file. My intention is for
128132
simplicity, lack of onerous dependencies (bye-bye `npm`), and
@@ -157,8 +161,8 @@ following order (as the lifecycle of the plugin):
157161
should not be modified by the plugin. Example use cases:
158162
- Define custom HTML elements.
159163
- Start fetching external resources.
160-
* `onRuntimeReady(config, runtime)` - Called once the runtime is ready to
161-
evaluate Python code. Example use cases:
164+
* `onInterpreterReady(config, interpreter)` - Called once the interpreter is
165+
ready to evaluate Python code. Example use cases:
162166
- `pip install` packages.
163167
- Import/initialize Python plugins.
164168

@@ -169,11 +173,11 @@ The following events, dispatched by PyScript itself, are related to plugins:
169173
immediately after the plugin's `configure` function is called.
170174
* `py-plugin-started` - Dispatched immediately after a plugin's `start`
171175
function is called. The event contains a reference to the started plugin.
172-
* `py-runtime-ready` - causes each plugin's `onRuntimeReady` function to be
173-
called.
176+
* `py-interpreter-ready` - causes each plugin's `onInterpreterReady` function
177+
to be called.
174178

175-
If a plugin is registered *after* the runtime is ready, all three functions are
176-
immediately called in the expected sequence, one after the other.
179+
If a plugin is registered *after* the interpreter is ready, all three functions
180+
are immediately called in the expected sequence, one after the other.
177181

178182
The recommended way to create and register plugins is:
179183

@@ -202,14 +206,15 @@ const myPlugin = function(e) {
202206
*/
203207

204208
const plugin = {
209+
name: "my-plugin",
205210
configure: function(config) {
206211
// ...
207212
},
208213
start: function(config) {
209214
// ...
210215
foo();
211216
},
212-
onRuntimeReady: function(config, runtime) {
217+
onInterpreterReady: function(config, interpreter) {
213218
// ...
214219
}
215220
};
@@ -226,44 +231,41 @@ Then in your HTML file:
226231
<script src="pyscript.js" type="module"></script>
227232
```
228233

229-
A good example of a plugin is the built-in plugin for the `<py-script>` tag
230-
found in `pyscript.js` (search for the object assigned to `pyScriptTag`).
231-
232-
## Runtimes
234+
## Interpreters
233235

234-
The `Runtime` class abstracts away all the implementation details of the
235-
various Python runtimes we might use.
236+
The `Interpreter` class abstracts away all the implementation details of the
237+
various Python interpreters we might use.
236238

237-
To see a complete implementation see the `MicroPythonRuntime` class that
238-
inherits from `Runtime`. There is also an incomplete `PyodideRuntime` class so
239-
I was able to compare and contrast the differences between implementations and
240-
arrive at a general abstraction (still very much a work in progress). Comments
241-
in the code should explain what's going on in terms of the life-cycle and
242-
capabilities of a "runtime".
239+
To see a complete implementation see the `MicroPythonInterpreter` class that
240+
inherits from `Interpreter`. There is also an incomplete `PyodideInterpreter`
241+
class so I was able to compare and contrast the differences between
242+
implementations and arrive at a general abstraction (still very much a work in
243+
progress). Comments in the code should explain what's going on in terms of the
244+
life-cycle and capabilities of a "interpreter".
243245

244-
The afore mentioned `MicroPythonRuntime`, `CPythonRuntime` and `PyodideRuntime`
245-
all, to a greater or lesser extent, define a uniform shim around their
246-
respective runtimes. The MicroPython one is most complete, but still needs work
247-
as I make changes to how MicroPython itself exposes `stdout`, `stderr` and
248-
consumes `stdin`.
246+
The afore mentioned `MicroPythonInterpreter`, `CPythonInterpreter` and
247+
`PyodideInterpreter` all, to a greater or lesser extent, define a uniform shim
248+
around their respective interpreter. The MicroPython one is most complete, but
249+
still needs work as I make changes to how MicroPython itself exposes `stdout`,
250+
`stderr` and consumes `stdin`.
249251

250252
## The future
251253

252-
Who knows..? But this is a good scaffold for testing different Python runtimes.
254+
Who knows..? But this is a good scaffold for testing different Python
255+
interpreters.
253256

254257
Next steps:
255258

256259
* More comprehensive tests.
257-
* `CPythonRuntime` fully implemented.
258-
* `PyodideRuntime` finished.
259-
* `MicroPythonRuntime` refactored after making MicroPython play nicer with
260+
* `CPythonInterpreter` fully implemented.
261+
* `PyodideInterpreter` finished.
262+
* `MicroPythonInterpreter` refactored after making MicroPython play nicer with
260263
`stdout` and `stderr`.
261-
* A plugin for a `<py-repl>` tag (the foundations are in place).
262-
* A uniform way to `pip install` packages in each runtime.
263-
* A uniform JavaScript gateway from within each runtime.
264+
* A uniform way to `pip install` packages in each interpreter.
265+
* A uniform JavaScript gateway from within each interpreter.
264266
* A uniform `navigator` object through which to access the DOM from within each
265-
runtime.
267+
interpreter.
266268
* Running in web-workers (and associated message passing work), for each
267-
runtime.
269+
interpreter.
268270

269271
That's it..! ;-)

arrr.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""
2+
A small module for piratical testing of PyScript. ;-)
3+
"""
4+
import random
5+
6+
7+
#: Defines English to Pirate-ish word substitutions.
8+
_PIRATE_WORDS = {
9+
"hello": "ahoy",
10+
"hi": "arrr",
11+
"my": "me",
12+
"friend": "m'hearty",
13+
"boy": "laddy",
14+
"girl": "lassie",
15+
"sir": "matey",
16+
"miss": "proud beauty",
17+
"stranger": "scurvy dog",
18+
"boss": "foul blaggart",
19+
"where": "whar",
20+
"is": "be",
21+
"the": "th'",
22+
"you": "ye",
23+
"old": "barnacle covered",
24+
"happy": "grog-filled",
25+
"nearby": "broadside",
26+
"bathroom": "head",
27+
"kitchen": "galley",
28+
"pub": "fleabag inn",
29+
"stop": "avast",
30+
"yes": "aye",
31+
"no": "nay",
32+
"yay": "yo-ho-ho",
33+
"money": "doubloons",
34+
"treasure": "booty",
35+
"strong": "heave-ho",
36+
"take": "pillage",
37+
"drink": "grog",
38+
"idiot": "scallywag",
39+
"sea": "briney deep",
40+
"vote": "mutiny",
41+
"song": "shanty",
42+
"drunk": "three sheets to the wind",
43+
"lol": "yo ho ho",
44+
"talk": "parley",
45+
"fail": "scupper",
46+
"quickly": "smartly",
47+
"captain": "cap'n",
48+
"meeting": "parley with rum and cap'n",
49+
}
50+
51+
52+
#: A list of Pirate phrases to randomly insert before or after sentences.
53+
_PIRATE_PHRASES = [
54+
"batten down the hatches!",
55+
"splice the mainbrace!",
56+
"thar she blows!",
57+
"arrr!",
58+
"weigh anchor and hoist the mizzen!",
59+
"savvy?",
60+
"dead men tell no tales.",
61+
"cleave him to the brisket!",
62+
"blimey!",
63+
"blow me down!",
64+
"avast ye!",
65+
"yo ho ho.",
66+
"shiver me timbers!",
67+
"blisterin' barnacles!",
68+
"ye flounderin' nincompoop.",
69+
"thundering typhoons!",
70+
"sling yer hook!",
71+
]
72+
73+
74+
def translate(english):
75+
"""
76+
Take some English text and return a Pirate-ish version thereof.
77+
"""
78+
# Normalise a list of words (remove whitespace and make lowercase)
79+
words = [w.lower() for w in english.split()]
80+
# Substitute some English words with Pirate equivalents.
81+
result = [_PIRATE_WORDS.get(word, word) for word in words]
82+
# Capitalize words that begin a sentence and potentially insert a pirate
83+
# phrase with a chance of 1 in 5.
84+
capitalize = True
85+
for i, word in enumerate(result):
86+
if capitalize:
87+
result[i] = word[0].upper() + word[1:]
88+
capitalize = False
89+
if word[-1] in (".", "!", "?", ":",):
90+
# It's a word that ends with a sentence ending character.
91+
capitalize = True
92+
if random.randint(0, 5) == 0:
93+
result.insert(i + 1, random.choice(_PIRATE_PHRASES))
94+
return " ".join(result)
95+

0 commit comments

Comments
 (0)