
mJS – A new approach to embedded scripting - dimonomid
https://mongoose-iot.com/blog/mjs-a-new-approach-to-embedded-scripting/
======
nneonneo
25k of flash space sounds like a lot for what is effectively just a JavaScript
parser and interpreter. I recall the days when you could fit a whole
language's compiler into a few measly KB. 1KB RAM is also quite a lot for
certain boards, especially if it has to be stack or SRAM.

How's the performance? Will my 72MHz Cortex or 16MHz Arduino be able to run
interesting things with mJS? If I have to do everything through FFI, what's
the benefit vs. e.g. C++11? The latter has nice language features too but
compiles to much smaller native code!

~~~
TazeTSchnitzel
Ideally none of the flash space is spent on a parser/compiler, that's quite
wasteful if you could compile bytecode offline.

I don't know if you can do that with mJS, alas.

~~~
phire
If you want to support eval, then the whole parser/compiler needs to be on the
device.

But do you really need eval?

------
dfabulich
That FFI interface looks _way_ nicer than the node-ffi interface.

    
    
        let f = ffi('int gpio_write(int, int)');
    

vs.

    
    
        var current = ffi.Library(null, {
          'atoi': [ 'int', [ 'string' ] ]
        });

~~~
hackcasual
You could probably write a wrapper to go from the node way to their style.

------
haldean
This looks a lot like Lua's FFI interface[0], which is a compliment (but
contradicts the statement that this sort of FFI is "the feature that no other
engine implemented so far"). Nicely done.

[0] [http://csl.sublevel3.org/post/luajit-
cpp/](http://csl.sublevel3.org/post/luajit-cpp/)

~~~
drozd
Nice! I wasn't aware of lua ffi api, thank you!

------
oso2k
Is this a re-packaged v7 [0] (it's from Cesanta as well)? mjs.c is 477K. It
looks like it as v7.c is 475K [1]. It shaves whole entire 1MB from my staticly
linked builds (v7 1.9MB vs. mjs 998K), and almost 2MB from dynamically linked
builds (v7 1.9MB vs. mjs 99K) on amd64. If it shares the same underlying
architecture and api [2] then this is a pretty great achievement.

In their docs, they claim 25K storage and 10K RAM. Blog post claims 25K
storage and 1K RAM.

[0]
[https://github.com/cesanta/mjs/blob/master/mjs.c](https://github.com/cesanta/mjs/blob/master/mjs.c)

[1]
[https://github.com/cesanta/v7/blob/master/v7.c](https://github.com/cesanta/v7/blob/master/v7.c)

[2]
[https://docs.cesanta.com/v7/master/#/v7-internals/](https://docs.cesanta.com/v7/master/#/v7-internals/)

~~~
jfe
i wonder if the size could be reduced by replacing the yacc code with a hand-
written parser.

~~~
drozd
There is no yacc code in either V7 or mJS.

V7 uses hand-written recursive-descent parser. Initially, it was using
ordinary C functions, and that created a problem on systems with low stack
size. E.g. each '(' starts statement parsing from the top, so 1 + (2 + (3 + (4
+ 5))) consumed stack, and sometimes resulted in stack overflow in e.g.
interrupt handlers or network callbacks.

Therefore we have rewritten recursive descent using C coroutines, and that is
extremely sophisticated piece of work. See
[https://raw.githubusercontent.com/cesanta/v7/master/v7.c](https://raw.githubusercontent.com/cesanta/v7/master/v7.c)
, search for #line 1 "v7/src/parser.c"

mJS on the other hand uses lemon parser generator - the one from sqlite
project. It generates a LALR parser which is quite efficient in terms of
memory.

~~~
oso2k
So it is API compatible with V7?

~~~
drozd
Nope. It's similar though, cause some of the concepts, and the code, were
reused. mJS does not need an embedding API, really. The intent is that FFI is
used.

------
jdormit
I've never worked with embedded systems, so forgive my ignorance. Why is this
any more useful than just writing c code, if all the code that actually
interacts with the hardware has to be written in c anyway?

~~~
drozd
The ability to script without the need of rebuilding/reflashing the whole
firmware is often fairly useful. The most value is not in JS per se, but in
the ability to script.

~~~
jdormit
Ah, I can see how that is useful. Way up in the web dev world I don't often
have to think of things like that :)

------
otikik
> One common thing these projects share is an attempt to implement the whole
> language specification, together with the more or less complete standard
> library

Not in the case of Lua. Well, the "whole language" part is correct, but Lua is
a very tiny language spec. It's "standard library", however, is the opposite
of "complete".

~~~
otikik
> none of the popular scripting languages have been designed for the embedded
> environment in the first place

Also false for Lua. It is designed to be embedded.

~~~
drozd
Embedded environment in this context means hardware low on resources, e.g.
microcontrollers. Your perception of that word is "embedded into the C/C++
program". These are two different things. I agree that Lua (like some other
languages) were designed to be embedded into C/C++ apps.

~~~
bdowling
Something like this?
[http://www.nodemcu.com/index_en.html](http://www.nodemcu.com/index_en.html)

------
ComputerGuru
So is the only benefit of wasting that space and CPU so you can bill your
platform/project/whatever as being "js" and C/C++-free? What does a no
standard library JS buy you over a no standard library C++11 everyone else is
using these days, besides the pain and trouble of dealing with a dynamically
typed language without native debugging support?

Why is there so much stigma against _learning_ to code in something other than
$favlang these days? Most hard core developers I know appreciate the
importance of using the right tool for the job, I don't see embedded/desktop
developers shying away from using whatever the native toolkit/language is for
their chosen platform and instead shoehorning $x to fit as much as we see this
constant trend to try to use "web tech" everywhere. (Scripting in embedded
systems has long been a solved problem: use lua.)

~~~
woah
For example, I'm working on a dashboard for wifi routers that displays
information beyond what is provided by OpenWRT's ubus system. We're get this
information from the kernel, but it needs to be converted to JSON to be
consumed by the dashboard frontend. Right now, I'm converting the information
to JSON using Go, but there are some platforms that Go can have problems
compiling binaries for. We have also written a lot of C for these routers, but
doing this kind of thing in C would just be overly cumbersome. Shell could be
an option, but it is also cumbersome. Lua could be an option, but I don't know
much about it and I don't know how well it supports JSON. Most other
developers that will work on this don't know much Lua either.

mJS might also be an option. I don't really see what's wrong with that if we
have the space for it.

This can probably get types from Flow, since it is valid es6. Flow has no
compilation step or runtime component.

You may have a point about the debugging, but JS is pretty easy to debug by
printing to stdout.

~~~
etiene
Lua is the easiest language to learn I ever saw. It has a fantastic json
support with the cjson library ([https://luarocks.org/modules/luarocks/lua-
cjson](https://luarocks.org/modules/luarocks/lua-cjson)). It's there by
default on many firmwares. Do check the docs for nodemcu, for example:
[https://nodemcu.readthedocs.io/en/master/](https://nodemcu.readthedocs.io/en/master/)
(a firmware for the ESP microcontrollers)

Not to mention Lua itself can be used as a data / configuration language,
their tables syntax is made especially for that. Normally when I work with Lua
I don't feel the need to embed any other parsers for configuration, but cjson
is there if you want.

------
qwertyuiop924
Neat. Although languages which are "almost, but not quite <x>" are, I would
say, harder for people who know <x> to learn than languages that are entirely
different from <x>.

------
_pmf_
As light weight, easily embeddable JS engines go, I'd like to mention
Duktape[0]. But the FFI of mJS does look quite a bit nicer.

~~~
drozd
Duktape is nice, and I've mentioned it in the article. However it's quite fat,
and can't be used on some boards like ESP8266. It's flash footprint is
Megabytes, whereas mJS flash footprint is ~25k.

~~~
megous
Duktape website:

"Embeddable, portable, compact: can run on platforms with 192kB flash and 64kB
system RAM"

~~~
ricardobeat
Also
[http://www.esp32.com/viewtopic.php?t=497](http://www.esp32.com/viewtopic.php?t=497)

~~~
drozd
Ah, thanks, wrong about flash footprint. Neil has done a great job adopting
Duktape on ESP32.

------
sdegutis
Taking this example:

    
    
        let malloc = ffi('void *malloc(int)');
        let mem = malloc(10);
    

How do you manipulate that memory?

How can you do the following in mJS?

    
    
        int* mem = malloc(10);
        mem[0] = 3;
        mem[1] = 7;
        mem[2] = 12;
        int *rest = mem + 3;
    

There are some things you can do in C without functions. How does mJS achieve
this?

~~~
drozd
Currently, mJS FFI is limited to functions only. And, to simple enough
functions.

On how to access memory from mJS: write an accessor function!

    
    
        // C
        void setmem(uint8_t *ptr, int index, int value) {
          ptr[index] = value;
        }
    
        // mJS
        let setmem = ffi('void setmem(void *, int, int)');
        let malloc = ffi('void *malloc(int)');
        let mem = malloc(10);
        setmem(mem, 3, 5);

------
formula1
This is neat. This isnt quite javascript (which is neither good nor bad) but I
think has a lot of potential

------
gfwilliams
Espruino's had an FFI interface for the last 3 years! Nice to see they did
their research :)

~~~
drozd
Hi Gordon! Nice to meet you :) I am the author of the article, and I am quite
fond of Espruino, it's a great project. Wasn't aware that Espruino has FFI
API!

~~~
gfwilliams
Hi! Thanks, v7 looked great too. mJS looks like a good idea - IMO it'd be nice
to standardise a sane, minimal subset of JS that can be easily implemented -
it'd be a huge help for all the developers of embedded JS interpreters, and
could potentially be targeted by transpilers too.

------
etiene
Worth mentioning: [https://github.com/elua](https://github.com/elua)

Allows to run Lua on bare metal, no OS involved. It's what the nodemcu
firmware for the ESP cards is based on.

------
teaearlgraycold
Seems a little crazy that there's no support for closures.

~~~
drozd
Yes. Closures add quite a bit of complexity and footprint. Anticipated use
case was a short script that orchestrates the device logic, calling existing
SDK functions.

