feat: add stdlib and browser examples

- stdlib/html.lux: Type-safe HTML construction
- stdlib/browser.lux: Browser utilities
- examples/web/: Counter app with DOM manipulation
- examples/counter.lux: Simple counter example

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 03:54:17 -05:00
parent ccd335c80f
commit 634f665b1b
11 changed files with 1177 additions and 0 deletions

144
examples/web/counter.js Normal file
View File

@@ -0,0 +1,144 @@
// Lux Runtime
const Lux = {
Some: (value) => ({ tag: "Some", value }),
None: () => ({ tag: "None" }),
Ok: (value) => ({ tag: "Ok", value }),
Err: (error) => ({ tag: "Err", error }),
Cons: (head, tail) => [head, ...tail],
Nil: () => [],
defaultHandlers: {
Console: {
print: (msg) => console.log(msg),
readLine: () => {
if (typeof require !== 'undefined') {
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
return new Promise(resolve => rl.question('', answer => { rl.close(); resolve(answer); }));
}
return prompt('') || '';
},
readInt: () => parseInt(Lux.defaultHandlers.Console.readLine(), 10)
},
Random: {
int: (min, max) => Math.floor(Math.random() * (max - min + 1)) + min,
bool: () => Math.random() < 0.5,
float: () => Math.random()
},
Time: {
now: () => Date.now(),
sleep: (ms) => new Promise(resolve => setTimeout(resolve, ms))
},
Http: {
get: async (url) => {
try {
const response = await fetch(url);
const body = await response.text();
const headers = [];
response.headers.forEach((v, k) => headers.push([k, v]));
return Lux.Ok({ status: response.status, body, headers });
} catch (e) {
return Lux.Err(e.message);
}
},
post: async (url, body) => {
try {
const response = await fetch(url, { method: 'POST', body });
const respBody = await response.text();
const headers = [];
response.headers.forEach((v, k) => headers.push([k, v]));
return Lux.Ok({ status: response.status, body: respBody, headers });
} catch (e) {
return Lux.Err(e.message);
}
},
postJson: async (url, json) => {
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(json)
});
const body = await response.text();
const headers = [];
response.headers.forEach((v, k) => headers.push([k, v]));
return Lux.Ok({ status: response.status, body, headers });
} catch (e) {
return Lux.Err(e.message);
}
}
}
}
};
// Model constructors
function Counter_lux(field0) { return { tag: "Counter", field0: field0 }; }
// Msg constructors
const Increment_lux = { tag: "Increment" };
const Decrement_lux = { tag: "Decrement" };
const Reset_lux = { tag: "Reset" };
function getCount_lux(m) {
const _match_0 = m;
let _result_1;
if (_match_0.tag === "Counter") {
const n = _match_0.field0;
_result_1 = n;
} else {
throw new Error('Non-exhaustive match on ' + JSON.stringify(_match_0));
}
return _result_1;
}
function init_lux() {
return Counter_lux(0);
}
function update_lux(model, msg) {
const _match_2 = msg;
let _result_3;
if (_match_2.tag === "Increment") {
_result_3 = Counter_lux((getCount_lux(model) + 1));
} else if (_match_2.tag === "Decrement") {
_result_3 = Counter_lux((getCount_lux(model) - 1));
} else if (_match_2.tag === "Reset") {
_result_3 = Counter_lux(0);
} else {
throw new Error('Non-exhaustive match on ' + JSON.stringify(_match_2));
}
return _result_3;
}
function view_lux(model) {
const count_4 = getCount_lux(model);
return (((((((((("<div class=\"counter\">" + "<h1>Lux Counter</h1>") + "<div class=\"display\">") + String(count_4)) + "</div>") + "<div class=\"buttons\">") + "<button onclick=\"dispatch('Decrement')\">-</button>") + "<button onclick=\"dispatch('Reset')\">Reset</button>") + "<button onclick=\"dispatch('Increment')\">+</button>") + "</div>") + "</div>");
}
function luxInit_lux() {
return init_lux();
}
function luxUpdate_lux(model, msgName) {
const _match_5 = msgName;
let _result_6;
if (_match_5 === "Increment") {
_result_6 = update_lux(model, Increment_lux);
} else if (_match_5 === "Decrement") {
_result_6 = update_lux(model, Decrement_lux);
} else if (_match_5 === "Reset") {
_result_6 = update_lux(model, Reset_lux);
} else if (true) {
_result_6 = model;
} else {
throw new Error('Non-exhaustive match on ' + JSON.stringify(_match_5));
}
return _result_6;
}
function luxView_lux(model) {
return view_lux(model);
}