so i had a few hours this aft to play with it some more, so i add bash support to. So i can call luajson from a bash script and get back a valid dict or array and use it in bash 
demo bash script
#!/bin/bash
echo "=== 1. Invoking C/Lua Engine for Nested Payload ==="
# Capture the output from script.
parent_obj=$(./test-bash -o "test" "me" "age" 99 "root" false "arr" "🍕 Food" "🎵 Music" "💻 Tech")
echo "Generated Bash Payload:"
echo "$parent_obj"
echo "--------------------------------------------------------"
echo -e "\n=== 2. Parsing the Parent Object ==="
# Declare an associative array (dictionary) for the parent object
declare -A my_obj
# Evaluate the payload directly into native Bash memory
eval "my_obj=$parent_obj"
# Print an item from the main Object
echo "Successfully extracted from Object -> [test]: ${my_obj[test]}"
echo "Successfully extracted from Object -> [age]: ${my_obj[age]}"
echo -e "\n=== 3. Unpacking and Parsing the Nested Array ==="
# Extract the nested string payload out of the parent object's "arr" key
nested_arr_str="${my_obj[arr]}"
echo "Extracted Nested String: $nested_arr_str"
# Declare a standard indexed array for the child list
declare -a child_arr
# Evaluate the inner string structure into the new indexed array
eval "child_arr=$nested_arr_str"
# Print an item from the nested Array
echo "Successfully extracted from Child Array -> Index 0: ${child_arr[0]}"
echo "Successfully extracted from Child Array -> Index 1: ${child_arr[1]}"
sample lua script called from bash
#!/usr/bin/env lua5.4
local JSON = require "JSON"
function main(...)
-- 1. Look for the flag at index 1, default to object "-o" if missing
local flag = "-o"
local start_idx = 1
if arg[1] == "-o" or arg[1] == "-a" then
flag = arg[1]
start_idx = 2 -- Skip the flag and start reading properties at index 2
end
local parent_table = {}
local child_array = {}
local in_child_array = false
-- 2. Build the nested layout from flat terminal inputs
local i = start_idx
while i <= #arg do
local token = arg[i]
if token == "arr" then
-- We hit the nested boundary marker!
in_child_array = true
parent_table["arr"] = child_array
i = i + 1 -- Move to the first element of the array
elseif in_child_array then
-- Everything after "arr" is a value in the child array
table.insert(child_array, tonumber(token) or token)
i = i + 1
else
-- Process standard object key-value pairs
local key = token
local val = arg[i+1]
if key and val then
parent_table[key] = tonumber(val) or val
end
i = i + 2 -- Skip key and value
end
end
-- 3. Pass the structured nested table to parser
local res = JSON:parse_table(parent_table, flag)
print(res:tobash())
return res:tobash()
end
main(...)
and the output
hostle@hostle-Inc:~/luajson-c-api/src$ ./test-bash.bash
=== 1. Invoking C/Lua Engine for Nested Payload ===
Generated Bash Payload:
([age]=99 [root]="false" [arr]="(\"🍕 Food\" \"🎵 Music\" \"💻 Tech\")" [test]="me")
--------------------------------------------------------
=== 2. Parsing the Parent Object ===
Successfully extracted from Object -> [test]: me
Successfully extracted from Object -> [age]: 99
=== 3. Unpacking and Parsing the Nested Array ===
Extracted Nested String: ("🍕 Food" "🎵 Music" "💻 Tech")
Successfully extracted from Child Array -> Index 0: 🍕 Food
Successfully extracted from Child Array -> Index 1: 🎵 Music
I added bash support in less than 2 hours, by simply adding the format strings for bash output to the template engine. I could add full html support in a weekend session but i have no need for it. But just to point out the elephant in the room, this could drop in beside ucode and bring the whole luci lua eco system back to the21st century .. and present way of doing things. I am currently working on a drag and drop iot dashboard builder and the whole back end for seamless iot control through luci .... drag and drop gauges, charts, graphs, tables ...so far
just finishing up the ubus bindings and i will have a simple demo
.. stay tuned
But the project i have been longing to get back to most ... the LuaThreads pthread api, i have it working stable any many working examples, all valgrind approved. I was just beginning the lua queue tables in which any writes to the table fire a signal to workers to ...do work 
all this was before i could serialize tables and objects/arrays etc ...and pass them from state to state to state . There is some real potential there, i"ve pretty much got a full blown lua kernel 
My hope is someone/s will see this and come join the cause. The code base is coherent and stable. It reads pretty well if you know lua c api. It compile to 64kb lua api + 30kb for c api. build with cmake and has compile time options to select lua version. I have full stack wasm bindings but again not my intention. but it the webui work as tho it was written in c ... oh wait, it was 
here a few demos of the wasm lua engine at play
mock pdu rack montior demo
market watcher demo using lua wasm engine
see it fetching 
here is the html for the above market watch
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Market Watch V21</title>
<link rel="stylesheet" href="style.css">
<style>
#loader {
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
color: #666; font-family: sans-serif; font-size: 14px; animation: pulse 1.5s infinite;
}
@keyframes pulse { 0% { opacity: 0.5; } 50% { opacity: 1; } 100% { opacity: 0.5; } }
</style>
</head>
<body>
<div id="loader">Booting Engine...</div>
<div id="workspace"></div>
<!-- CONFIGURATION -->
<script>
var Module = {
nodes: [],
onRuntimeInitialized: function() {
console.log(">> SYSTEM: WASM Kernel Mounted.");
// 1. INITIALIZE LUA STATE (The Missing Step)
try {
Module.ccall('init_lua', 'void', [], []);
console.log(">> SYSTEM: Lua State Created.");
} catch(e) {
console.error("Init Failed:", e);
}
// 2. FETCH APPLICATION
console.log(">> SYSTEM: Fetching App...");
fetch('app.lua')
.then(r => {
if (!r.ok) throw new Error("Failed to load app.lua");
return r.text();
})
.then(code => {
console.log(">> SYSTEM: Running Logic...");
document.getElementById("loader").style.display = "none";
Module.ccall('run_lua', 'number', ['string'], [code]);
})
.catch(e => {
console.error(e);
document.getElementById("loader").innerText = "Error: " + e.message;
document.getElementById("loader").style.color = "red";
});
}
};
</script>
<!-- ENGINE -->
<script src="luajson_engine.js"></script>
</body>
</html>
and the lua backend
local JSON = require("JSON")
local DOM = {
create = js_create_el,
append = js_append_child,
click = js_on_click,
style = js_set_style,
attr = update_dom_attr,
fetch = js_fetch,
val = js_get_value,
content = js_set_content,
remove = js_remove_el,
save = js_storage_set,
load = js_storage_get,
resize_canvas = js_canvas_resize,
clear = js_canvas_clear,
rect = js_canvas_rect,
poly = js_canvas_poly,
interval = js_interval
}
-- [[ STATE MANAGEMENT ]]
local watchlist = {}
local chart_data = {} -- Stores arrays of points: chart_data["BTC"] = {40, 42, 41...}
local function find_sym(sym)
for i, s in ipairs(watchlist) do
if s == sym then return i end
end
return nil
end
local function save_state()
local str = table.concat(watchlist, ",")
DOM.save("my_watchlist", str)
end
local function format_currency(amount)
local formatted = string.format("%.2f", amount)
local k
while true do
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
if k == 0 then break end
end
return "$" .. formatted
end
-- [[ UI BUILDER ]]
-- No CSS string here anymore!
local grid = DOM.create("div", "")
DOM.attr(grid, "class", "dash-container")
DOM.append(grid, "workspace")
local controls = DOM.create("div", "")
DOM.attr(controls, "class", "control-bar")
DOM.append(controls, grid)
local inp = DOM.create("input", "")
DOM.attr(inp, "class", "input-sym")
DOM.attr(inp, "id", "inp-sym")
DOM.attr(inp, "placeholder", "SYMBOL")
DOM.append(inp, controls)
local btn_add = DOM.create("button", "ADD")
DOM.attr(btn_add, "class", "btn btn-add")
DOM.append(btn_add, controls)
local btn_ref = DOM.create("button", "SYNC")
DOM.attr(btn_ref, "class", "btn btn-refresh")
DOM.append(btn_ref, controls)
-- [[ LOGIC CORE ]]
local function fetch_price(sym)
DOM.style("price-"..sym, "opacity", "0.5")
local url = "https://api.coinbase.com/v2/prices/" .. sym .. "-USD/spot"
DOM.fetch(url, function(resp)
local status, data = pcall(function() return JSON:parse(resp) end)
if status and data and data.data then
local pretty = format_currency(data.data.amount)
DOM.content("price-"..sym, pretty)
DOM.style("price-"..sym, "opacity", "1")
DOM.style("price-"..sym, "color", "#fff")
else
DOM.content("price-"..sym, "Unavailable")
DOM.style("price-"..sym, "color", "#ef4444")
end
end)
end
local function draw_sparkline(sym)
local id = "canv-" .. sym
-- Initialize data if missing
if not chart_data[sym] then
chart_data[sym] = {}
local y = 40
for i=1, 20 do
table.insert(chart_data[sym], y)
end
end
-- 1. Simulate "Live Feed" (Shift Left)
-- Remove oldest point
table.remove(chart_data[sym], 1)
-- Add new random point based on last point
local last = chart_data[sym][#chart_data[sym]]
local delta = (math.random() * 10) - 5
local new_y = last + delta
-- Clamp
if new_y < 5 then new_y = 5 end
if new_y > 75 then new_y = 75 end
table.insert(chart_data[sym], new_y)
-- 2. Prepare Draw
DOM.resize_canvas(id)
DOM.clear(id)
-- Convert simple array {40, 42} to coord string "[[0,40], [10,42]...]"
local points = {}
for i, y_val in ipairs(chart_data[sym]) do
local x = (i-1) * 10 -- 10px spacing
table.insert(points, string.format("[%d, %d]", x, y_val))
end
local json_pts = "[" .. table.concat(points, ",") .. "]"
-- Color Logic
local color = "#10b981" -- Green
if new_y > 40 then color = "#ef4444" end
DOM.poly(id, json_pts, color, 2.5)
end
local function render_card(sym)
local id_base = "card-" .. sym
if find_sym(sym) == nil then
table.insert(watchlist, sym)
save_state()
end
local card = DOM.create("div", "")
DOM.attr(card, "class", "card")
DOM.attr(card, "id", id_base)
-- 1. Header
local header = DOM.create("div", "")
DOM.attr(header, "class", "card-header")
DOM.append(header, card)
local title = DOM.create("div", sym)
DOM.attr(title, "class", "sym-title")
DOM.append(title, header)
local del = DOM.create("button", "×")
DOM.attr(del, "class", "btn-del")
DOM.click(del, function()
DOM.remove(id_base)
local idx = find_sym(sym)
if idx then
table.remove(watchlist, idx)
save_state()
end
end)
DOM.append(del, header)
-- 2. Canvas (Create, but DON'T draw yet)
local canv = DOM.create("canvas", "")
DOM.attr(canv, "id", "canv-" .. sym)
DOM.attr(canv, "class", "sparkline")
DOM.attr(canv, "width", "200")
DOM.attr(canv, "height", "80")
DOM.append(canv, card)
-- 3. Price
local price = DOM.create("div", "Loading...")
DOM.attr(price, "class", "price-main")
DOM.attr(price, "id", "price-" .. sym)
DOM.append(price, card)
-- 4. MOUNT TO DOM (Critical Step)
DOM.append(card, grid)
-- 5. NOW WE DRAW (Element is visible, ID exists)
draw_sparkline(sym)
end
-- [[ EVENTS ]]
DOM.click(btn_add, function()
local val = DOM.val("inp-sym")
if not val or val == "" then return end
local sym = string.upper(val)
if find_sym(sym) then return end
render_card(sym)
fetch_price(sym)
DOM.attr(inp, "value", "")
end)
DOM.click(btn_ref, function()
for i, sym in ipairs(watchlist) do fetch_price(sym) end
end)
-- [[ INIT ]]
local saved_str = DOM.load("my_watchlist")
if saved_str and saved_str ~= "" then
local temp_list = {}
for s in string.gmatch(saved_str, "([^,]+)") do table.insert(temp_list, s) end
watchlist = {}
for i, sym in ipairs(temp_list) do
render_card(sym)
fetch_price(sym)
end
end
-- [[ THE HEARTBEAT ]]
-- This function is called by C every 1000ms
function app_tick()
for i, sym in ipairs(watchlist) do
-- Animate the chart
draw_sparkline(sym)
-- 10% chance to fetch real price update to save bandwidth
if math.random() > 0.9 then
fetch_price(sym)
end
end
end
-- Start the engine (100ms = 10 Frames Per Second for smooth-ish animation)
DOM.interval(100, "app_tick")