@mikrojs/native 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CMakeLists.txt +198 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/cmake/mikrojs_bytecode.cmake +146 -0
  5. package/cmake.js +22 -0
  6. package/dist/index.d.ts +52 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +132 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/types.d.ts +43 -0
  11. package/dist/types.d.ts.map +1 -0
  12. package/dist/types.js +2 -0
  13. package/dist/types.js.map +1 -0
  14. package/include/byteorder_apple.h +11 -0
  15. package/include/byteorder_windows.h +12 -0
  16. package/include/mikrojs/cbor_helpers.h +24 -0
  17. package/include/mikrojs/cutils_wrap.h +59 -0
  18. package/include/mikrojs/errors.h +144 -0
  19. package/include/mikrojs/mem.h +11 -0
  20. package/include/mikrojs/mik_color.h +32 -0
  21. package/include/mikrojs/mikrojs.h +331 -0
  22. package/include/mikrojs/platform.h +82 -0
  23. package/include/mikrojs/private.h +281 -0
  24. package/include/mikrojs/utils.h +125 -0
  25. package/package.json +100 -0
  26. package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
  27. package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
  28. package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
  29. package/runtime/ble/ble.ts +231 -0
  30. package/runtime/ble/types.ts +194 -0
  31. package/runtime/ble/uuid.ts +89 -0
  32. package/runtime/ble/validators.ts +61 -0
  33. package/runtime/cbor/cbor.ts +1 -0
  34. package/runtime/cbor/types.ts +8 -0
  35. package/runtime/console/types.ts +50 -0
  36. package/runtime/env/env.ts +17 -0
  37. package/runtime/env/types.ts +12 -0
  38. package/runtime/format/types.ts +4 -0
  39. package/runtime/fs/fs.ts +93 -0
  40. package/runtime/fs/types.ts +92 -0
  41. package/runtime/globals.d.ts +87 -0
  42. package/runtime/http/helpers.ts +222 -0
  43. package/runtime/http/native.ts +151 -0
  44. package/runtime/http/request.ts +25 -0
  45. package/runtime/i2c/i2c.ts +35 -0
  46. package/runtime/i2c/types.ts +55 -0
  47. package/runtime/inspect/types.ts +10 -0
  48. package/runtime/internal.d.ts +456 -0
  49. package/runtime/kv/nvs.ts +17 -0
  50. package/runtime/kv/rtc.ts +17 -0
  51. package/runtime/kv/shared.ts +107 -0
  52. package/runtime/kv/types.ts +150 -0
  53. package/runtime/neopixel/neopixel.ts +38 -0
  54. package/runtime/neopixel/types.ts +27 -0
  55. package/runtime/pin/pin.ts +51 -0
  56. package/runtime/pin/types.ts +49 -0
  57. package/runtime/pwm/pwm.ts +32 -0
  58. package/runtime/pwm/types.ts +29 -0
  59. package/runtime/reader/reader.ts +167 -0
  60. package/runtime/reader/types.ts +34 -0
  61. package/runtime/result/native-result.node-shim.ts +44 -0
  62. package/runtime/result/result.ts +26 -0
  63. package/runtime/result/types.ts +60 -0
  64. package/runtime/schema/schema.ts +321 -0
  65. package/runtime/schema/types.ts +152 -0
  66. package/runtime/sleep/sleep.ts +14 -0
  67. package/runtime/sleep/types.ts +44 -0
  68. package/runtime/sntp/sntp.ts +54 -0
  69. package/runtime/sntp/types.ts +38 -0
  70. package/runtime/spi/spi.ts +31 -0
  71. package/runtime/spi/types.ts +42 -0
  72. package/runtime/stdio/stdio.ts +44 -0
  73. package/runtime/stdio/types.ts +22 -0
  74. package/runtime/stream/stream.ts +150 -0
  75. package/runtime/stream/types.ts +47 -0
  76. package/runtime/sys/sys.ts +90 -0
  77. package/runtime/sys/types.ts +131 -0
  78. package/runtime/test/test.ts +595 -0
  79. package/runtime/test/types.ts +97 -0
  80. package/runtime/uart/types.ts +75 -0
  81. package/runtime/uart/uart.ts +51 -0
  82. package/runtime/wifi/types.ts +156 -0
  83. package/runtime/wifi/wifi.ts +208 -0
  84. package/scripts/bundle-runtime.js +149 -0
  85. package/scripts/compare-minifiers.js +189 -0
  86. package/scripts/compile-bytecode.sh +38 -0
  87. package/scripts/copy-prebuild.js +20 -0
  88. package/scripts/generate-symbol-map.js +146 -0
  89. package/src/builtins.cpp +82 -0
  90. package/src/cutils_compat.c +38 -0
  91. package/src/eval_bytecode.cpp +42 -0
  92. package/src/fs.cpp +878 -0
  93. package/src/mem.cpp +63 -0
  94. package/src/mik_abort.cpp +160 -0
  95. package/src/mik_app_config.cpp +358 -0
  96. package/src/mik_cbor.cpp +334 -0
  97. package/src/mik_color.cpp +46 -0
  98. package/src/mik_console.cpp +422 -0
  99. package/src/mik_inspect.cpp +850 -0
  100. package/src/mik_repl.cpp +1122 -0
  101. package/src/mik_result.cpp +344 -0
  102. package/src/mik_stdio.cpp +147 -0
  103. package/src/mik_sys.cpp +239 -0
  104. package/src/mik_text_encoding.cpp +443 -0
  105. package/src/mikrojs.cpp +942 -0
  106. package/src/modules.cpp +944 -0
  107. package/src/platform_posix.cpp +134 -0
  108. package/src/timers.cpp +208 -0
  109. package/src/utils.cpp +173 -0
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Compare minifier output sizes for firmware runtime modules.
5
+ *
6
+ * Usage: node scripts/compare-minifiers.js [--open]
7
+ *
8
+ * Builds runtime modules with each minifier/level combination, collects
9
+ * bundle sizes, and writes an HTML report to compare-minifiers.html.
10
+ * Pass --open to open the report in the default browser.
11
+ */
12
+
13
+ import {execSync} from 'node:child_process'
14
+ import {existsSync, mkdirSync, readFileSync, rmSync, writeFileSync} from 'node:fs'
15
+ import {join} from 'node:path'
16
+ import {fileURLToPath} from 'node:url'
17
+
18
+ const ROOT = join(fileURLToPath(import.meta.url), '../..')
19
+ const CONFIGS = [
20
+ {name: 'esbuild', args: ''},
21
+ {name: 'terser', args: '-DMIK_MINIFIER=terser'},
22
+ {name: 'terser max', args: '-DMIK_MINIFIER=terser -DMIK_MINIFY_LEVEL=max'},
23
+ {name: 'swc', args: '-DMIK_MINIFIER=swc'},
24
+ {name: 'swc max', args: '-DMIK_MINIFIER=swc -DMIK_MINIFY_LEVEL=max'},
25
+ ]
26
+
27
+ function run(cmd) {
28
+ return execSync(cmd, {cwd: ROOT, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']})
29
+ }
30
+
31
+ function parseBundleOutput(output) {
32
+ const modules = new Map()
33
+ for (const match of output.matchAll(/Bundled (\S+) \((\d+) bytes/g)) {
34
+ modules.set(match[1], Number(match[2]))
35
+ }
36
+ return modules
37
+ }
38
+
39
+ function buildConfig(config) {
40
+ const buildDir = join(ROOT, `.compare-${config.name.replace(/\s+/g, '-')}`)
41
+ if (existsSync(buildDir)) rmSync(buildDir, {recursive: true, force: true})
42
+ mkdirSync(buildDir, {recursive: true})
43
+
44
+ const configureCmd = `cmake -B ${buildDir} ${config.args} .`
45
+ run(configureCmd)
46
+
47
+ const buildCmd = `cmake --build ${buildDir} --target gen_bytecode`
48
+ const output = run(buildCmd)
49
+
50
+ const modules = parseBundleOutput(output)
51
+ rmSync(buildDir, {recursive: true, force: true})
52
+ return modules
53
+ }
54
+
55
+ // Run all builds
56
+ // eslint-disable-next-line no-console
57
+ console.log('Comparing minifiers for firmware runtime modules...\n')
58
+ const results = []
59
+ for (const config of CONFIGS) {
60
+ process.stdout.write(` Building with ${config.name}...`)
61
+ const start = Date.now()
62
+ const modules = buildConfig(config)
63
+ const elapsed = ((Date.now() - start) / 1000).toFixed(1)
64
+ // eslint-disable-next-line no-console
65
+ console.log(` ${modules.size} modules (${elapsed}s)`)
66
+ results.push({name: config.name, modules})
67
+ }
68
+
69
+ // Collect all module names
70
+ const allModules = [...new Set(results.flatMap((r) => [...r.modules.keys()]))].sort()
71
+
72
+ // Find the baseline (esbuild)
73
+ const baseline = results[0].modules
74
+
75
+ // Generate HTML
76
+ function generateHtml() {
77
+ const rows = allModules.map((mod) => {
78
+ const cells = results.map((r) => {
79
+ const size = r.modules.get(mod) ?? 0
80
+ const base = baseline.get(mod) ?? 0
81
+ const diff = size - base
82
+ const pct = base > 0 ? ((diff / base) * 100).toFixed(1) : '—'
83
+ return {size, diff, pct}
84
+ })
85
+ return {mod, cells}
86
+ })
87
+
88
+ const totals = results.map((r) => {
89
+ const total = allModules.reduce((sum, mod) => sum + (r.modules.get(mod) ?? 0), 0)
90
+ const baseTotal = allModules.reduce((sum, mod) => sum + (baseline.get(mod) ?? 0), 0)
91
+ const diff = total - baseTotal
92
+ const pct = baseTotal > 0 ? ((diff / baseTotal) * 100).toFixed(1) : '—'
93
+ return {total, diff, pct}
94
+ })
95
+
96
+ const configHeaders = results.map((r) => `<th>${r.name}</th>`).join('\n ')
97
+
98
+ const tableRows = rows
99
+ .map(({mod, cells}) => {
100
+ const tds = cells
101
+ .map(({size, diff, pct}, i) => {
102
+ if (i === 0) return `<td class="num">${size}</td>`
103
+ const cls = diff < 0 ? 'better' : diff > 0 ? 'worse' : 'same'
104
+ const sign = diff > 0 ? '+' : ''
105
+ return `<td class="num ${cls}">${size} <span class="diff">(${sign}${diff}, ${sign}${pct}%)</span></td>`
106
+ })
107
+ .join('\n ')
108
+ return ` <tr>
109
+ <td class="mod">${mod}</td>
110
+ ${tds}
111
+ </tr>`
112
+ })
113
+ .join('\n')
114
+
115
+ const totalRow = totals
116
+ .map(({total, diff, pct}, i) => {
117
+ if (i === 0) return `<td class="num"><strong>${total}</strong></td>`
118
+ const cls = diff < 0 ? 'better' : diff > 0 ? 'worse' : 'same'
119
+ const sign = diff > 0 ? '+' : ''
120
+ return `<td class="num ${cls}"><strong>${total}</strong> <span class="diff">(${sign}${diff}, ${sign}${pct}%)</span></td>`
121
+ })
122
+ .join('\n ')
123
+
124
+ return `<!DOCTYPE html>
125
+ <html lang="en">
126
+ <head>
127
+ <meta charset="utf-8">
128
+ <title>Minifier Comparison — mikrojs firmware runtime modules</title>
129
+ <style>
130
+ * { box-sizing: border-box; margin: 0; padding: 0; }
131
+ body { font-family: system-ui, -apple-system, sans-serif; padding: 2rem; background: #f8f9fa; color: #1a1a1a; }
132
+ h1 { font-size: 1.4rem; margin-bottom: 0.25rem; }
133
+ .subtitle { color: #666; margin-bottom: 1.5rem; font-size: 0.9rem; }
134
+ table { border-collapse: collapse; width: 100%; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
135
+ th { background: #1a1a1a; color: white; padding: 0.6rem 1rem; text-align: left; font-weight: 500; font-size: 0.85rem; }
136
+ td { padding: 0.45rem 1rem; border-bottom: 1px solid #eee; font-size: 0.85rem; }
137
+ tr:last-child td { border-bottom: none; }
138
+ tr:hover td { background: #f5f5f5; }
139
+ .mod { font-family: ui-monospace, monospace; font-size: 0.8rem; }
140
+ .num { text-align: right; font-family: ui-monospace, monospace; font-size: 0.8rem; white-space: nowrap; }
141
+ .diff { font-size: 0.75rem; opacity: 0.7; }
142
+ .better { color: #16a34a; }
143
+ .worse { color: #dc2626; }
144
+ .same { color: #666; }
145
+ .total-row td { border-top: 2px solid #1a1a1a; background: #f9fafb; }
146
+ .legend { margin-top: 1rem; font-size: 0.8rem; color: #666; }
147
+ .legend span { margin-right: 1.5rem; }
148
+ .timestamp { margin-top: 0.5rem; font-size: 0.75rem; color: #999; }
149
+ </style>
150
+ </head>
151
+ <body>
152
+ <h1>Minifier Comparison</h1>
153
+ <p class="subtitle">Bundle sizes (bytes) for mikrojs firmware runtime modules. Diff relative to esbuild baseline.</p>
154
+ <table>
155
+ <thead>
156
+ <tr>
157
+ <th>Module</th>
158
+ ${configHeaders}
159
+ </tr>
160
+ </thead>
161
+ <tbody>
162
+ ${tableRows}
163
+ <tr class="total-row">
164
+ <td class="mod"><strong>Total</strong></td>
165
+ ${totalRow}
166
+ </tr>
167
+ </tbody>
168
+ </table>
169
+ <p class="legend">
170
+ <span class="better">Green = smaller than esbuild</span>
171
+ <span class="worse">Red = larger than esbuild</span>
172
+ <span class="same">Gray = same size</span>
173
+ </p>
174
+ <p class="timestamp">Generated ${new Date().toISOString()}</p>
175
+ </body>
176
+ </html>`
177
+ }
178
+
179
+ const html = generateHtml()
180
+ const outPath = join(ROOT, 'compare-minifiers.html')
181
+ writeFileSync(outPath, html)
182
+ // eslint-disable-next-line no-console
183
+ console.log(`\nReport written to ${outPath}`)
184
+
185
+ if (process.argv.includes('--open')) {
186
+ const {platform} = await import('node:os')
187
+ const cmd = platform() === 'darwin' ? 'open' : platform() === 'win32' ? 'start' : 'xdg-open'
188
+ execSync(`${cmd} ${outPath}`)
189
+ }
@@ -0,0 +1,38 @@
1
+ #!/bin/sh
2
+ # Compile a bundled JS module to a C bytecode header using qjsc.
3
+ #
4
+ # Usage: compile-bytecode.sh <qjsc> <input.js> <output.h> <module_name> <symbol_name>
5
+ #
6
+ # Reads <input>.externals for external module declarations.
7
+
8
+ set -e
9
+
10
+ QJSC="$1"
11
+ INPUT="$2"
12
+ OUTPUT="$3"
13
+ MODULE_NAME="$4"
14
+ SYMBOL_NAME="$5"
15
+
16
+ EXTERNALS_FILE="${INPUT%.js}.externals"
17
+
18
+ # Build -M flags for external modules
19
+ M_FLAGS=""
20
+ if [ -f "$EXTERNALS_FILE" ]; then
21
+ while IFS= read -r ext || [ -n "$ext" ]; do
22
+ [ -n "$ext" ] && M_FLAGS="$M_FLAGS -M $ext"
23
+ done < "$EXTERNALS_FILE"
24
+ fi
25
+
26
+ # Compile with qjsc: ES module, strip source, custom name and script name.
27
+ #
28
+ # We use -s (strip source), NOT -ss (strip source + debug info). Stripping
29
+ # debug info via -ss drops b->filename from every function bytecode
30
+ # (quickjs.c writes filename inside `if (s->allow_debug)` — see
31
+ # JS_WriteFunctionBytecode). Once filename is gone, JS_GetScriptOrModuleName
32
+ # returns JS_ATOM_NULL for any function in the deserialized module, and
33
+ # js_import_meta fails with "import.meta not supported in this context" —
34
+ # so any runtime module that reads `import.meta.*` (e.g. mikrojs/env
35
+ # accessing import.meta.env) breaks at runtime. The ~1 KB heap saving
36
+ # from stripping debug isn't worth crippling a spec-standard module API.
37
+ # shellcheck disable=SC2086
38
+ "$QJSC" -m -s -N "$SYMBOL_NAME" -n "$MODULE_NAME" $M_FLAGS -o "$OUTPUT" "$INPUT"
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import {copyFileSync, existsSync, mkdirSync} from 'node:fs'
3
+ import {dirname, resolve} from 'node:path'
4
+ import {fileURLToPath} from 'node:url'
5
+
6
+ const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
7
+ const source = resolve(pkgRoot, 'addon', 'build', 'Release', 'mikrojs_node.node')
8
+
9
+ if (!existsSync(source)) {
10
+ // eslint-disable-next-line no-console
11
+ console.error(`No native binary at ${source}. Run 'cmake-js compile --directory addon' first.`)
12
+ process.exit(1)
13
+ }
14
+
15
+ const targetDir = resolve(pkgRoot, 'prebuilds', `${process.platform}-${process.arch}`)
16
+ mkdirSync(targetDir, {recursive: true})
17
+ const target = resolve(targetDir, 'mikrojs.napi.node')
18
+ copyFileSync(source, target)
19
+ // eslint-disable-next-line no-console
20
+ console.log(`Copied → ${target}`)
@@ -0,0 +1,146 @@
1
+ /* eslint-disable no-console */
2
+ import {execFileSync, spawnSync} from 'node:child_process'
3
+ import {createHash} from 'node:crypto'
4
+ import {readFileSync, writeFileSync} from 'node:fs'
5
+
6
+ /**
7
+ * Generate a compact symbol map from an ELF for offline panic-trace decoding.
8
+ *
9
+ * Toolchain-agnostic: takes a binutils prefix (e.g. "riscv32-esp-elf-",
10
+ * "xtensa-esp32-elf-", "arm-none-eabi-") so the same script works for any
11
+ * target a future port (RP2350, etc.) might add.
12
+ *
13
+ * Usage: node generate-symbol-map.js <elf> <out.json> <toolchain-prefix> [chip]
14
+ */
15
+
16
+ const [, , elfPath, outPath, toolchainPrefix, chip = ''] = process.argv
17
+
18
+ if (!elfPath || !outPath || !toolchainPrefix) {
19
+ console.error('Usage: node generate-symbol-map.js <elf> <out.json> <toolchain-prefix> [chip]')
20
+ process.exit(1)
21
+ }
22
+
23
+ const nm = `${toolchainPrefix}nm`
24
+ const addr2line = `${toolchainPrefix}addr2line`
25
+
26
+ const elfBuf = readFileSync(elfPath)
27
+ const elfHash = createHash('sha256').update(elfBuf).digest('hex')
28
+
29
+ // nm -S --defined-only --print-size: "ADDR SIZE T name"
30
+ // Symbols without a size (size = 0) are skipped — they're typically labels,
31
+ // not real functions, and would only pollute the lookup table.
32
+ const nmOut = execFileSync(nm, ['-S', '--defined-only', '--print-size', elfPath], {
33
+ encoding: 'utf8',
34
+ maxBuffer: 256 * 1024 * 1024,
35
+ })
36
+
37
+ const rawFuncs = []
38
+ const addrs = []
39
+ let codeMin = Infinity
40
+ let codeMax = 0
41
+
42
+ for (const line of nmOut.split('\n')) {
43
+ const m = line.match(/^([0-9a-f]+)\s+([0-9a-f]+)\s+([tTwW])\s+(.+)$/)
44
+ if (!m) continue
45
+ const start = parseInt(m[1], 16)
46
+ const size = parseInt(m[2], 16)
47
+ if (size === 0) continue
48
+ rawFuncs.push({start, size, mangled: m[4]})
49
+ addrs.push('0x' + start.toString(16))
50
+ if (start < codeMin) codeMin = start
51
+ if (start + size > codeMax) codeMax = start + size
52
+ }
53
+
54
+ if (rawFuncs.length === 0) {
55
+ console.error('No function symbols found in ELF — nothing to do.')
56
+ process.exit(1)
57
+ }
58
+
59
+ // Resolve every function entry's file:line in a single addr2line invocation.
60
+ // `-f` = function name, `-C` = demangle, `-s` = base names only (we keep
61
+ // full paths from the file column instead — `-s` would shorten file paths).
62
+ // Actually we want full paths: drop -s.
63
+ const a2l = spawnSync(addr2line, ['-f', '-C', '-e', elfPath], {
64
+ input: addrs.join('\n') + '\n',
65
+ encoding: 'utf8',
66
+ maxBuffer: 256 * 1024 * 1024,
67
+ })
68
+
69
+ if (a2l.status !== 0) {
70
+ console.error(`addr2line failed: ${a2l.stderr}`)
71
+ process.exit(1)
72
+ }
73
+
74
+ // addr2line output: two lines per input address — function name, then file:line
75
+ const a2lLines = a2l.stdout.split('\n')
76
+
77
+ const fileIdx = new Map()
78
+ const files = []
79
+ const nameIdx = new Map()
80
+ const names = []
81
+
82
+ function internFile(f) {
83
+ let i = fileIdx.get(f)
84
+ if (i === undefined) {
85
+ i = files.length
86
+ files.push(f)
87
+ fileIdx.set(f, i)
88
+ }
89
+ return i
90
+ }
91
+
92
+ function internName(n) {
93
+ let i = nameIdx.get(n)
94
+ if (i === undefined) {
95
+ i = names.length
96
+ names.push(n)
97
+ nameIdx.set(n, i)
98
+ }
99
+ return i
100
+ }
101
+
102
+ const funcs = [] // [start, size, nameIdx, fileIdx, line]
103
+
104
+ for (let i = 0; i < rawFuncs.length; i++) {
105
+ const fn = rawFuncs[i]
106
+ const fname = (a2lLines[i * 2] || '').trim()
107
+ const floc = (a2lLines[i * 2 + 1] || '').trim()
108
+
109
+ // addr2line emits "??" when DWARF info is missing.
110
+ const displayName = fname && fname !== '??' ? fname : fn.mangled
111
+ let fIdx = -1
112
+ let lineNo = 0
113
+ if (floc && floc !== '??:?' && floc !== '??:0') {
114
+ // "path/to/file.cpp:123" (or "...:123 (discriminator N)")
115
+ const colon = floc.lastIndexOf(':')
116
+ if (colon > 0) {
117
+ const file = floc.slice(0, colon)
118
+ const lineStr = floc.slice(colon + 1).split(/\s/)[0]
119
+ const parsed = parseInt(lineStr, 10)
120
+ if (file && file !== '??' && Number.isFinite(parsed)) {
121
+ fIdx = internFile(file)
122
+ lineNo = parsed
123
+ }
124
+ }
125
+ }
126
+ funcs.push([fn.start, fn.size, internName(displayName), fIdx, lineNo])
127
+ }
128
+
129
+ funcs.sort((a, b) => a[0] - b[0])
130
+
131
+ const out = {
132
+ v: 1,
133
+ chip,
134
+ elfHash,
135
+ codeStart: codeMin,
136
+ codeEnd: codeMax,
137
+ files,
138
+ names,
139
+ funcs,
140
+ }
141
+
142
+ writeFileSync(outPath, JSON.stringify(out))
143
+ console.log(
144
+ `Wrote ${outPath}: ${funcs.length} funcs, ${files.length} files, ${names.length} names, ` +
145
+ `code range 0x${codeMin.toString(16)}-0x${codeMax.toString(16)}`,
146
+ )
@@ -0,0 +1,82 @@
1
+
2
+ #include <cinttypes>
3
+ #include <cstdio>
4
+
5
+ #include "mikrojs/platform.h"
6
+ #include "mikrojs/private.h"
7
+ #include "mikrojs/utils.h"
8
+
9
+ typedef struct {
10
+ const char* name;
11
+ const uint8_t* data;
12
+ uint32_t data_size;
13
+ } mik_builtin_t;
14
+
15
+ /* Generated by mikrojs_generate_bytecode() in CMake — includes + lookup table.
16
+ * Adding a new module only requires updating the MODULES list in CMakeLists.txt. */
17
+ #include "gen/mikrojs_builtins_table.h"
18
+ #define builtins mikrojs_builtins
19
+
20
+ /* External builtins registered by board/driver packages via MIK_REGISTER_BUILTIN() */
21
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
22
+ mik_ext_builtin_t* mik__ext_builtin_head = nullptr;
23
+
24
+ /* Deserialize bytecode into a module. Shared by core and external builtins. */
25
+ static JSModuleDef* mik__deserialize_builtin(JSContext* ctx, const char* name,
26
+ const uint8_t* data, uint32_t data_size) {
27
+ const MIKPlatform* platform = MIK_GetPlatform();
28
+
29
+ platform->log(MIK_LOG_DEBUG, "mikrojs", "Loading builtin '%s' (%u bytes, bc_version=%u, data_ptr=%p)", name,
30
+ data_size, data_size > 0 ? data[0] : 0, (void*)data);
31
+
32
+ JSValue obj = JS_ReadObject(ctx, data, data_size, JS_READ_OBJ_BYTECODE);
33
+
34
+ if (JS_IsException(obj)) {
35
+ platform->log(MIK_LOG_ERROR, "mikrojs", "Failed to deserialize bytecode for builtin '%s' (%u bytes)", name,
36
+ data_size);
37
+ /* Leave the JS exception on ctx so the caller can propagate the
38
+ * real error (e.g. "Maximum call stack size exceeded") instead of
39
+ * replacing it with a generic "not available" message. */
40
+ return NULL;
41
+ }
42
+
43
+ if (JS_VALUE_GET_TAG(obj) != JS_TAG_MODULE) {
44
+ platform->log(MIK_LOG_ERROR, "mikrojs", "Builtin '%s' bytecode is not a module (tag=%d)", name,
45
+ JS_VALUE_GET_TAG(obj));
46
+ JS_FreeValue(ctx, obj);
47
+ return NULL;
48
+ }
49
+
50
+ /* Initialize import.meta (url, env, etc.) for the builtin module */
51
+ js_module_set_import_meta(ctx, obj, false, false);
52
+
53
+ /* Resolve imports so the module loader is called for dependencies */
54
+ if (JS_ResolveModule(ctx, obj) < 0) {
55
+ platform->log(MIK_LOG_ERROR, "mikrojs", "Failed to resolve imports for builtin '%s'", name);
56
+ JS_FreeValue(ctx, obj);
57
+ return NULL;
58
+ }
59
+
60
+ JSModuleDef* m = static_cast<JSModuleDef*>(JS_VALUE_GET_PTR(obj));
61
+ JS_FreeValue(ctx, obj);
62
+
63
+ return m;
64
+ }
65
+
66
+ JSModuleDef* mik__load_builtin(JSContext* ctx, const char* name) {
67
+ /* Check core builtins table */
68
+ for (const mik_builtin_t* p = builtins; p->name != NULL; ++p) {
69
+ if (strcmp(p->name, name) == 0) {
70
+ return mik__deserialize_builtin(ctx, name, p->data, p->data_size);
71
+ }
72
+ }
73
+
74
+ /* Check external builtins (registered by board/driver packages) */
75
+ for (mik_ext_builtin_t* p = mik__ext_builtin_head; p != nullptr; p = p->next) {
76
+ if (strcmp(p->name, name) == 0) {
77
+ return mik__deserialize_builtin(ctx, name, p->data, p->data_size);
78
+ }
79
+ }
80
+
81
+ return NULL;
82
+ }
@@ -0,0 +1,38 @@
1
+ /* Provide non-inline definitions of cutils.h functions for C++ linkage.
2
+ *
3
+ * cutils.h declares these as static inline, so they have internal linkage
4
+ * and aren't visible to C++ translation units that use cutils_wrap.h
5
+ * (declarations only). This file provides real external definitions. */
6
+ #include <cutils.h>
7
+
8
+ int mik__dbuf_put(DynBuf *s, const void *data, size_t len) {
9
+ return dbuf_put(s, data, len);
10
+ }
11
+
12
+ int mik__dbuf_putc(DynBuf *s, uint8_t val) {
13
+ return dbuf_putc(s, val);
14
+ }
15
+
16
+ int mik__dbuf_putstr(DynBuf *s, const char *str) {
17
+ return dbuf_putstr(s, str);
18
+ }
19
+
20
+ void mik__dbuf_free(DynBuf *s) {
21
+ dbuf_free(s);
22
+ }
23
+
24
+ void mik__dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func) {
25
+ dbuf_init2(s, opaque, realloc_func);
26
+ }
27
+
28
+ int mik__js_has_suffix(const char *str, const char *suffix) {
29
+ return js__has_suffix(str, suffix);
30
+ }
31
+
32
+ void mik__js_pstrcpy(char *buf, int buf_size, const char *str) {
33
+ js__pstrcpy(buf, buf_size, str);
34
+ }
35
+
36
+ char *mik__js_pstrcat(char *buf, int buf_size, const char *s) {
37
+ return js__pstrcat(buf, buf_size, s);
38
+ }
@@ -0,0 +1,42 @@
1
+ #include <quickjs.h>
2
+
3
+ #include "mikrojs/private.h"
4
+ #include "mikrojs/utils.h"
5
+
6
+ int mik__eval_bytecode(JSContext* ctx, const uint8_t* buf, size_t buf_len, bool check_promise) {
7
+ JSValue obj = JS_ReadObject(ctx, buf, buf_len, JS_READ_OBJ_BYTECODE);
8
+
9
+ if (JS_IsException(obj)) {
10
+ mik_dump_error(ctx);
11
+ return -1;
12
+ }
13
+
14
+ if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) {
15
+ if (JS_ResolveModule(ctx, obj) < 0) {
16
+ JS_FreeValue(ctx, obj);
17
+ mik_dump_error(ctx);
18
+ return -1;
19
+ }
20
+ }
21
+
22
+ JSValue val = JS_EvalFunction(ctx, obj);
23
+ if (JS_IsException(val)) {
24
+ mik_dump_error(ctx);
25
+ return -1;
26
+ }
27
+
28
+ if (check_promise) {
29
+ JSPromiseStateEnum promise_state = JS_PromiseState(ctx, val);
30
+ if (promise_state != (JSPromiseStateEnum)-1) {
31
+ if (promise_state == JS_PROMISE_REJECTED) {
32
+ JSValue res = JS_PromiseResult(ctx, val);
33
+ JS_FreeValue(ctx, res);
34
+ JS_FreeValue(ctx, val);
35
+ return -1;
36
+ }
37
+ }
38
+ }
39
+
40
+ JS_FreeValue(ctx, val);
41
+ return 0;
42
+ }