{"id":13414464,"url":"https://github.com/cesanta/mjs","last_synced_at":"2025-05-14T07:09:17.379Z","repository":{"id":37502601,"uuid":"77057142","full_name":"cesanta/mjs","owner":"cesanta","description":"Embedded JavaScript engine for C/C++","archived":false,"fork":false,"pushed_at":"2025-04-22T16:50:50.000Z","size":5273,"stargazers_count":1958,"open_issues_count":191,"forks_count":179,"subscribers_count":73,"default_branch":"master","last_synced_at":"2025-04-22T17:55:37.257Z","etag":null,"topics":["embedded","esp32","esp8266","javascript","js","mcu"],"latest_commit_sha":null,"homepage":"https://mongoose-os.com","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":"2016-12-21T14:11:16.000Z","updated_at":"2025-04-22T16:50:48.000Z","dependencies_parsed_at":"2024-01-05T20:59:47.347Z","dependency_job_id":"779c6bc5-67d7-4dba-841c-2d3c75673477","html_url":"https://github.com/cesanta/mjs","commit_stats":{"total_commits":565,"total_committers":18,"mean_commits":31.38888888888889,"dds":0.6513274336283186,"last_synced_commit":"b1b6eac6b1e5b830a5cb14f8f4dc690ef3162551"},"previous_names":[],"tags_count":63,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fmjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fmjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fmjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fmjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cesanta","download_url":"https://codeload.github.com/cesanta/mjs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254092785,"owners_count":22013290,"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","esp32","esp8266","javascript","js","mcu"],"created_at":"2024-07-30T21:00:21.775Z","updated_at":"2025-05-14T07:09:12.347Z","avatar_url":"https://github.com/cesanta.png","language":"C","readme":"mJS: Restricted JavaScript engine\n====================================\n\n[![License](https://img.shields.io/badge/license-GPL_2-green.svg)](https://github.com/cesanta/mjs/blob/master/LICENSE)\n\n# Overview\n\nmJS is designed for microcontrollers with limited resources. Main design\ngoals are: small footprint and simple C/C++ interoperability. mJS\nimplements a strict subset of ES6 (JavaScript version 6):\n\n- Any valid mJS code is a valid ES6 code.\n- Any valid ES6 code is not necessarily a valid mJS code.\n\nOn 32-bit ARM mJS engine takes about 50k of flash memory, and less than 1k\nof RAM (see [intro article](https://mongoose-os.com/blog/mjs-a-new-approach-to-embedded-scripting/)).\nmJS is part of [MongooseOS](https://mongoose-os.com), \nwhere it enables scripting for IoT devices.\n\n# Restrictions\n\n- No standard library. No String, Number, RegExp, Date, Function, etc.\n- **`JSON.parse()`** and **`JSON.stringify()`** are available.\n- No closures, only lexical scoping (i.e. nested functions are allowed).\n- No exceptions.\n- No `new`. In order to create an object with a custom prototype, use\n  **`Object.create()`**, which is available.\n- Strict mode only.\n- No `var`, only `let`.\n- No `for..of`, `=\u003e`, destructors, generators, proxies, promises.\n- No getters, setters, `valueOf`, prototypes, classes, template strings.\n- No `==` or `!=`, only `===` and `!==`.\n- mJS strings are byte strings, not Unicode strings: `'ы'.length === 2`,\n  `'ы'[0] === '\\xd1'`, `'ы'[1] === '\\x8b'`.\n  mJS string can represent any binary data chunk.\n\n# Built-in API\n\n\n\u003cdl\u003e\n  \u003cdt\u003e\u003ctt\u003eprint(arg1, arg2, ...);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003ePrint arguments to stdout, separated by space.\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003eload('file.js', obj);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eExecute file \u003ctt\u003efile.js\u003c/tt\u003e. \u003ctt\u003eobj\u003c/tt\u003e paramenter is\n  optional. \u003ctt\u003eobj\u003c/tt\u003e is a global namespace object.\n  If not specified, a current global namespace is passed to the script,\n  which allows \u003ctt\u003efile.js\u003c/tt\u003e to modify the current namespace.\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003edie(message);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eExit interpreter with the given error message\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003elet value = JSON.parse(str);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eParse JSON string and return parsed value.\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003elet str = JSON.stringify(value);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eGet string representation of the mJS value.\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003elet proto = {foo: 1}; let o = Object.create(proto);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eCreate an object with the provided prototype.\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003e'some_string'.slice(start, end);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eReturn a substring between two indices. Example:\n      \u003ctt\u003e'abcdef'.slice(1,3) === 'bc';\u003c/tt\u003e\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003e'abc'.at(0);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eReturn numeric byte value at given string index. Example:\n      \u003ctt\u003e'abc'.at(0) === 0x61;\u003c/tt\u003e\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003e'abc'.indexOf(substr[, fromIndex]);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eReturn index of first occurence of substr within the string or `-1`\n  if not found.\n      \u003ctt\u003e'abc'.indexOf('bc') === 1;\u003c/tt\u003e\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003echr(n);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eReturn 1-byte string whose ASCII code is the integer `n`. If `n` is\n    not numeric or outside of `0-255` range, `null` is returned. Example:\n      \u003ctt\u003echr(0x61) === 'a';\u003c/tt\u003e\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003elet a = [1,2,3,4,5]; a.splice(start, deleteCount, ...);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eChange the contents of an array by removing existing elements and/or\n    adding new elements. Example:\n  \u003ctt\u003elet a = [1,2,3,4,5]; a.splice(1, 2, 100, 101, 102); a === [1,100,101,102,4,5];\u003c/tt\u003e\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003elet s = mkstr(ptrVar, length);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eCreate a string backed by a C memory chunk. A string \u003ctt\u003es\u003c/tt\u003e starts\n  at memory location \u003ctt\u003eptrVar\u003c/tt\u003e, and is \u003ctt\u003elength\u003c/tt\u003e bytes long.\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003elet s = mkstr(ptrVar, offset, length, copy = false);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eLike `mkstr(ptrVar, length)`, but string \u003ctt\u003es\u003c/tt\u003e starts\n  at memory location \u003ctt\u003eptrVar + offset\u003c/tt\u003e, and the caller can specify\n  whether the string needs to be copied to the internal mjs buffer. By default\n  it's not copied.\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003elet f = ffi('int foo(int)');\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003eImport C function into mJS. See next section.\u003c/dd\u003e\n\n  \u003cdt\u003e\u003ctt\u003egc(full);\u003c/tt\u003e\u003c/dt\u003e\n  \u003cdd\u003ePerform garbage collection. If `full` is `true`, reclaim RAM to OS.\u003c/dd\u003e\n\u003c/dl\u003e\n\n# C/C++ interoperability\n\nmJS requires no glue code. The mJS's Foreign Function Interface (FFI)\nallows the user to call an existing C function with an arbitrary signature.\nCurrently mJS provides a simple implementation of the FFI trampoline\nthat supports up to 6 32-bit arguments, or up to 2 64-bit arguments:\n\n```javascript\nlet floor = ffi('double floor(double)');\nprint(floor(1.23456));\n```\n\nFunction arguments should be simple: only `int`, `double`, `char *`, `void *`\nare supported. Use `char *` for NUL-terminated C strings, `void *` for any\nother pointers. In order to import more complex functions\n(e.g. the ones that use structures as arguments), write wrappers.\n\n## Callbacks\n\nCallbacks are implemented similarly. Consider that you have a C function\nthat takes a callback and user data `void *` pointer, which should be marked\nas `userdata` in the signature:\n\n```C\nvoid timer(int seconds, void (*callback)(int, void *), void *user_data);\n```\n\nThis is how to make an mJS callback - note the usage of `userdata`:\n\n```javascript\nlet Timer = {\n  set: ffi('void timer(int, void (*)(int, userdata), userdata)')\n};\n\nTimer.set(200, function(t) {\n  print('Time now: ', t);\n}, null);\n```\n\n## Symbol resolver\n\nIn order to make FFI work, mJS must be able to get the address of a C\nfunction by its name. On POSIX systems, `dlsym()` API can do that. On\nWindows, `GetProcAddress()`. On embedded systems, a system resolver should\nbe either manually written, or be implemented with some aid from a firmware\nlinker script. mJS resolver uses `dlsym`-compatible signature.\n\n## Converting structs to objects\n\nmJS provides a helper to facilitate coversion of C structs to JS objects.\nThe functions is called `s2o` and takes two parameters: foreign pointer to\nthe struct and foreign pointer to the struct's descriptor which specifies\nnames and offsets of the struct's members. Here's an simple example:\n\nC/C++ side code:\n```c\n#include \"mjs.h\"\n\nstruct my_struct {\n  int a;\n  const char *b;\n  double c;\n  struct mg_str d;\n  struct mg_str *e;\n  float f;\n  bool g;\n};\n\nstatic const struct mjs_c_struct_member my_struct_descr[] = {\n  {\"a\", offsetof(struct my_struct, a), MJS_STRUCT_FIELD_TYPE_INT, NULL},\n  {\"b\", offsetof(struct my_struct, b), MJS_STRUCT_FIELD_TYPE_CHAR_PTR, NULL},\n  {\"c\", offsetof(struct my_struct, c), MJS_STRUCT_FIELD_TYPE_DOUBLE, NULL},\n  {\"d\", offsetof(struct my_struct, d), MJS_STRUCT_FIELD_TYPE_MG_STR, NULL},\n  {\"e\", offsetof(struct my_struct, e), MJS_STRUCT_FIELD_TYPE_MG_STR_PTR, NULL},\n  {\"f\", offsetof(struct my_struct, f), MJS_STRUCT_FIELD_TYPE_FLOAT, NULL},\n  {\"g\", offsetof(struct my_struct, g), MJS_STRUCT_FIELD_TYPE_BOOL, NULL},\n  {NULL, 0, MJS_STRUCT_FIELD_TYPE_INVALID, NULL},\n};\n\nconst struct mjs_c_struct_member *get_my_struct_descr(void) {\n  return my_struct_descr;\n};\n```\n\nJS side code:\n```js\n// Assuming `s` is a foreign pointer to an instance of `my_struct`, obtained elsewhere.\nlet sd = ffi('void *get_my_struct_descr(void)')();\nlet o = s2o(s, sd);\nprint(o.a, o.b);\n```\n\nNested structs are also supported - use `MJS_STRUCT_FIELD_TYPE_STRUCT` field type\nand provide pointer to the definition:\n\n```c\nstruct my_struct2 {\n  int8_t i8;\n  int16_t i16;\n  uint8_t u8;\n  uint16_t u16;\n};\n\nstatic const struct mjs_c_struct_member my_struct2_descr[] = {\n  {\"i8\", offsetof(struct my_struct2, i8), MJS_STRUCT_FIELD_TYPE_INT8, NULL},\n  {\"i16\", offsetof(struct my_struct2, i16), MJS_STRUCT_FIELD_TYPE_INT16, NULL},\n  {\"u8\", offsetof(struct my_struct2, u8), MJS_STRUCT_FIELD_TYPE_UINT8, NULL},\n  {\"u16\", offsetof(struct my_struct2, u16), MJS_STRUCT_FIELD_TYPE_UINT16, NULL},\n  {NULL, 0, MJS_STRUCT_FIELD_TYPE_INVALID, NULL},\n};\n\nstruct my_struct {\n  struct my_struct2 s;\n  struct my_struct2 *sp;\n};\n\nstatic const struct mjs_c_struct_member my_struct_descr[] = {\n  {\"s\", offsetof(struct my_struct, s), MJS_STRUCT_FIELD_TYPE_STRUCT, my_struct2_descr},\n  {\"sp\", offsetof(struct my_struct, sp), MJS_STRUCT_FIELD_TYPE_STRUCT_PTR, my_struct2_descr},\n  {NULL, 0, MJS_STRUCT_FIELD_TYPE_INVALID, NULL},\n};\n```\n\nFor complicated cases, a custom conversion function can be invoked that returns value:\n```c\nmjs_val_t custom_value_func(struct mjs *mjs, void *ap) {\n  /* Do something with ap, construct and return mjs_val_t */\n}\n\nstatic const struct mjs_c_struct_member my_struct_descr[] = {\n  ...\n  {\"x\", offsetof(struct my_struct, x), MJS_STRUCT_FIELD_TYPE_CUSTOM, custom_value_func},\n  ...\n};\n```\n\n\n# Complete embedding example\n\nWe export C function `foo` to the JS environment and call it from the JS.\n\n```c\n#include \"strings.h\"\n#include \"mjs.h\"\n\nvoid foo(int x) {\n  printf(\"Hello %d!\\n\", x);\n}\n\nvoid *my_dlsym(void *handle, const char *name) {\n  if (strcmp(name, \"foo\") == 0) return foo;\n  return NULL;\n}\n\nint main(void) {\n  struct mjs *mjs = mjs_create();\n  mjs_set_ffi_resolver(mjs, my_dlsym);\n  mjs_exec(mjs, \"let f = ffi('void foo(int)'); f(1234)\", NULL);\n  return 0;\n}\n```\n\nCompile \u0026 run:\n\n```\n$ cc main.c mjs.c -o /tmp/x \u0026\u0026 /tmp/x\nHello 1234!\n```\n\n# Build stand-alone mJS binary\n\nBuild:\n```\n$ make\n```\n\nUse as a simple calculator:\n```\n$ ./build/mjs -e '1 + 2 * 3'\n7\n```\n\nFFI standard C functions:\n```\n$ ./build/mjs -e 'ffi(\"double sin(double)\")(1.23)'\n0.942489\n```\n\nView generated bytecode:\n```\n$ ./build/mjs -l 3 -e '2 + 2'\n------- MJS VM DUMP BEGIN\n    DATA_STACK (0 elems):\n    CALL_STACK (0 elems):\n        SCOPES (1 elems):  [\u003cobject\u003e]\n  LOOP_OFFSETS (0 elems):\n  CODE:\n  0   BCODE_HDR [\u003cstdin\u003e] size:28\n  21  PUSH_INT  2\n  23  PUSH_INT  2\n  25  EXPR      +\n  27  EXIT\n  28  NOP\n------- MJS VM DUMP END\n4\n```\n\nThe stand-alone binary uses `dlsym()` symbol resolver, that's why\n`ffi(\"double sin(double)\")(1.23)` works.\n\n# Licensing\n\nmJS is released under commercial and\n[GNU GPL v.2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html)\nopen source licenses.\n\nCommercial Projects: once your project becomes commercialised, GPLv2 licensing\ndictates that you need to either open your source fully or purchase a\ncommercial license. Cesanta offer full, royalty-free commercial licenses\nwithout any GPL restrictions. If your needs require a custom license, we’d be\nhappy to work on a solution with you.\n[Contact us for pricing](https://mongoose-os.com/contact.html)\n\nPrototyping: While your project is still in prototyping stage and not for sale,\nyou can use MJS’s open source code without license restrictions.\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\n# Technical guides\n\nTechnical atricles and deep dives into embedded networking technologies:\n- [Embedded Web Server: A Comprehensive Guide for Modern Connected Devices](https://mongoose.ws/articles/embedded-web-server-a-comprehensive-guide-for-modern-connected-devices/)\n","funding_links":[],"categories":["C","Awesome Mongoose OS [![Awesome](https://awesome.re/badge.svg)](https://awesome.re)","JavaScript框架","Scripting","Embedded Interpreters","Libraries"],"sub_categories":["Community Tutorials","大语言对话模型及数据","JavaScript","Others"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesanta%2Fmjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcesanta%2Fmjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesanta%2Fmjs/lists"}