New model
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
// The runtime that emitted C is compiled against. Declarations only for now.
|
||||
// The natives are declared by the emitted C itself, from compiler/src/native.rs
|
||||
// See docs/model.md for the representation of values
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct { char _; } undefined_t;
|
||||
typedef struct { char _; } null_t;
|
||||
|
||||
typedef struct jsrt_string *string_t;
|
||||
|
||||
// ────────────────────────────────────────────── cells ──────────────────────────────────────────────
|
||||
|
||||
// The start of every object: the site that created it, which tells its shape
|
||||
// and, for a fn, its code
|
||||
typedef struct { uint32_t site; } jsrt_header;
|
||||
|
||||
// The sites below 3 are tags. An object of a value shape is a struct, not a
|
||||
// pointer, so its header tells if it is undefined, null or absent instead
|
||||
#define JSRT_SITE_UNDEFINED 0
|
||||
#define JSRT_SITE_NULL 1
|
||||
#define JSRT_SITE_ABSENT 2
|
||||
|
||||
// A pointer to an object or a string can also be one of these. NULL is null
|
||||
extern jsrt_header jsrt_undefined_cell, jsrt_absent_cell;
|
||||
#define JSRT_UNDEFINED ((void *)&jsrt_undefined_cell)
|
||||
#define JSRT_ABSENT ((void *)&jsrt_absent_cell) // a field that an object lacks
|
||||
|
||||
// A new heap cell of `size` bytes
|
||||
void *jsrt_alloc(size_t size);
|
||||
|
||||
// Throws a TypeError. Exceptions are not supported yet, so it stops the program
|
||||
_Noreturn void jsrt_type_error(void);
|
||||
|
||||
// ───────────────────────────────────────────── strings ─────────────────────────────────────────────
|
||||
|
||||
string_t jsrt_string_lit(const char *s);
|
||||
int32_t jsrt_string_length(string_t s);
|
||||
bool jsrt_string_eq(string_t a, string_t b);
|
||||
int32_t jsrt_string_cmp(string_t a, string_t b);
|
||||
|
||||
// ─────────────────────────────────────────────── any ───────────────────────────────────────────────
|
||||
// A value of any kind: a NaN-boxed 64-bit word. An object in it is the pointer
|
||||
// to its cell, so it keeps its identity. A field can also hold absent
|
||||
typedef uint64_t any_t;
|
||||
|
||||
// Box a value. A pointer that is a sentinel (NULL, JSRT_UNDEFINED,
|
||||
// JSRT_ABSENT) boxes as null, undefined or absent
|
||||
any_t __any_undefined(void);
|
||||
any_t __any_null(void);
|
||||
any_t __any_absent(void);
|
||||
any_t __any_from_bool(bool b);
|
||||
any_t __any_from_int(int32_t n);
|
||||
any_t __any_from_float(double x);
|
||||
any_t __any_from_string(string_t s);
|
||||
any_t __any_from_object(void *o);
|
||||
|
||||
// Unbox a value. A value of another kind throws. A string or an object unboxes
|
||||
// null, undefined and absent to their sentinels
|
||||
bool __any_as_bool(any_t a);
|
||||
int32_t __any_as_int(any_t a);
|
||||
double __any_as_float(any_t a); // an int too
|
||||
string_t __any_as_string(any_t a);
|
||||
void *__any_as_object(any_t a);
|
||||
|
||||
// The kind of a value
|
||||
bool __any_is_absent(any_t a);
|
||||
bool __any_is_undefined(any_t a);
|
||||
bool __any_is_null(any_t a);
|
||||
bool __any_is_nullish(any_t a); // undefined or null
|
||||
bool __any_is_boolean(any_t a);
|
||||
bool __any_is_number(any_t a);
|
||||
bool __any_is_string(any_t a);
|
||||
bool __any_is_object(any_t a); // a fn too
|
||||
bool __any_is_function(any_t a);
|
||||
|
||||
// JS's operators, with JS's conversions
|
||||
any_t __any_add(any_t a, any_t b);
|
||||
any_t __any_sub(any_t a, any_t b);
|
||||
any_t __any_mul(any_t a, any_t b);
|
||||
any_t __any_div(any_t a, any_t b);
|
||||
any_t __any_mod(any_t a, any_t b);
|
||||
any_t __any_pow(any_t a, any_t b);
|
||||
any_t __any_neg(any_t a);
|
||||
any_t __any_plus(any_t a); // ToNumber
|
||||
any_t __any_inc(any_t a);
|
||||
any_t __any_dec(any_t a);
|
||||
|
||||
bool __any_lt(any_t a, any_t b);
|
||||
bool __any_lte(any_t a, any_t b);
|
||||
bool __any_gt(any_t a, any_t b);
|
||||
bool __any_gte(any_t a, any_t b);
|
||||
bool __any_eq(any_t a, any_t b);
|
||||
bool __any_neq(any_t a, any_t b);
|
||||
bool __any_strict_eq(any_t a, any_t b);
|
||||
bool __any_strict_neq(any_t a, any_t b);
|
||||
|
||||
// JS's abstract operations
|
||||
bool __any_truthy(any_t a); // ToBoolean
|
||||
int32_t __any_to_int32(any_t a); // ToInt32
|
||||
string_t __any_to_string(any_t a); // ToString
|
||||
string_t __any_typeof(any_t a);
|
||||
+260
-5
@@ -1,9 +1,264 @@
|
||||
const Object = {
|
||||
create(proto) { return { __proto__: proto }; },
|
||||
// The stdlib, compiled in front of every program
|
||||
//
|
||||
// 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`. `console.log` and `String.prototype.concat` also need rest
|
||||
// params, which need arrays
|
||||
|
||||
const NaN = 0 / 0;
|
||||
const Infinity = 1 / 0;
|
||||
|
||||
// ─────────────────────────────────────────────── Object ───────────────────────────────────────────────
|
||||
|
||||
// `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 };
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────── String ───────────────────────────────────────────────
|
||||
|
||||
// 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; },
|
||||
|
||||
concat(...parts) {
|
||||
let s = this;
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
s = __native_string_concat(s, String(parts[i]));
|
||||
}
|
||||
return s;
|
||||
},
|
||||
|
||||
// ── 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);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────── Number ───────────────────────────────────────────────
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────── Boolean ──────────────────────────────────────────────
|
||||
|
||||
// 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);
|
||||
|
||||
// ─────────────────────────────────────────────── console ──────────────────────────────────────────────
|
||||
|
||||
const console = {
|
||||
// TODO: all __native_* fns should be monomorphic, console.log() should act a bit like Rust's write!/print!
|
||||
// and break down complex printing in JS-land
|
||||
log(x) { __native_print(x); },
|
||||
log(...args) {
|
||||
let line = "";
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (i > 0) line = __native_string_concat(line, " ");
|
||||
line = __native_string_concat(line, String(args[i]));
|
||||
}
|
||||
__native_print_string(line);
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user