Elk is a tiny embeddable JavaScript engine that implements a small but usable subset of ES6

Overview

Elk: a tiny JS engine for embedded systems

Build Status License: GPLv2/Commercial Code Coverage

Elk is a tiny embeddable JavaScript engine that implements a small but usable subset of ES6. It is designed for microcontroller development. Instead of writing firmware code in C/C++, Elk allows to develop in JavaScript. Another use case is providing customers with a secure, protected scripting environment for product customisation.

Elk features include:

  • Cross platform. Works anywhere from 8-bit microcontrollers to 64-bit servers
  • Zero dependencies. Builds cleanly by ISO C or ISO C++ compilers
  • Easy to embed: just copy elk.c and elk.h to your source tree
  • Very small and simple embedding API
  • Can call native C/C++ functions from JavaScript and vice versa
  • Does not use malloc. Operates with a given memory buffer only
  • Small footprint: about 20KB on flash/disk, about 100 bytes RAM for core VM
  • No bytecode. Interprets JS code directly

Below is a demonstration on a classic Arduino Nano board which has 2K RAM and 30K flash (see full sketch):

Elk on Arduino Nano

JavaScript on ESP32

The Esp32JS Arduino sketch is an example of Elk integration with ESP32. Flash this sketch on your ESP32 board, go to http://elk-js.com, and get a JavaScript development environment instantly! Reloading your script takes a fraction of a second - compare that with a regular reflashing.. Here how it looks like:

The example JS firmware implements:

  • Blinks an LED periodically
  • Connects to the HiveMQ MQTT server
  • Subscribes to the elk/rx topic
  • When an MQTT message is received, sends some stats to the elk/tx topic:

That's screenshot is taken from the MQTT server which shows that we sent a hello JS! message and received stats in response:

Call JavaScript from C

#include <stdio.h>
#include "elk.h"

int main(void) {
  char mem[200];
  struct js *js = js_create(mem, sizeof(mem));  // Create JS instance
  jsval_t v = js_eval(js, "1 + 2 * 3", ~0);     // Execute JS code
  printf("result: %s\n", js_str(js, v));        // result: 7
  return 0;
}

Call C from JavaScript

This demonstrates how JS code can import and call existing C functions:

#include <stdio.h>
#include "elk.h"

// C function that adds two numbers. Will be called from JS
int sum(int a, int b) {
  return a + b;
}

int main(void) {
  char mem[200];
  struct js *js = js_create(mem, sizeof(mem));  // Create JS instance
  jsval_t v = js_import(js, sum, "iii");        // Import C function "sum"
  js_set(js, js_glob(js), "f", v);              // Under the name "f"
  jsval_t result = js_eval(js, "f(3, 4);", ~0); // Call "f"
  printf("result: %s\n", js_str(js, result));   // result: 7
  return 0;
}

Supported features

  • Operations: all standard JS operations except:
    • !=, ==. Use strict comparison !==, ===
    • No ternary operator a ? b : c
    • No computed member access a[b]
  • Typeof: typeof('a') === 'string'
  • While: while (...) { ... }
  • Conditional: if (...) ... else ...
  • Simple types: let a, b, c = 12.3, d = 'a', e = null, f = true, g = false;
  • Functions: let f = function(x, y) { return x + y; };
  • Objects: let obj = {f: function(x) { return x * 2}}; obj.f(3);
  • Every statement must end with a semicolon ;
  • Strings are binary data chunks, not Unicode strings: 'Київ'.length === 8

Not supported features

  • No var, no const. Use let (strict mode only)
  • No do, switch, for. Use while
  • No => functions. Use let f = function(...) {...};
  • No arrays, closures, prototypes, this, new, delete
  • No standard library: no Date, Regexp, Function, String, Number

Performance

Since Elk parses and interprets JS code on the fly, it is not meant to be used in a performance-critical scenarios. For example, below are the numbers for a simple loop code on a different architectures.

let a = 0;        // 97 milliseconds on a 16Mhz 8-bit Atmega328P (Arduino Uno and alike)
while (a < 100)   // 16 milliseconds on a 48Mhz SAMD21
  a++;            //  5 milliseconds on a 133Mhz Raspberry RP2040
                  //  2 milliseconds on a 240Mhz ESP32

Build options

Available preprocessor definitions:

Name Default Description
JS_EXPR_MAX 20 Maximum tokens in expression. Expression evaluation function declares an on-stack array jsval_t stk[JS_EXPR_MAX];. Increase to allow very long expressions. Reduce to save C stack space.
JS_DUMP undefined Define to enable js_dump(struct js *) function which prints JS memory internals to stdout
JS_GC_THRESHOLD 80 A percentage (from 0 to 100) of runtime memory when a garbage collection (GC) is triggered. A trigger point is a beginning of statement block (function body, loop body, etc)

Note: on ESP32 or ESP8266, compiled functions go into the .text ELF section and subsequently into the IRAM MCU memory. It is possible to save IRAM space by copying Elk code into the irom section before linking. First, compile the object file, then rename .text section, e.g. for ESP8266:

$ xtensa-lx106-elf-gcc ... elk.c -c tmp
$ xtensa-lx106-elf-objcopy --rename-section .text=.irom0.text tmp elk.a

API reference

js_create()

struct js *js_create(void *buf, size_t len);

Initialize JS engine in a given memory block. Elk will only use that memory block to hold its runtime, and never use any extra memory. Return: a non-NULL opaque pointer on success, or NULL when len is too small. The minimum len is about 100 bytes.

The given memory buffer is laid out in the following way:

  | <-------------------------------- len ------------------------------> |
  | struct js, ~100 bytes  |   runtime vars    |    free memory           | 

js_eval()

jsval_t js_eval(struct js *, const char *buf, size_t len);

Evaluate JS code in buf, len and return result of the evaluation. During the evaluation, Elk stores variables in the "runtime" memory section. When js_eval() returns, Elk does not keep any reference to the evaluated code: all strings, functions, etc, are copied to the runtime.

Important note: the returned result is valid only before the next call to js_eval(). The reason is that js_eval() triggers a garbage collection. A garbage collection is mark-and-sweep.

The runtime footprint is as follows:

  • An empty object is 8 bytes
  • Each object property is 16 bytes
  • A string is 4 bytes + string length, aligned to 4 byte boundary
  • A C stack usage is ~200 bytes per nested expression evaluation

js_str()

const char *js_str(struct js *, jsval_t val);

Stringify JS value val and return a pointer to a 0-terminated result. The string is allocated in the "free" memory section. If there is no enough space there, an empty string is returned. The returned pointer is valid until the next js_eval() call.

js_import()

jsval_t js_import(struct js *js, uintptr_t funcaddr, const char *signature);

Import an existing C function with address funcaddr and signature signature. Return imported function, suitable for subsequent js_set().

  • js: JS instance
  • funcaddr: C function address: (uintptr_t) &my_function
  • signature: specifies C function signature that tells how JS engine should marshal JS arguments to the C function. First letter specifies return value type, following letters - parameters:
    • b: a C bool type
    • d: a C double type
    • i: a C integer type: char, short, int, long
    • s: a C string, a 0-terminated char *
    • j: a jsval_t
    • m: a current struct js *. In JS, pass null
    • p: any C pointer
    • v: valid only for the return value, means void

The imported C function must satisfy the following requirements:

  • A function must have maximum 6 parameters
  • C double parameters could be only 1st or 2nd. For example, function void foo(double x, double y, struct bar *) could be imported, but void foo(struct bar *, double x, double y) could not
  • C++ functions must be declared as extern "C"
  • Functions with float params cannot be imported. Write wrappers with double

Here are some example of the import specifications:

  • int sum(int) -> js_import(js, (uintptr_t) sum, "ii")
  • double sub(double a, double b) -> js_import(js, (uintptr_t) sub, "ddd")
  • int rand(void) -> js_import(js, (uintptr_t) rand, "i")
  • unsigned long strlen(char *s) -> js_import(js, (uintptr_t) strlen, "is")
  • char *js_str(struct js *, js_val_t) -> js_import(js, (uintptr_t) js_str, "smj")

In some cases, C APIs use callback functions. For example, a timer C API could specify a time interval, a C function to call, and a function parameter. It is possible to marshal JS function as a C callback - in other words, it is possible to pass JS functions as C callbacks.

A C callback function should take between 1 and 6 arguments. One of these arguments must be a void * pointer, that is passed to the C callback by the imported function. We call this void * parameter a "userdata" parameter.

The C callback specification is enclosed into the square brackets [...]. In addition to the signature letters above, a new letter u is available that specifies userdata parameter. In JS, pass null for u param. Here is a complete example:

#include <stdio.h>
#include "elk.h"

// C function that invokes a callback and returns the result of invocation
int f(int (*fn)(int a, int b, void *userdata), void *userdata) {
  return fn(1, 2, userdata);
}

int main(void) {
  char mem[500];
  struct js *js = js_create(mem, sizeof(mem));
  js_set(js, js_glob(js), "f", js_import(js, f, "i[iiiu]u"));
  jsval_t v = js_eval(js, "f(function(a,b,c){return a + b;}, 0);", ~0);
  printf("result: %s\n", js_str(js, v));  // result: 3
  return 0;
}

js_set(), js_glob(), js_mkobj()

jsval_t js_glob(struct js *);   // Return global object
jsval_t js_mkobj(struct js *);  // Create a new object
void js_set(struct js *, jsval_t obj, const char *key, jsval_t val);  // Assign property to an object

These are helper functions for assigning properties to objects. The anticipated use case is to give names to imported C functions.

Importing a C function sum into the global namespace:

  jsval_t global_namespace = js_glob(js);
  jsval_t imported_function = js_import(js, (uintptr_t) sum, "iii");
  js_set(js, global_namespace, "f", imported_function);

Use js_mkobj() to create a dedicated object to hold groups of functions and keep a global namespace tidy. For example, all GPIO related functions can go into the gpio object:

  jsval_t gpio = js_mkobj(js);              // Equivalent to:
  js_set(js, js_glob(js), "gpio", gpio);    // let gpio = {};

  js_set(js, gpio, "mode",  js_import(js, (uintptr_t) func1, "iii");  // Create gpio.mode(pin, mode)
  js_set(js, gpio, "read",  js_import(js, (uintptr_t) func2, "ii");   // Create gpio.read(pin)
  js_set(js, gpio, "write", js_import(js, (uintptr_t) func3, "iii");  // Create gpio.write(pin, value)

js_usage()

int js_usage(struct js *);

Return memory usage percentage - a number between 0 and 100. See JS_GC_THRESHOLD build time option. Note: you could trigger garbage collection by evaluating an empty expression: js_eval(js, "", 0).

LICENSE

Dual license: GPLv2 or commercial. For commercial licensing, technical support and integration help, please contact us at https://cesanta.com/contact.html

Comments
  • Bitwise AND operator

    Bitwise AND operator

    LINUX_DIST="Linux Mint 18.3 Sylvia MATE 32-bit" KERNEL=Linux computer 4.10.0-38-generic i686 i686 i686 GNU/Linux

    ARDUINO IDE: 1.8.10 (32bit) elk Library: version 0.0.16 (Installed using Arduino IDE library manager) ESP32 boards (DEVKIT): 1.0.4 (Installed with board manager from Arduino IDE)

    I'm having difficulty with some code I'm trying to get working on the esp32 ... I believe my problem stems from my use of the Bitwise AND operator ... here is a snippet code snippet

    js_eval(js, "let rainbow =
    
    function(wait) { 
        b = 0; 
        while(b < 1280) { 
            c = 0; 
            while(c < numPixels()) { 
                setPixelColor32(c, Wheel(((c * 256 / numPixels()) + b) & 255));
                c++; 
            } 
            show(); 
            delay(wait); 
            b++; 
        } 
    }; 
    ", 0);
    
    opened by sfranzyshen 23
  • Fails to compile under the Arduino IDE

    Fails to compile under the Arduino IDE

    LINUX_DIST="Linux Mint 18.3 Sylvia MATE 32-bit" KERNEL=Linux computer 4.10.0-38-generic i686 i686 i686 GNU/Linux

    ARDUINO IDE: 1.8.10 (32bit) Elk Library: release 0.0.3 (Installed manually into Arduino IDE library folder) ESP8266 BOARD: 2.5.2 (Installed with board manager from Arduino IDE)

    TEST CODE: (ArduinoBlink.ino)

    
    #include <elk.h>  // Sketch -> Add File -> elk.h and elk.c
    
    extern "C" void myDelay(int milli) {
      delay(milli);
    }
    extern "C" void myWrite(int pin, int val) {
      digitalWrite(pin, val);
    }
    
    void setup() {
      void *mem = malloc(500);
      struct js *js = js_create(mem, 500);
      js_ffi(js, myDelay, "vi");
      js_ffi(js, myWrite, "vii");
      js_eval(js,
              "while (1) { "
              "  myWrite(13, 0); "
              "  myDelay(100); "
              "  myWrite(13, 1); "
              "  myDelay(100); "
              "}",
              -1);
    }
    
    void loop() {
      delay(1000);
    }
    
    

    ESP8266 OUTPUT:

    /home/name/.arduino15/packages/esp8266/tools/xtensa-lx106-elf-gcc/2.5.0-3-20ed2b9/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: ArduinoBlink.ino.elf section `.text1' will not fit in region `iram1_0_seg'
    collect2: error: ld returned 1 exit status
    exit status 1
    Error compiling for board Generic ESP8266 Module.
    
    

    NOTES: Generic ESP8266 4MB (1MB spiffs)

    AVR OUTPUT:

    /home/name/Arduino/libraries/elk/src/atmega328p/libelk.a: In function `js_object_first_prop':
    /Users/lsm/src/cpqelk/elk.c:517: undefined reference to `__mulhi3'
    /home/name/Arduino/libraries/elk/src/atmega328p/libelk.a: In function `js_object_key':
    /Users/lsm/src/cpqelk/elk.c:529: undefined reference to `__mulhi3'
    /home/name/Arduino/libraries/elk/src/atmega328p/libelk.a: In function `js_ref':
    /Users/lsm/src/cpqelk/elk.c:787: undefined reference to `__mulhi3'
    /home/name/Arduino/libraries/elk/src/atmega328p/libelk.a: In function `js_get_prop':
    /Users/lsm/src/cpqelk/elk.c:802: undefined reference to `__mulhi3'
    /home/name/Arduino/libraries/elk/src/atmega328p/libelk.a: In function `js_object_first_prop':
    /Users/lsm/src/cpqelk/elk.c:517: undefined reference to `__mulhi3'
    /home/name/Arduino/libraries/elk/src/atmega328p/libelk.a:/Users/lsm/src/cpqelk/elk.c:657: more undefined references to `__mulhi3' follow
    collect2: error: ld returned 1 exit status
    exit status 1
    Error compiling for board Arduino/Genuino Uno.
    
    

    NOTES:

    I had to rename avr to atmega328p in the src folder ...

    opened by sfranzyshen 14
  • Cannot make simple program work

    Cannot make simple program work

    I'm trying this code

    print("what is the value?");
    let obj = {sensor: 10.0}
    if (obj.sensor >= 1) {
        print("greater");
    } else {
        print("smaller");
    };
    

    which correctly shows the first "what is the value?" line, but then exits without showing either of the other print line, and I'm not sure why?

    opened by maelp 10
  • 'equal' & 'not equal' is not working ...

    'equal' & 'not equal' is not working ...

    LINUX_DIST="Linux Mint 18.3 Sylvia MATE 32-bit" KERNEL=Linux computer 4.10.0-38-generic i686 i686 i686 GNU/Linux

    ARDUINO IDE: 1.8.10 (32bit) elk Library: version 0.0.19 (Installed using Arduino IDE library manager) ESP32 boards (DEVKIT): 1.0.4 (Installed with board manager from Arduino IDE)

    From the elk github page ... it reads

    All but !=, ==. Use !==, === instead

    but the following code demonstrates that '===' isn't working (I also tested '==' ... as well as tested '!==' and '!=' with same result ... ERROR 1898)

    code

        v = js_eval(js, "if(1 === 1) { print('working ...'); return 1; } print('not working ...'); return 0;", 0);
        Serial.print("test result: "); Serial.println(js_str(js, v));
        js_gc(js, v);
    

    result test result: ERROR 1898

    if I replace '===' with either the '>=' or '<=' then that works as expected ... but not what I need

    result

    "working ..."
    test result: 1
    
    opened by sfranzyshen 9
  • Garbage collection doesn't fixup object references to upper and deletes used entities from memory.

    Garbage collection doesn't fixup object references to upper and deletes used entities from memory.

    Hello again. While further testing Elk on my ESP project and after incorporation of my previous fix, I encountered another issue with the garbage collection. It only occurs in certain circumstances and produces unpredictable behavior.

    In my project it was very reproducible and failed every time I ran this minimized test script: let fn=function(x) { let i=0; while(i<x) {res=res+"!"; i++; }; }; let res="123"; fn(3); In my ESP-sketch this script results in a WDT-reset. Different variants of this script often wouldn't show the issue the same way. The proper functioning should be that global variable res is set to the value of "123!!!".

    After lengthy debugging by inserting debug dumps and debug printfs in js_stmt() and in js_delete_marked_entities() I think I found the issue. Long story short it appears that js_fixup_offsets() doesn't fixup the offset to upper for objects. Having an incorrect reference in upper can result in incorrect deletions in subsequent garbage collections, giving further issues, During my debug sessions I saw the upper reference remained the same after in-between T_STR values for res were deleted by CG. It think this issue floated to the surface now because of the more frequent garbage collection.

    After first trying to understand your code, by analyzing debug output and based on that adding comments, I managed to make the above script function work without generating a WDT reset. This is the modified version of js_fixup_offsets():

    static void js_fixup_offsets(struct js *js, jsoff_t start, jsoff_t size) {
      // fixup all entities prior to moving data at start for deleting the start entity (which is marked for deletion) 
      for (jsoff_t n, v, off = 0; off < js->brk; off += n) {  // start from 0!
        v = loadoff(js, off);       // load 4-byte offset value of item at off. 
                                             // For objects v is only the offset of its first property. For properties it is the offset of the next item (0 if first)
        n = esize(v & ~GCMASK);
        if (v & GCMASK) continue;  // To be deleted, don't bother
        if ((v & 3) != T_OBJ && (v & 3) != T_PROP) continue;      // T_STR doesn't need further fixup
    
        // Fixup first/next offset for values beyond starting point 
        if (v > start) saveoff(js, off, v - size);      // save new offset (v-size) for first/next at off  
          
        // MMOLE 220216: proper fixup of upper offset for objects that don't fall under global scope (upper@0)
        if ((v & 3) == T_OBJ) {
          jsoff_t  u = loadoff(js, off+sizeof(jsoff_t));       // load 4-byte offset value of upper
          if(u > start)
            saveoff(js, off+sizeof(jsoff_t), u - size); // fixup upper, checked for negatives
        }
        
        // fixup property, key and value
        if ((v & 3) == T_PROP) {
          
          // fixup key offset
          jsoff_t koff = loadoff(js, off + sizeof(off));
          if (koff > start) saveoff(js, off + sizeof(off), koff - size);
          
          // fixup value data offset
          jsval_t val = loadval(js, off + sizeof(off) + sizeof(off));
          if (is_mem_entity(vtype(val)) && vdata(val) > start) {
            // printf("MV %u %lu -> %lu\n", off, vdata(val), vdata(val) - size);
            saveval(js, off + sizeof(off) + sizeof(off), mkval(vtype(val), vdata(val) - size));
          }
        }
      }
    
      // fixup callbacks
      for (jsoff_t i = 0; i < js->ncbs; i++) {
        jsoff_t base = js->size + i * 3 * sizeof(jsoff_t) + sizeof(jsoff_t);
        jsoff_t o1 = loadoff(js, base), o2 = loadoff(js, base + sizeof(o1));
        if (o1 > start) saveoff(js, base, o1 - size);
        if (o2 > start) saveoff(js, base + sizeof(jsoff_t), o2 - size);
      }
      // Fixup js->scope
      jsoff_t off = vdata(js->scope);
      if (off > start) js->scope = mkval(T_OBJ, off - size);
    }
    

    Disclaimer: Please note that I only tried to fix this issue as I encountered it. Using my code is at your own risk. No guaranties given and no liability accepted. Unfortunately I am not able to provide support....

    opened by maxint-rd 8
  • ESP8266 Fails to compile under the Arduino IDE

    ESP8266 Fails to compile under the Arduino IDE

    continued from issue #4

    LINUX_DIST="Linux Mint 18.3 Sylvia MATE 32-bit" KERNEL=Linux computer 4.10.0-38-generic i686 i686 i686 GNU/Linux

    ARDUINO IDE: 1.8.10 (32bit) Elk Library: release 0.0.7 (Installed manually into Arduino IDE library folder) (the same as 0.0.6 since nothing changed for the esp8266 ...) ESP8266 BOARD: 2.5.2 (Installed with board manager from Arduino IDE)

    ArduinoBlink.ino.elf section.text1' will not fit in region iram1_0_seg'

    it seams that the esp8266 platform is not a priority ...

    opened by sfranzyshen 8
  • Quickly running out of memory in while loop

    Quickly running out of memory in while loop

    Hello and thank you for sharing your code. It's a wonderful lightweight scripting engine that's easy to use while providing powerful possibilities.

    While trying to use it for my ESP8266 device, I unfortunately ran into some memory issues. This was the simple script I tried to run: let n=10; while(n-->0) { console.log("u"+str(n)+":"+str(usage())+"\n"); } The console.log function is a simple FFI printf addition I used to get debug output and usage calls your js_usage to report the percentage of memory used.

    When running this script I quickly ran into an OOM error. So first I increased my js-buffer, but that wasn't enough to allow a much greater amount of iterations (say n=100). I noticed that each iteration would substantially increase the amount of memory used. At the global scope the memory would be released by the garbage collection, but not within a block. After three days of debugging, I learned more of your code and I found a solution that worked for me.

    After adding some debug statements it appeared that the Fast scope GC line within js_block() never really released any memory:

    printf("js_block[s%u] pre GC %u (1:%u, 2:%u) \n", (jsoff_t) vdata(js->scope), js->brk, brk1, brk2);
    if (js->brk == brk2) js->brk = brk1;  // Fast scope GC (ORIGINAL CODE)
    printf("js_block[s%u] post GC %u-%u=%u\n", (jsoff_t) vdata(js->scope), js->brk, brk1, js->brk-brk1);
    

    I saw that js->brk would always be different from brk2. Skipping that check to always call js->brk = brk1 did set the memory-scope back, but resulted in corruption for instance with string variables that had text added within the loop.

    What finally worked for me was to call the slower js_gc() function instead:

    printf("js_block[s%u] pre GC %u (1:%u, 2:%u) \n", (jsoff_t) vdata(js->scope), js->brk, brk1, brk2);
    if((js->flags & F_NOEXEC) != F_NOEXEC)   // only do gc() when executing (i.e not when defining function in object)
      js_gc(js);   // Slower scope GC (NEW CODE)
    printf("js_block[s%u] post GC %u-%u=%u\n", (jsoff_t) vdata(js->scope), js->brk, brk1, js->brk-brk1);
    

    This modification allowed me to run much larger iterations without loosing any memory.

    From the looks of it, this issue was introduced in one of the may-2021 releases where a call to js_gc() within js_block() was removed, but I guess you have a better environment to debug and improve your code. I just wanted to share my experience and give people running into the same issue a possible solution (providing no guarantee that what worked for me will also work for you).

    Edit: had some issues with objects containing functions. Fixed by checking for F_NOEXEC

    opened by maxint-rd 7
  • string end with random character

    string end with random character

    Hi, I'm trying to use this library on ESP8266 (Core 2.6.3,with Arduino IDE 1.8.12).

    I added a C function "print" to JS,and found that: When calling this C function from JS with string as a parameter continuously,random characters appear at the end of the string.Even when calling not the same function continuously,random character will appear in the output from serial.But the first call outputed normal.That's was quiet strange.

    My sketch files are here: BlinkyJS-lucy2003.zip Note that I load scripts from SPIFFS,which means scripts will need to be uploaded by 'ESP8266 Sketch Data Upload' tool.Otherwise it will need to be wrote in the code.

    Any solution?Thanks you.

    opened by DynamicLoader 5
  • 0.0.17 breaks nested while loops  on the esp32 platform ...

    0.0.17 breaks nested while loops on the esp32 platform ...

    LINUX_DIST="Linux Mint 18.3 Sylvia MATE 32-bit" KERNEL=Linux computer 4.10.0-38-generic i686 i686 i686 GNU/Linux

    ARDUINO IDE: 1.8.10 (32bit) elk Library: version 0.0.17 (Installed using Arduino IDE library manager) ESP32 boards (DEVKIT): 1.0.4 (Installed with board manager from Arduino IDE)

    I have code that was working correctly with 0.0.16 on the esp32 platform that stopped working with version 0.0.17 ... the problem seams to be with nested while loops ... this code was working on the esp32 ... untested on other platforms

    Code snippet

    js_eval(js, "let theaterChase = 
    function(color, wait) { 
        a = 0; 
        while(a < 10) { 
            b = 0; 
            while(b < 3) { 
                clear(); 
                c = b; 
                while(c < numPixels()) { 
                    setPixelColor32(c, color); 
                    c = c + 3; 
                } 
                show(); 
                delay(wait); 
                b++; 
            } 
            a++; 
        } 
    }; 
    ", 0);
    
    
    opened by sfranzyshen 5
  • AVR under the Arduino IDE does not work

    AVR under the Arduino IDE does not work

    continued from issue #4

    LINUX_DIST="Linux Mint 18.3 Sylvia MATE 32-bit" KERNEL=Linux computer 4.10.0-38-generic i686 i686 i686 GNU/Linux

    ARDUINO IDE: 1.8.10 (32bit) elk Library: version 0.0.7 (Installed using Arduino IDE library manager) Arduino AVR boards (Nano): 1.8.1 (Installed with board manager from Arduino IDE)

    Directory for the Library In order to test ... I had to rename the avr/ folder under the library src folder to atmega328p/ I am assuming the libelk.a file was indeed built targeting the atmega328p processor ... or I would expected it not to link ... or run otherwise.

    Test Code:

      Serial.println(js_eval(js,
              "while (1) { "
              "  myWrite(13, 0); "
              "  myDelay(100); "
              "  myWrite(13, 1); "
              "  myDelay(100); "
              "}",
              -1));
    

    Responce: This code should block and loop ... it doesn't it returns this right away ... 4286578688

    opened by sfranzyshen 5
  • Question how to write on MCP23017

    Question how to write on MCP23017

    Hi there! I have been facing an issue with my code that I am unable to find out a solution (I don't think it is a issue with the library). I am using MCP23017 over I2C with my esp32 and I am unable to create a function that works to digitalWrite on MCP23017. Could someone help me? My code is the Esp32JS example with these add.

    This one is inside extern "C" void mcp_write(int pin, bool val) { mcp1.digitalWrite(pin, val); }

    This new line below `js_set(js, js_glob(js), "gpio", gpio);

    js_set(js, gpio, "mode", js_import(js, (uintptr_t) gpio_mode, "vii"));

    js_set(js, gpio, "write", js_import(js, (uintptr_t) gpio_write, "vii"));

    js_set(js, gpio, "read", js_import(js, (uintptr_t) gpio_read, "ii"));

    js_set(js, gpio, "mcpwrite", js_import(js, (uintptr_t) mcp_write, "vib")); //my new mcp write`

    When I do this I get this error: Guru Meditation Error: Core 1 panic'ed (IntegerDivideByZero). Exception was unhandled. Core 1 register dump: PC : 0x4011b26b PS : 0x00060830 A0 : 0x8011b6e8 A1 : 0x3ffb1e10 A2 : 0x00000133 A3 : 0x3ffb1e9c A4 : 0x00000032 A5 : 0x3ffbef4c A6 : 0x00004e20 A7 : 0x60013000 A8 : 0x00000113 A9 : 0x3ffb1df0 A10 : 0x00000000 A11 : 0x00000000 A12 : 0x3ffb8548 A13 : 0x3ffcd8e4 A14 : 0x00002710 A15 : 0x3ffbef4c SAR : 0x0000001c EXCCAUSE: 0x00000006 EXCVADDR: 0x00000000 LBEG : 0x4000c46c LEND : 0x4000c477 LCOUNT : 0x00000000

    Backtrace: 0x4011b26b:0x3ffb1e10 0x4011b6e5:0x3ffb1e40 0x4010f97d:0x3ffb1e60 0x4010fa31:0x3ffb1e90 0x4010fab1:0x3ffb1ec0 0x4010fb3d:0x3ffb1ee0 0x4010fc87:0x3ffb1f00 0x400d7c39:0x3ffb1f20 0x400ec36d:0x3ffb1f40 0x400f8d34:0x3ffb1f80 0x4011c38d:0x3ffb1fb0 0x40089911:0x3ffb1fd0

    Rebooting...

    opened by kaiquecav 4
  • missing type check for --/++ causes js.mem corruption

    missing type check for --/++ causes js.mem corruption

    Hello,

    I've stumbled upon a bug in do_op when handling TOK_POSTINC (and TOK_POSTDEC) assignment. There's a missing type check for lhs, which causes it to be used directly instead of as an offset into variables (if lhs was an object).

    Bug

    static jsval_t do_assign_op(struct js *js, uint8_t op, jsval_t l, jsval_t r) {
    ...
    jsval_t res = do_op(js, m[op - TOK_PLUS_ASSIGN], resolveprop(js, l), r);
    assign(js, l, res);
    

    in which resolveprop(js, l) will return l (T_NUM)

    calling do_op with TOK_POSTINC or TOK_POSTDEC, is turned into do_assign_op(..., TOK_PLUS_ASSIGN|TOK_MINUS_ASSIGN, lhs, ...) with lhs being a number (so resolveprop(..., lhs) = lhs).

    This causes OOB in struct js { uint8_t *mem; }.

    Example

    #include <stdio.h>
    #include "elk.h"
    int main(void) {
      char mem[200];
      struct js *js = js_create(mem, sizeof(mem));      // Create JS instance
      jsval_t result = js_eval(js, "1.1--;", ~0);       // Call sum
      printf("result: %s\n", js_str(js, result));       // result: 7
      return 0;
    }
    

    This causes a contrived memory corruption, bug somewhat controllable:

    0x403d80 <saveval+32>    mov    qword ptr [rax + rdx], rdi
    

    With rax pointing to js.mem, rdx set to 0x999999a0 and rdi to the full float 0x3fb99999999999a0

    The corruption is given as:

    *(js.mem+offset) = (val << 32) | offset;
    

    Fix suggestion

    Adding a type check to lhs before do_assign_op might be enough:

    if (vtype(lhs) != T_OBJ && vtype(lhs) != T_CODEREF) return js_err(js, "bad lhs");
    

    As such:

    --- a/elk.c
    +++ b/elk.c
    @@ -154,6 +154,7 @@ static void saveoff(struct js *js, jsoff_t off, jsoff_t val) { memcpy(&js->mem[o
     static void saveval(struct js *js, jsoff_t off, jsval_t val) { memcpy(&js->mem[off], &val, sizeof(val)); }
     static jsoff_t loadoff(struct js *js, jsoff_t off) { jsoff_t v = 0; assert(js->brk <= js->size); memcpy(&v, &js->mem[off], sizeof(v)); return v; }
     static jsoff_t offtolen(jsoff_t off) { return (off >> 2) - 1; }
     static jsoff_t vstrlen(struct js *js, jsval_t v) { return offtolen(loadoff(js, vdata(v))); }
     static jsval_t loadval(struct js *js, jsoff_t off) { jsval_t v = 0; memcpy(&v, &js->mem[off], sizeof(v)); return v; }
     static jsval_t upper(struct js *js, jsval_t scope) { return mkval(T_OBJ, loadoff(js, vdata(scope) + sizeof(jsoff_t))); }
    @@ -910,11 +911,23 @@ static jsval_t do_op(struct js *js, uint8_t op, jsval_t lhs, jsval_t rhs) {
         case TOK_TYPEOF:  return js_mkstr(js, typestr(vtype(r)), strlen(typestr(vtype(r))));
         case TOK_CALL:    return do_call_op(js, l, r);
         case TOK_ASSIGN:  return assign(js, lhs, r);
    -    case TOK_POSTINC: { do_assign_op(js, TOK_PLUS_ASSIGN, lhs, tov(1)); return l; }
    -    case TOK_POSTDEC: { do_assign_op(js, TOK_MINUS_ASSIGN, lhs, tov(1)); return l; }
    +    case TOK_POSTINC: {
    +
    +                          if (vtype(lhs) != T_OBJ && vtype(lhs) != T_CODEREF) return js_err(js, "bad lhs");
    +                          do_assign_op(js, TOK_PLUS_ASSIGN, lhs, tov(1));
    +                          return l;
    +                      }
    +    case TOK_POSTDEC: {
    +                          if (vtype(lhs) != T_OBJ && vtype(lhs) != T_CODEREF) return js_err(js, "bad lhs");
    +                          do_assign_op(js, TOK_MINUS_ASSIGN, lhs, tov(1));
    +                          return l;
    +                      }
         case TOK_NOT:     if (vtype(r) == T_BOOL) return mkval(T_BOOL, !vdata(r)); break;
       }
    -  if (is_assign(op))    return do_assign_op(js, op, lhs, r);
    +  if (is_assign(op)) {
    +    if (vtype(lhs) == T_OBJ || vtype(lhs) == T_CODEREF) return do_assign_op(js, op, lhs, r);
    +    else return js_err(js, "bad lhs");
    +  }
       if (vtype(l) == T_STR && vtype(r) == T_STR) return do_string_op(js, op, l, r);
       if (is_unary(op) && vtype(r) != T_NUM) return js_err(js, "type mismatch");
       if (!is_unary(op) && op != TOK_DOT && (vtype(l) != T_NUM || vtype(r) != T_NUM)) return js_err(js, "type mismatch");
    
    opened by profjohnhoff 0
Releases(2.2.0)
Owner
Cesanta Software
Embedded Communication
Cesanta Software
Re-usable test jig controller

Hubble Hubble is Winterbloom's re-usable development board for test jigs. Repository layout This repository contains the hardware design files and fir

Winterbloom 11 Dec 18, 2022
Golang bindings of Sciter: the Embeddable HTML/CSS/script engine for modern UI development

Go bindings for Sciter Check this page for other language bindings (Delphi / D / Go / .NET / Python / Rust). Attention The ownership of project is tra

Terra Informatica Software, Inc 2.5k Dec 23, 2022
A subset of WidgetsFlutterBinding specifically for initializing the ServicesBinding.

flutter_services_binding A subset of WidgetsFlutterBinding specifically for initializing the ServicesBinding. When executing runApp within a custom Zo

Felix Angelov 27 Nov 17, 2022
Code for the paper Succinct k-mer Set Representations Using Subset Rank Queries on the Spectral Burrows-Wheeler Transform (SBWT)

SBWT This is the code for the paper Succinct k-mer Set Representations Using Subset Rank Queries on the Spectral Burrows-Wheeler Transform (SBWT). The

Algorithmic Bioinformatics Group @ University of Helsinki 14 Jun 18, 2022
A tiny but (will be) featured rasterizer

Introduction This is my own rasterizer implementation. Developed in C++17. Usage compile ./rasterizer [model], model names plz refer to this README Ro

Hyiker 8 Aug 22, 2022
Love 6's Regular Expression Engine. Support Concat/Select/Closure Basic function. Hope u can enjoy this tiny engine :)

Regex_Engine Love 6's Blog Website: https://love6.blog.csdn.net/ Love 6's Regular Expression Engine Hope u can love my tiny regex engine :) maybe a fe

Love6 2 May 24, 2022
Android Bindings for QuickJS, A fine little javascript engine.

quickjs-android quickjs-android 是 QuickJS JavaScript 引擎的 Android 接口框架,整体基于面向对象设计,提供了自动GC功能,使用简单。armeabi-v7a 的大小仅 350KB,是 Google V8 不错的替代品,启动速度比 V8 快,内

Wiki 121 Dec 28, 2022
Header-only ECMAScript (JavaScript) compatible regular expression engine

SRELL (std::regex-like library) is a regular expression template library for C++ and has native support for UTF-8, UTF-16, and UTF-32. This is up-to-d

Dmitry Atamanov 4 Mar 11, 2022
Embedded JavaScript engine for C/C++

mJS: Restricted JavaScript engine Overview mJS is designed for microcontrollers with limited resources. Main design goals are: small footprint and sim

Cesanta Software 1.7k Dec 30, 2022
A dependency free, embeddable debugger for Lua in a single file (.lua or .c)

debugger.lua A simple, embedabble debugger for Lua 5.x, and LuaJIT 2.x. debugger.lua is a simple, single file, pure Lua debugger that is easy to integ

Scott Lembcke 600 Dec 31, 2022
Zep - An embeddable editor, with optional support for using vim keystrokes.

Zep - A Mini Editor Zep is a simple embeddable editor, with a rendering agnostic design and optional Vim mode. It is built as a shared modern-cmake li

Rezonality - 703 Dec 30, 2022
Treexy is a library that implements a compact hierarchical data structure that can store and manipulate volumetric data, discretized on a three-dimensional grid

Treexy is a library that implements a compact hierarchical data structure that can store and manipulate volumetric data, discretized on a three-dimens

Davide Faconti 324 Jan 4, 2023
A STM32F428I-DISC1 based sonar which implements the HC-SR04 ultrasonic sensor and a mini-servo

STM32 Sonar Youtube This is a Sonar based on the STM32 microcontroller. It uses the following parts: STM32F428I-DISC1 Devboard HC-SR04 ultrasonic sens

Robert Sorić 2 Nov 9, 2022
Implements a complete GNU/Linux program

Linux-app Implements a complete GNU/Linux program The example program is part of a system for monitoring a running GNU/Linux system. It includes these

Spoorthi Naik 1 Jun 19, 2022
Example code for the research paper "Masked Software Occlusion Culling"; implements an efficient alternative to the hierarchical depth buffer algorithm.

MaskedOcclusionCulling This code accompanies the research paper "Masked Software Occlusion Culling", and implements an efficient alternative to the hi

null 536 Dec 22, 2022
Implements a Windows service (in a DLL) that removes the rounded corners for windows in Windows 11

ep_dwm Implements a Windows service that removes the rounded corners for windows in Windows 11. Tested on Windows 11 build 22000.434. Pre-compiled bin

Valentin-Gabriel Radu 24 Dec 29, 2022
Experimental Worms Armageddon WormKit module that implements real-time online multiplayer for racing schemes

wkRealTime v.0.0.4d Experimental Worms Armageddon WormKit module that implements real-time online multiplayer for racing schemes. Installation Place w

null 15 Jul 12, 2022
CppUTest For QP/C++ implements a CppUTest port of the QP Framework, with supporting utilities, enabling easy host based unit testing of active objects.

CppUTest for the QP/C++ Real-Time Embedded Framework Build and Test status: Copyright Matthew Eshleman If this project inspires your team to select th

Cove Mountain Software 5 Sep 14, 2022