{"id":13424642,"url":"https://github.com/cesanta/elk","last_synced_at":"2025-05-14T14:09:45.408Z","repository":{"id":37412785,"uuid":"213346952","full_name":"cesanta/elk","owner":"cesanta","description":"A low footprint JavaScript engine for embedded systems","archived":false,"fork":false,"pushed_at":"2025-03-05T18:02:38.000Z","size":4763,"stargazers_count":1713,"open_issues_count":4,"forks_count":88,"subscribers_count":34,"default_branch":"master","last_synced_at":"2025-04-13T16:50:24.255Z","etag":null,"topics":["embedded","engine","javascript","javascript-engine","js","small","tiny"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cesanta.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-10-07T09:47:17.000Z","updated_at":"2025-04-11T13:28:45.000Z","dependencies_parsed_at":"2023-11-18T18:22:52.445Z","dependency_job_id":"372a7d3c-6475-47cb-ba8f-9254b80c0e92","html_url":"https://github.com/cesanta/elk","commit_stats":null,"previous_names":[],"tags_count":35,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Felk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Felk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Felk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Felk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cesanta","download_url":"https://codeload.github.com/cesanta/elk/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254160482,"owners_count":22024569,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["embedded","engine","javascript","javascript-engine","js","small","tiny"],"created_at":"2024-07-31T00:00:57.266Z","updated_at":"2025-05-14T14:09:40.381Z","avatar_url":"https://github.com/cesanta.png","language":"C","readme":"# Elk: a tiny JS engine for embedded systems\n\n[![Build Status](https://github.com/cesanta/elk/workflows/build/badge.svg)](https://github.com/cesanta/elk/actions)\n[![License: AGPL/Commercial](https://img.shields.io/badge/License-AGPL%20or%20Commercial-green.svg)](https://opensource.org/licenses/gpl-2.0.php)\n[![Code Coverage](https://codecov.io/gh/cesanta/elk/branch/master/graph/badge.svg)](https://codecov.io/gh/cesanta/elk)\n\n\nElk is a tiny embeddable JavaScript engine that implements a small but usable\nsubset of ES6. It is designed for microcontroller development. Instead of\nwriting firmware code entirely in C/C++, Elk allows to add JavaScript\ncustomisations to the firmware developed in C - which is a great way to let\ncustomers to extend/customise device functionality.\n\nElk features include:\n\n- Cross platform. Works anywhere from 8-bit microcontrollers to 64-bit servers\n- Zero dependencies. Builds cleanly by ISO C or ISO C++ compilers\n- Easy to embed: just copy `elk.c` and `elk.h` to your source tree\n- Small and simple embedding API\n- Does not use malloc. Operates with a given memory buffer only\n- Small footprint: about 20KB on flash/disk, about 100 bytes RAM for core VM\n- No bytecode. Interprets JS code directly\n\nElk approach is different from other scripting environments like micropython,\nwhich provide a complete JS API for everything.  Elk is completely bare, it\ndoes not even have a standard library. All required functionality is supposed\nto be imported from C/C++ firmware, and JS code simply orchestrates things.\nThat leaves Elk very minimal and tunable.\n\nBelow is a blinky demonstration on a classic Arduino Nano board which has\n2K RAM and 30K flash (see [full sketch](examples/BlinkyJS/BlinkyJS.ino)):\n\n![Elk on Arduino Nano](test/nano.gif)\n\n\n## JavaScript on ESP32\n\nThe [Esp32JS](examples/Esp32JS) Arduino sketch is an example of Elk integration\nwith ESP32. Flash this sketch on your ESP32 board, go to\nhttps://cesanta.com/elk, and get a JavaScript development environment\ninstantly! All components, including ESP32 firmware and Web editor, are open.\nHere how it looks like:\n\n![](test/editor.png)\n\nThe example JS firmware implements a classic blinky that uses timers imported\nfrom C.\n\n\n## Call JavaScript from C\n```c\n#include \u003cstdio.h\u003e\n#include \"elk.h\"\n\nint main(void) {\n  char mem[200];\n  struct js *js = js_create(mem, sizeof(mem));  // Create JS instance\n  jsval_t v = js_eval(js, \"1 + 2 * 3\", ~0);     // Execute JS code\n  printf(\"result: %s\\n\", js_str(js, v));        // result: 7\n  return 0;\n}\n```\n\n## Call C from JavaScript\n\nThis demonstrates how JS code can import and call existing C functions:\n\n```c\n#include \u003cstdio.h\u003e\n#include \"elk.h\"\n\n// C function that adds two numbers. Will be called from JS\njsval_t sum(struct js *js, jsval_t *args, int nargs) {\n  if (nargs != 2) return js_err(js, \"2 args expected\");\n  double a = js_getnum(args[0]);  // Fetch 1st arg\n  double b = js_getnum(args[1]);  // Fetch 2nd arg\n  return js_mknum(a + b);\n}\n\nint main(void) {\n  char mem[200];\n  struct js *js = js_create(mem, sizeof(mem));      // Create JS instance\n  js_set(js, js_glob(js), \"sum\", js_mkfun(sum)));   // Import sum()\n  jsval_t result = js_eval(js, \"sum(3, 4);\", ~0);   // Call sum\n  printf(\"result: %s\\n\", js_str(js, result));       // result: 7\n  return 0;\n}\n```\n\n## Supported features\n\n- Operations: all standard JS operations except:\n   - `!=`, `==`. Use strict comparison `!==`, `===`\n   - No computed member access `a[b]`\n   - No exponentiation operation `a ** b`\n- Typeof: `typeof('a') === 'string'`\n- For loop: `for (...;...;...)  ...`\n- Conditional: `if (...) ... else ...`\n- Ternary operator `a ? b : c`\n- Simple types: `let a, b, c = 12.3, d = 'a', e = null, f = true, g = false;`\n- Functions: `let f = function(x, y) { return x + y; };`\n- Objects: `let obj = {f: function(x) { return x * 2}}; obj.f(3);`\n- Every statement must end with a semicolon `;`\n- Strings are binary data chunks, not Unicode strings: `'Київ'.length === 8`\n\n## Not supported features\n\n- No `var`, no `const`. Use `let` (strict mode only)\n- No `do`, `switch`, `while`. Use `for`\n- No `=\u003e` functions. Use `let f = function(...) {...};`\n- No arrays, closures, prototypes, `this`, `new`, `delete`\n- No standard library: no `Date`, `Regexp`, `Function`, `String`, `Number`\n\n## Performance\n\nSince Elk parses and interprets JS code on the fly, it is not meant to be\nused in a performance-critical scenarios. For example, below are the numbers\nfor a simple loop code on a different architectures.\n\n```javascript\nfor (let i = 0; i \u003c 100; i++) true;\n// 97 milliseconds on a 16Mhz 8-bit Atmega328P (Arduino Uno and alike)\n// 16 milliseconds on a 48Mhz SAMD21\n//  5 milliseconds on a 133Mhz Raspberry RP2040\n//  2 milliseconds on a 240Mhz ESP32\n```\n\n## Build options\n\nAvailable preprocessor definitions:\n\n| Name         | Default   | Description |\n| ------------ | --------- | ----------- |\n|`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. |\n|`JS_DUMP`     | undefined | Define to enable `js_dump(struct js *)` function which prints JS memory internals to stdout |\n\nNote: on ESP32 or ESP8266, compiled functions go into the `.text` ELF\nsection and subsequently into the IRAM MCU memory. It is possible to save\nIRAM space by copying Elk code into the irom section before linking.\nFirst, compile the object file, then rename `.text` section, e.g. for ESP32:\n\n```sh\n$ xtensa-esp32-elf-gcc $CFLAGS elk.c -c elk.tmp\n$ xtensa-esp32-elf-objcopy --rename-section .text=.irom0.text elk.tmp elk.o\n```\n\nNote: Elk uses `snprintf()` standard function to format numbers (double).\nOn some architectures, for example AVR Arduino, that standard function does\nnot support float formatting - therefore printing numbers may output nothing\nor `?` symbols.\n\n## API reference\n\n### js\\_create()\n\n```c\nstruct js *js_create(void *buf, size_t len);\n```\n\nInitialize JS engine in a given memory block. Elk will only use that memory\nblock to hold its runtime, and never use any extra memory.\nReturn: a non-`NULL` opaque pointer on success, or `NULL` when\n`len` is too small. The minimum `len` is about 100 bytes.\n\nThe given memory buffer is laid out in the following way:\n```\n  | \u003c-------------------------------- len ------------------------------\u003e |\n  | struct js, ~100 bytes  |   runtime vars    |    free memory           | \n```\n\n### js\\_eval()\n\n```c\njsval_t js_eval(struct js *, const char *buf, size_t len);\n```\n\nEvaluate JS code in `buf`, `len` and return result of the evaluation.  During\nthe evaluation, Elk stores variables in the \"runtime\" memory section. When\n`js_eval()` returns, Elk does not keep any reference to the evaluated code: all\nstrings, functions, etc, are copied to the runtime.\n\nImportant note: the returned result is valid only before the next call to\n`js_eval()`. The reason is that `js_eval()` triggers a garbage collection.\nA garbage collection is mark-and-sweep, run before every top-level statement\ngets executed.\n\nThe runtime footprint is as follows:\n- An empty object is 8 bytes\n- Each object property is 16 bytes\n- A string is 4 bytes + string length, aligned to 4 byte boundary\n- A C stack usage is ~200 bytes per nested expression evaluation\n\n\n### js\\_str()\n\n```c\nconst char *js_str(struct js *, jsval_t val);\n```\n\nStringify JS value `val` and return a pointer to a 0-terminated result.\nThe string is allocated in the \"free\" memory section. If there is no\nenough space there, an empty string is returned. The returned pointer\nis valid until the next `js_eval()` call.\n\n### js\\_glob()\n\n```c\njsval_t js_glob(struct js *);\n```\n\nReturn global JS object, i.e. a root namespace.\n\n\n### js\\_mk\\*()\n\n```c\njsval_t js_mkundef(void);  // Create undefined\njsval_t js_mknull(void);   // Create null, null, true, false\njsval_t js_mktrue(void);   // Create true\njsval_t js_mkfalse(void);  // Create false\njsval_t js_mkstr(struct js *, const void *, size_t);           // Create string\njsval_t js_mknum(double);                                      // Create number\njsval_t js_mkerr(struct js *js, const char *fmt, ...);         // Create error\njsval_t js_mkfun(jsval_t (*fn)(struct js *, jsval_t *, int));  // Create func\njsval_t js_mkobj(struct js *);                                 // Create object\nvoid js_set(struct js *, jsval_t, const char *, jsval_t);      // Set obj attr\n```\n\nCreate JS values from C values\n\n### js\\_get\\*()\n\n```c\nenum { JS_UNDEF, JS_NULL, JS_TRUE, JS_FALSE, JS_STR, JS_NUM, JS_ERR, JS_PRIV };\nint js_type(jsval_t val);       // Return JS value type\ndouble js_getnum(jsval_t val);  // Get number\nint js_getbool(jsval_t val);    // Get boolean, 0 or 1\nchar *js_getstr(struct js *js, jsval_t val, size_t *len);  // Get string\n```\n\nExtract C values from JS values\n\n### js\\_chkargs()\n\n```c\nbool js_chkargs(jsval_t *args, int nargs, const char *spec);\n```\n\nA helper function that checks a validity of the arguments passed to a function.\nA `spec` is a 0-terminated string where each character represents a type of\nthe expected argument: `b` for `bool`, `d` for number, `s` for string, `j`\nfor any other JS value.\n\nUsage example - a C function that implements a JS function\n`greater_than(number1, number2)`:\n\n```c\nstatic jsval_t js_gt(struct js *js, jsval_t *args, int nargs) {\n  if (!js_chkargs(args, nargs, \"dd\")) return js_mkerr(js, \"bad args!\");\n  return js_getnum(args[0]) \u003e js_getnum(args[1]) ? js_mktrue() : js_mkfalse();\n}\n```\n\n### js\\_setmaxcss()\n\n```c\nvoid js_setmaxcss(struct js *, size_t max);\n```\n\nSet maximum allowed C stack size usage\n\n### js\\_stats()\n\n```c\nvoid js_stats(struct js *, size_t *total, size_t *min, size_t *cstacksize);\n```\n\nReturn resource usage statistics: `total` for total usable JS memory, `min`\nfor the lowest free JS memory observed (low watermark), and `cstacksize` for\nthe largest C stack usage observed.\n\n### js\\_dump()\n\n```c\nvoid js_dump(struct js *);\n```\n\nPrint debug info about the current JS state to stdout. Requires `-DJS_DUMP`\n\n## LICENSE\n\nDual license: AGPLv3 or commercial. For commercial licensing, technical support\nand integration help, please contact us at https://cesanta.com/contact.html\n\n# See also\n- [Mongoose Web Server Library](https://mongoose.ws/) - a robust, open-source solution licensed under GPLv2, designed to seamlessly integrate web server functionality into your embedded devices. \n- With complementary [Mongoose Wizard](https://mongoose.ws/wizard/) - a no-code visual tool that enables rapid WebUI creation without the need for frontend expertise.\n","funding_links":[],"categories":["JavaScript框架","C","Scripts"],"sub_categories":["其他_文本生成、文本对话"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesanta%2Felk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcesanta%2Felk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesanta%2Felk/lists"}