Skip to content

Javascript Take II #4485

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ports/javascript/JSBackend.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--- JSBackend.cpp 2018-01-10 16:35:07.331418145 +1100
+++ JSBackend_mp_js.cpp 2018-01-10 16:40:04.804633134 +1100
@@ -4280,6 +4280,7 @@

void JSWriter::calculateNativizedVars(const Function *F) {
NativizedVars.clear();
+ return;

for (Function::const_iterator I = F->begin(), BE = F->end(); I != BE; ++I) {
auto BI = &*I;
63 changes: 63 additions & 0 deletions ports/javascript/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
include ../../py/mkenv.mk

CROSS = 0

QSTR_DEFS = qstrdefsport.h

include $(TOP)/py/py.mk

CC = emcc -g4
LD = emcc -g4

INC += -I.
INC += -I$(TOP)
INC += -I$(BUILD)

CPP = clang -E

ifdef EMSCRIPTEN
CPP += -isystem $(EMSCRIPTEN)/system/include/libc -cxx-isystem $(EMSCRIPTEN)/system/include/libcxx
endif

CFLAGS = -m32 -Wall -Werror $(INC) -std=c99 $(COPT)
LDFLAGS = -m32 -Wl,-Map=$@.map,--cref -Wl,--gc-sections

CFLAGS += -O0 -DNDEBUG
CFLAGS += -fdata-sections -ffunction-sections

SRC_LIB = $(addprefix lib/,\
utils/interrupt_char.c \
utils/stdout_helpers.c \
utils/pyexec.c \
mp-readline/readline.c \
)

SRC_C = \
main.c \
mphalport.c \
modutime.c \

SRC_QSTR += $(SRC_C)

OBJ =
OBJ = $(PY_O)
OBJ += $(addprefix $(BUILD)/, $(SRC_LIB:.c=.o))
OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))

JSFLAGS = -O0 -s EXPORTED_FUNCTIONS="['_mp_js_init', '_mp_js_init_repl', '_mp_js_do_str', '_mp_js_process_char', '_mp_hal_get_interrupt_char', '_mp_keyboard_interrupt']" -s EXTRA_EXPORTED_RUNTIME_METHODS="['ccall', 'cwrap']" --memory-init-file 0 --js-library library.js

all: $(BUILD)/micropython.js

$(BUILD)/micropython.js: $(OBJ) library.js wrapper.js
$(ECHO) "LINK $(BUILD)/firmware.js"
$(Q)emcc $(LDFLAGS) -o $(BUILD)/firmware.js $(OBJ) $(JSFLAGS)
cat wrapper.js $(BUILD)/firmware.js > $@

min: $(BUILD)/micropython.js
uglifyjs $< -c -o $(BUILD)/micropython.min.js

test: $(BUILD)/micropython.js $(TOP)/tests/run-tests
$(eval DIRNAME=ports/$(notdir $(CURDIR)))
cd $(TOP)/tests && MICROPY_MICROPYTHON=../ports/javascript/node_run.sh ./run-tests

include $(TOP)/py/mkrules.mk
128 changes: 128 additions & 0 deletions ports/javascript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
MicroPython.js
==============

MicroPython transmuted into Javascript by Emscripten.

Dependencies
------------

Building micropython.js bears the same requirements as the standard MicroPython
ports with the addition of Emscripten (and uglify-js for the minified file).

A standard installation of Emscripten should provide functional code, however
if memory errors are encountered it may be worthwhile to modify the tool.
`emscripten-fastcomp/lib/Target/JSBackend.cpp` may require the minor fix
found in JSBackend.patch. This patch attempts to address situations where
C code running through Emscripten is denied access to Javascript variables
leading to false-positives in the MicroPython garbage collector as variables
with pointers exclusively in Javascript will be erased prematurely.
Refer to Emscripten documentation for instructions on building Emscripten
from source.

Build instructions
------------------

In order to build micropython.js, run:

$ make

To generate the minified file micropython.min.js, run:

$ make min

Running with Node.js
--------------------

Access the repl with:

$ node build/micropython.js

Stack size may be modified using:

$ node build/micropython.js -X stack=64K

Where stack size may be represented in Bytes, KiB or MiB.

MicroPython scripts may be executed using:

$ node build/micropython.js hello.py

Alternatively micropython.js may by accessed by other javascript programs in node
using the require command and the general API outlined below. For example:

```javascript
var mp_js = require('./build/micropython.js');

mp_js_init(64 * 1024);
mp_js_do_str("print('hello world')\n");
```

Running with HTML
-----------------

The prerequisite for browser operation of micropython.js is an element with
the id `mp_js_stdout` which receives `print` events. The following code
demonstrates basic functionality:

```html
<!doctype html>
<html>
<head>
<script src="build/micropython.js"></script>
</head>
<body>
<div id='mp_js_stdout'></div>
<script>
mp_js_stdout.addEventListener('print', function(e) {
document.write(e.data);
}, false);

mp_js_init(64 * 1024);
mp_js_do_str('print(\'hello world\')');
</script>
</body>
</html>
```

MicroPython code execution will suspend the browser so be sure to atomize usage
within this environment. Unfortunately interrupts have not been implemented for the
browser.

Testing
-------

Run the test suite using:

$ make test

API
---

The following functions have been exposed to javascript.

```
mp_js_init(stack_size)
```

Initialize MicroPython with the given stack size in bytes. This must be
called before attempting to interact with MicroPython.

```
mp_js_do_str(code)
```

Execute the input code. `code` must be a `string`.

```
mp_js_init_repl()
```

Initialize MicroPython repl. Must be called before entering characters into
the repl.

```
mp_js_process_char(char)
```

Input character into MicroPython repl. `char` must be of type `number`. This
will execute MicroPython code when necessary.
31 changes: 31 additions & 0 deletions ports/javascript/library.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2017, 2018 Rami Ali
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#include "py/obj.h"

extern void mp_js_write(const char *str, mp_uint_t len);
extern int mp_js_ticks_ms(void);
extern void mp_js_hook(void);
69 changes: 69 additions & 0 deletions ports/javascript/library.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2017, 2018 Rami Ali
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

mergeInto(LibraryManager.library, {
mp_js_write: function(ptr, len) {
for (var i = 0; i < len; ++i) {
c = String.fromCharCode(getValue(ptr + i, 'i8'));
if (typeof window === 'undefined') {
process.stdout.write(c);
} else {
var mp_js_stdout = document.getElementById('mp_js_stdout');
var print = new Event('print');
print.data = c;
mp_js_stdout.dispatchEvent(print);
}
}
},

mp_js_ticks_ms: function() {
return (new Date()).getTime() - MP_JS_EPOCH;
},

mp_js_hook: function() {
if (typeof window === 'undefined') {
var mp_interrupt_char = Module.ccall('mp_hal_get_interrupt_char', 'number', ['number'], ['null']);
var fs = require('fs');

var buf = new Buffer(1);
try {
var n = fs.readSync(process.stdin.fd, buf, 0, 1);
if (n > 0) {
if (buf[0] == mp_interrupt_char) {
Module.ccall('mp_keyboard_interrupt', 'null', ['null'], ['null']);
} else {
process.stdout.write(String.fromCharCode(buf[0]));
}
}
} catch (e) {
if (e.code === 'EAGAIN') {
} else {
throw e;
}
}
}
},
});
Loading