Broken site based impl
This commit is contained in:
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
// Runs after the program: the event loop (see loop.js)
|
||||
__run_loop();
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 };
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user