Broken site based impl

This commit is contained in:
2026-10-03 14:06:24 +00:00
parent 23a2c3bc46
commit 44498b32a9
39 changed files with 4250 additions and 1750 deletions
+153
View File
@@ -0,0 +1,153 @@
// Arrays. The compiler gives only `a[i]`, `a[i] = x` and `a.length`, which can
// also be written. Every method here is built on those
//
// Not as the spec says:
// - A method takes one value where the spec takes any number (`push(x)`,
// `unshift(x)`, `concat(other)`), because more need rest params
// - A callback gets the element and its index, not the array too
// - Holes are not skipped: a callback sees undefined for a hole
// `Array(n)` and `new Array(n)` make an array of `n` holes
function Array(n) {
const a = [];
if (n !== undefined) a.length = n;
return a;
}
Array.prototype = {
// ── Change the array ──
push(x) {
this[this.length] = x;
return this.length;
},
pop() {
if (this.length === 0) return undefined;
const x = this[this.length - 1];
this.length = this.length - 1;
return x;
},
shift() {
if (this.length === 0) return undefined;
const x = this[0];
for (let i = 1; i < this.length; i++) this[i - 1] = this[i];
this.length = this.length - 1;
return x;
},
unshift(x) {
for (let i = this.length; i > 0; i--) this[i] = this[i - 1];
this[0] = x;
return this.length;
},
reverse() {
for (let i = 0, j = this.length - 1; i < j; i++, j--) {
const x = this[i];
this[i] = this[j];
this[j] = x;
}
return this;
},
fill(x, start, end) {
const len = this.length;
const s = relativeIndex(start === undefined ? 0 : start, len);
const e = relativeIndex(end === undefined ? len : end, len);
for (let i = s; i < e; i++) this[i] = x;
return this;
},
// ── Read ──
at(i) {
if (i < 0) i += this.length;
return this[i];
},
indexOf(x, from) {
for (let i = from === undefined ? 0 : relativeIndex(from, this.length); i < this.length; i++) {
if (this[i] === x) return i;
}
return -1;
},
lastIndexOf(x) {
for (let i = this.length - 1; i >= 0; i--) {
if (this[i] === x) return i;
}
return -1;
},
includes(x) {
return this.indexOf(x) !== -1;
},
join(sep) {
const s = sep === undefined ? "," : sep;
let out = "";
for (let i = 0; i < this.length; i++) {
if (i > 0) out = out + s;
const x = this[i];
if (x !== undefined && x !== null) out = out + String(x);
}
return out;
},
toString() {
return this.join(",");
},
// ── Make a new array ──
slice(start, end) {
const len = this.length;
const s = relativeIndex(start === undefined ? 0 : start, len);
const e = relativeIndex(end === undefined ? len : end, len);
const out = [];
for (let i = s; i < e; i++) out[i - s] = this[i];
return out;
},
concat(other) {
const out = this.slice();
for (let i = 0; i < other.length; i++) out[out.length] = other[i];
return out;
},
// ── With a callback ──
forEach(f) {
for (let i = 0; i < this.length; i++) f(this[i], i);
},
map(f) {
const out = [];
for (let i = 0; i < this.length; i++) out[i] = f(this[i], i);
return out;
},
filter(f) {
const out = [];
for (let i = 0; i < this.length; i++) {
if (f(this[i], i)) out[out.length] = this[i];
}
return out;
},
reduce(f, init) {
let acc = init;
for (let i = 0; i < this.length; i++) acc = f(acc, this[i], i);
return acc;
},
find(f) {
for (let i = 0; i < this.length; i++) {
if (f(this[i], i)) return this[i];
}
return undefined;
},
findIndex(f) {
for (let i = 0; i < this.length; i++) {
if (f(this[i], i)) return i;
}
return -1;
},
some(f) {
for (let i = 0; i < this.length; i++) {
if (f(this[i], i)) return true;
}
return false;
},
every(f) {
for (let i = 0; i < this.length; i++) {
if (!f(this[i], i)) return false;
}
return true;
},
};
__link_proto_array(Array.prototype);
+12
View File
@@ -0,0 +1,12 @@
// Converts any value to a boolean, like the spec's ToBoolean
function Boolean(x) {
return !!x;
}
// In these methods `this` is the primitive boolean itself
Boolean.prototype = {
toString() { return this ? "true" : "false"; },
valueOf() { return this; },
};
__link_proto_boolean(Boolean.prototype);
+7
View File
@@ -0,0 +1,7 @@
// Not as the spec says: it prints its first arg only, because more args need
// rest params, which need arrays
const console = {
log(x) {
__native_print_string(String(x));
},
};
+2
View File
@@ -0,0 +1,2 @@
// Runs after the program: the event loop (see loop.js)
__run_loop();
+19
View File
@@ -0,0 +1,19 @@
// The stdlib, compiled in front of every program. Its files are joined in the
// order of `STDLIB` in compiler/src/main.rs, and this one is first
//
// Built on the natives in compiler/src/native.rs, the common layer of
// operations on primitive values. The compiler's own code for operators (`+` on
// strings and so on) uses those natives directly, not these functions.
//
// Restrictions:
// - The prototypes linked with `__link_proto_*` are fixed. User code may not
// change them (no monkey-patching)
// - `new String(x)`, `new Number(x)` and `new Boolean(x)` (wrapper objects) are
// not supported. These functions only convert
//
// Needs `typeof`. `String.prototype.concat` is not here, because it needs rest
// params, which need arrays. `console.log` prints one arg only, for the same
// reason
const NaN = 0 / 0;
const Infinity = 1 / 0;
+72
View File
@@ -0,0 +1,72 @@
// Files, with promises, on the IO natives (see lib/rt/io.h). They are like
// node's `fs.promises`, but only for UTF-8 text:
// - `fs.readFile(path)` gives the text of the file
// - `fs.writeFile(path, text)` makes or replaces the file
// A failure rejects with the negative errno, not an `Error`. A read or a write
// that the kernel does not finish in one op is not continued
// A promise of what op `h` gives when it completes: its status, its data, or
// a number. A result can only be read before the next wait, so these read it
// when the op completes
function __io_status(h) {
return new Promise((resolve) => {
__pending[h] = (h) => resolve(__native_io_status(h));
});
}
function __io_string(h) {
return new Promise((resolve, reject) => {
__pending[h] = (h) => {
const status = __native_io_status(h);
if (status < 0) reject(status);
else resolve(__native_io_result_string(h));
};
});
}
function __io_number(h) {
return new Promise((resolve, reject) => {
__pending[h] = (h) => {
const status = __native_io_status(h);
if (status < 0) reject(status);
else resolve(__native_io_result_number(h));
};
});
}
// A promise of the status of op `h`, rejected if it failed
function __io_ok(h) {
return __io_status(h).then((status) => (status < 0 ? Promise.reject(status) : status));
}
const fs = {
readFile(path) {
let fd = -1;
let text = "";
return __io_ok(__native_io_queue_open(path, 0, 0))
.then((f) => {
fd = f;
return __io_number(__native_io_queue_stat(path));
})
.then((size) => __io_string(__native_io_queue_read(fd, size, 0)))
.then((s) => {
text = s;
return __io_ok(__native_io_queue_close(fd));
})
.then(() => text);
},
writeFile(path, text) {
let fd = -1;
// O_WRONLY | O_CREAT | O_TRUNC, mode 0644
return __io_ok(__native_io_queue_open(path, 577, 420))
.then((f) => {
fd = f;
return __io_ok(__native_io_queue_write(fd, text, 0));
})
.then(() => __io_ok(__native_io_queue_close(fd)))
.then(() => undefined);
},
};
+46
View File
@@ -0,0 +1,46 @@
// The event loop. A macrotask is an IO op or a timer, which completes in C
// (see lib/rt/io.h): `__native_io_wait()` gives the handle of the next one
// that completes. A microtask is a promise job, in `__microtasks`. After the
// program, end.js runs the loop: all microtasks, then one macrotask, then all
// microtasks again, until nothing is pending
// What to do when IO op `h` completes: `__pending[h]`, a fn that gets `h`.
// Handles are small ints, and a handle is used again after its op completes
const __pending = [];
// Promise jobs, in order. `head` is the next one, so that taking a job does
// not move the others. A job is a closure, which keeps its own data: the
// handler and the value that it is called with. So each handler gets only the
// values of its own promise (see `docs/model.md`, section 3.3)
const __microtasks = { jobs: [], head: 0 };
function __queue_job(job) {
__microtasks.jobs.push(job);
}
function __drain_microtasks() {
const q = __microtasks;
while (q.head < q.jobs.length) {
const job = q.jobs[q.head];
q.jobs[q.head] = undefined;
q.head++;
job();
}
q.jobs.length = 0;
q.head = 0;
}
function __run_loop() {
for (;;) {
__drain_microtasks();
const h = __native_io_wait();
if (h < 0) return;
const done = __pending[h];
__pending[h] = undefined;
if (done !== undefined) done(h);
}
}
+49
View File
@@ -0,0 +1,49 @@
// Converts any value to a number, like the spec's ToNumber
function Number(x) {
switch (typeof x) {
case "number": return x;
case "string": return __native_string_to_number(x);
case "boolean": return x ? 1 : 0;
case "undefined": return NaN;
case "object": return x === null ? 0 : NaN;
default: return NaN;
}
}
Number.NaN = NaN;
Number.POSITIVE_INFINITY = Infinity;
Number.NEGATIVE_INFINITY = -Infinity;
Number.MAX_SAFE_INTEGER = 9007199254740991;
Number.MIN_SAFE_INTEGER = -9007199254740991;
Number.isNaN = function (x) { return typeof x === "number" && x !== x; };
Number.isFinite = function (x) { return typeof x === "number" && x === x && x !== Infinity && x !== -Infinity; };
Number.isInteger = function (x) { return Number.isFinite(x) && x % 1 === 0; };
// In these methods `this` is the primitive number itself
Number.prototype = {
toString(radix) {
if (radix === undefined || radix === 10) return __native_number_to_string(this);
return integerToString(this, radix);
},
toFixed(digits) { return __native_number_to_fixed(this, digits === undefined ? 0 : digits); },
valueOf() { return this; },
};
__link_proto_number(Number.prototype);
// An integer in `radix` (2 to 36). Only integers: the fraction is dropped
function integerToString(n, radix) {
let negative = n < 0;
if (negative) n = -n;
n = n - (n % 1);
let out = "";
do {
let digit = n % radix;
out = __native_string_concat(__native_string_from_char_code(digit < 10 ? 48 + digit : 87 + digit), out);
n = (n - digit) / radix;
} while (n > 0);
return negative ? __native_string_concat("-", out) : out;
}
+10
View File
@@ -0,0 +1,10 @@
// `new Object()` makes an empty object. Called without `new`, it does the same
function Object() {
return {};
}
Object.prototype = {};
Object.create = function (proto) {
return { __proto__: proto };
};
+165
View File
@@ -0,0 +1,165 @@
// Promises, as the spec says, except:
// - A throw in a handler or an executor does not reject, because there is no
// `try`. `reject()` and a returned rejected promise do
// - An unhandled rejection does nothing. Node prints it and stops
// - `Promise.all` takes an array, not any iterable
// The states of a promise
const __PENDING = 0;
const __FULFILLED = 1;
const __REJECTED = 2;
class Promise {
constructor(executor) {
this.state = __PENDING;
this.value = undefined;
this.resolved = false; // `resolve()` or `reject()` was called, maybe with a thenable
this.reactions = [];
if (executor !== undefined) {
executor((value) => __resolve(this, value), (reason) => __reject(this, reason));
}
}
then(onFulfilled, onRejected) {
const derived = new Promise();
const reaction = { onFulfilled: onFulfilled, onRejected: onRejected, derived: derived };
if (this.state === __PENDING) {
this.reactions.push(reaction);
} else {
__queue_reaction(reaction, this.state, this.value);
}
return derived;
}
catch(onRejected) {
return this.then(undefined, onRejected);
}
finally(f) {
return this.then(
(value) => {
f();
return value;
},
(reason) => {
f();
return Promise.reject(reason);
},
);
}
static resolve(value) {
if (__is_promise(value)) return value;
const p = new Promise();
__resolve(p, value);
return p;
}
static reject(reason) {
const p = new Promise();
__reject(p, reason);
return p;
}
static all(promises) {
const p = new Promise();
const values = [];
let left = promises.length;
if (left === 0) __resolve(p, values);
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(
(value) => {
values[i] = value;
left--;
if (left === 0) __resolve(p, values);
},
(reason) => __reject(p, reason),
);
}
return p;
}
}
function __is_promise(x) {
return typeof x === "object" && x !== null && x.reactions !== undefined;
}
// Resolve `p` with `value`, unless it was resolved or rejected before
function __resolve(p, value) {
if (p.resolved) return;
p.resolved = true;
__fulfil_or_adopt(p, value);
}
// Fulfil `p` with `value`, or if `value` is a thenable, adopt its state. That
// is in a job of its own, as the spec says, so it takes a tick more
function __fulfil_or_adopt(p, value) {
if ((typeof value === "object" && value !== null) || typeof value === "function") {
const then = value.then;
if (typeof then === "function") {
__queue_job(() => __adopt(p, value));
return;
}
}
__settle(p, __FULFILLED, value);
}
function __reject(p, reason) {
if (p.resolved) return;
p.resolved = true;
__settle(p, __REJECTED, reason);
}
function __settle(p, state, value) {
p.state = state;
p.value = value;
const reactions = p.reactions;
p.reactions = [];
for (let i = 0; i < reactions.length; i++) {
__queue_reaction(reactions[i], state, value);
}
}
function __queue_reaction(reaction, state, value) {
const handler = state === __FULFILLED ? reaction.onFulfilled : reaction.onRejected;
const derived = reaction.derived;
__queue_job(() => __react(handler, value, derived, state));
}
// Call `handler` with the value of a promise that is in `state`, and resolve
// `derived` with the result. With no handler, `derived` gets the same state
function __react(handler, value, derived, state) {
if (typeof handler === "function") {
__resolve(derived, handler(value));
} else if (state === __FULFILLED) {
__resolve(derived, value);
} else {
__reject(derived, value);
}
}
// Make `p` follow the thenable `thenable`
function __adopt(p, thenable) {
thenable.then(
(value) => {
if (p.state === __PENDING) __fulfil_or_adopt(p, value);
},
(reason) => {
if (p.state === __PENDING) __settle(p, __REJECTED, reason);
},
);
}
function queueMicrotask(f) {
__queue_job(f);
}
+143
View File
@@ -0,0 +1,143 @@
// Converts any value to a string, like the spec's ToString
function String(x) {
switch (typeof x) {
case "string": return x;
case "number": return __native_number_to_string(x);
case "boolean": return x ? "true" : "false";
case "undefined": return "undefined";
case "function": return "function () { [native code] }";
case "object":
if (x === null) return "null";
if (typeof x.toString === "function") return x.toString();
return "[object Object]";
}
}
String.fromCharCode = function (code) {
return __native_string_from_char_code(code);
};
// In these methods `this` is the primitive string itself
String.prototype = {
toString() { return this; },
valueOf() { return this; },
// ── Characters ──
charAt(i) {
if (i < 0 || i >= this.length) return "";
return __native_string_slice(this, i, i + 1);
},
charCodeAt(i) {
if (i < 0 || i >= this.length) return NaN;
return __native_string_char_code_at(this, i);
},
at(i) {
if (i < 0) i += this.length;
if (i < 0 || i >= this.length) return undefined;
return __native_string_slice(this, i, i + 1);
},
// ── Search ──
indexOf(search, from) {
let start = from === undefined ? 0 : clampIndex(from, this.length);
for (let i = start; i + search.length <= this.length; i++) {
if (__native_string_slice(this, i, i + search.length) === search) return i;
}
return -1;
},
lastIndexOf(search) {
for (let i = this.length - search.length; i >= 0; i--) {
if (__native_string_slice(this, i, i + search.length) === search) return i;
}
return -1;
},
includes(search, from) { return this.indexOf(search, from) !== -1; },
startsWith(search, from) {
let start = from === undefined ? 0 : clampIndex(from, this.length);
return __native_string_slice(this, start, start + search.length) === search;
},
endsWith(search, end) {
let stop = end === undefined ? this.length : clampIndex(end, this.length);
let start = stop - search.length;
return start >= 0 && __native_string_slice(this, start, stop) === search;
},
// ── Substrings ──
slice(start, end) {
let len = this.length;
let s = relativeIndex(start === undefined ? 0 : start, len);
let e = relativeIndex(end === undefined ? len : end, len);
return s < e ? __native_string_slice(this, s, e) : "";
},
substring(start, end) {
let len = this.length;
let s = clampIndex(start === undefined ? 0 : start, len);
let e = clampIndex(end === undefined ? len : end, len);
return s < e ? __native_string_slice(this, s, e) : __native_string_slice(this, e, s);
},
// ── Case ──
toUpperCase() { return mapCodes(this, 97, 122, -32); }, // a-z to A-Z
toLowerCase() { return mapCodes(this, 65, 90, 32); }, // A-Z to a-z
// ── Whitespace ──
trim() { return this.trimStart().trimEnd(); },
trimStart() {
let i = 0;
while (i < this.length && isSpace(__native_string_char_code_at(this, i))) i++;
return __native_string_slice(this, i, this.length);
},
trimEnd() {
let i = this.length;
while (i > 0 && isSpace(__native_string_char_code_at(this, i - 1))) i--;
return __native_string_slice(this, 0, i);
},
// ── Building ──
repeat(count) {
let s = "";
for (let i = 0; i < count; i++) s = __native_string_concat(s, this);
return s;
},
padStart(length, fill) { return pad(this, length, fill, true); },
padEnd(length, fill) { return pad(this, length, fill, false); },
};
__link_proto_string(String.prototype);
// An index clamped to [0, len]
function clampIndex(i, len) {
return i < 0 ? 0 : i > len ? len : i;
}
// An index where a negative value counts from the end, clamped to [0, len]
function relativeIndex(i, len) {
return clampIndex(i < 0 ? i + len : i, len);
}
// `s` with every char code in [lo, hi] moved by `shift`, for ASCII case mapping
function mapCodes(s, lo, hi, shift) {
let out = "";
for (let i = 0; i < s.length; i++) {
let c = __native_string_char_code_at(s, i);
if (c >= lo && c <= hi) c += shift;
out = __native_string_concat(out, __native_string_from_char_code(c));
}
return out;
}
function isSpace(c) {
// tab, line feed, vertical tab, form feed, carriage return, space, nbsp
return (c >= 9 && c <= 13) || c === 32 || c === 160;
}
function pad(s, length, fill, atStart) {
let f = fill === undefined ? " " : String(fill);
if (s.length >= length || f.length === 0) return s;
let padding = "";
while (padding.length < length - s.length) padding = __native_string_concat(padding, f);
padding = __native_string_slice(padding, 0, length - s.length);
return atStart ? __native_string_concat(padding, s) : __native_string_concat(s, padding);
}
+25
View File
@@ -0,0 +1,25 @@
// Timers. `setTimeout` gives a record, as node does, not an int: an IO handle
// is used again after its op completes, so `clearTimeout` of an old int could
// cancel another op. A record stays the timer's own
//
// Not as the spec says: `setTimeout` takes no args for the callback
function setTimeout(f, ms) {
// As in node, a delay below 1 ms is 1 ms
const delay = ms === undefined || !(ms >= 1) ? 1 : ms;
const timer = { handle: __native_io_queue_timeout(delay), active: true };
__pending[timer.handle] = (h) => {
if (!timer.active) return;
timer.active = false;
f();
};
return timer;
}
function clearTimeout(timer) {
if (timer === undefined || timer === null || !timer.active) return;
timer.active = false;
__native_io_queue_cancel(timer.handle);
}