Compare commits

..

5 Commits

Author SHA1 Message Date
b97e4bf163
improve: UserValue API 2024-12-28 13:20:04 +02:00
9ce0504948
fix: debugger concurrency issues 2024-12-28 13:19:53 +02:00
4c53689d9c
feat: some behavioral fixes 2024-12-28 13:19:35 +02:00
6c8c329992
feat: improve transpiler infrastructure 2024-12-28 13:19:02 +02:00
74f08b7483
small stdlib improvements
Some checks failed
tagged-release / Tagged Release (push) Failing after 7m8s
2024-12-28 13:17:45 +02:00
24 changed files with 329 additions and 87 deletions

View File

@ -31,7 +31,7 @@ task compileEnv(type: NpmTask) {
}
task compileTypescript(type: NpmTask) {
inputs.files('rollup.config.js');
inputs.dir('src/lib/ts');
inputs.dir('src/lib/transpiler');
outputs.files("build/js/ts.js");
// nom nom tasty ram
environment.put("NODE_OPTIONS", "--max-old-space-size=4096");

View File

@ -1,10 +1,17 @@
{
"scripts": {
"build-env": "rollup -c --environment INPUT:src/lib/libs/_entry.ts,OUTPUT:build/js/index.js,POLYFILLS:src/lib/libs/polyfills",
"build-ts": "rollup -c --environment INPUT:src/lib/ts/_entry.ts,OUTPUT:build/js/ts.js"
"build-ts": "rollup -c --environment INPUT:src/lib/transpiler/_entry.ts,OUTPUT:build/js/ts.js"
},
"dependencies": {
"@babel/core": "^7.26.0",
"@babel/runtime": "^7.26.0",
"@babel/standalone": "^7.26.4",
"@rollup/plugin-json": "^6.1.0",
"@types/babel__preset-env": "^7.9.7",
"@types/babel__standalone": "^7.1.9",
"@types/coffeescript": "^2.5.7",
"coffeescript": "^2.7.0",
"typescript": "^5.7.2"
},
"devDependencies": {

View File

@ -4,6 +4,7 @@ const typescript = require("@rollup/plugin-typescript");
const babel = require("@rollup/plugin-babel");
const commonjs = require("@rollup/plugin-commonjs");
const nodeResolve = require("@rollup/plugin-node-resolve");
const json = require("@rollup/plugin-json");
const { resolve } = require("path");
const shouldMinify = () => false;
@ -21,6 +22,7 @@ const construct = (input, output) => defineConfig({
},
commonjs(),
nodeResolve(),
json(),
babel({
extensions: [],
exclude: ["node_modules/**"],
@ -107,6 +109,14 @@ const construct = (input, output) => defineConfig({
path: "null",
os: "null",
inspector: "null",
tty: "null",
util: "null",
assert: "null",
url: "null",
"@babel/preset-typescript/package.json": "null",
module: "null",
process: "null",
v8: "null",
},
// plugins: [babel.getBabelOutputPlugin({
// allowAllFormats: true,

View File

@ -13,6 +13,7 @@ import { Date } from "./date.ts";
import { Math as _Math } from "./math.ts";
import { Set, WeakSet } from "./set.ts";
import { JSON } from "./json.ts";
import { console } from "./console.ts";
import { encodeURI, encodeURIComponent } from "./url.ts";
import { Promise } from "./promise.ts";
@ -27,6 +28,8 @@ function fixup<T extends Function>(clazz: T) {
return clazz;
}
object.setPrototype(target, Object.prototype);
object.defineField(target, "undefined", { e: false, c: false, w: false, v: void 0 });
target.Symbol = fixup(Symbol);
@ -52,6 +55,8 @@ target.Date = fixup(Date);
target.Promise = fixup(Promise);
target.Math = object.setPrototype(_Math, Object.prototype);
target.JSON = object.setPrototype(JSON, Object.prototype);
target.console = object.setPrototype(console, Object.prototype);
target.TYPED_ARRAY_SUPPORT = false;
target.parseInt = Number.parseInt;
target.parseFloat = Number.parseFloat;

View File

@ -157,6 +157,16 @@ export const Array = (() => {
return res;
}
public reduce(this: any[], cb: Function, initial: any) {
let i = 0;
if (arguments.length <= 1) initial = this[i++];
for (; i < this.length; i++) {
initial = cb(initial, this[i], i, this);
}
return initial;
}
public some(this: any[], cb: Function, self?: any) {
for (let i = 0; i < this.length; i++) {
if (i in this && func.invoke(cb, self, [this[i], i, this])) return true;
@ -197,6 +207,13 @@ export const Array = (() => {
return -1;
}
public includes(this: any[], val: any) {
for (let i = 0; i < this.length; i++) {
if (i in this && this[i] === val) return i;
}
return false;
}
public sort(this: any[], cb?: Function) {
cb ||= (a: any, b: any) => {
@ -207,6 +224,18 @@ export const Array = (() => {
return object.sort(this, cb);
}
public reverse(this: any[]) {
const mid = this.length >> 1;
const end = this.length - 1;
for (let i = 0; i < mid; i++) {
const tmp = this[i];
this[i] = this[end - i];
this[end - i] = tmp;
}
return this;
}
public [symbols.iterator](this: any[]) {
let i = 0;

11
src/lib/libs/console.ts Normal file
View File

@ -0,0 +1,11 @@
import { func, json, object } from "./primordials";
export const console = {};
function method(name: string, func: Function) {
object.defineField(console, name, { c: true, e: false, w: true, v: func });
}
method("log", function log() {
func.invoke(print, null, arguments as any);
});

View File

@ -55,3 +55,6 @@ method("ceil", function floor(val: number) {
method("pow", function pow(a: number, b: number) {
return number.pow(a, b);
});
method("log", function log(val: number) {
return number.log(val);
});

View File

@ -13,9 +13,10 @@ export const Object = (() => {
else if (this === null) return "[object Null]";
else if (typeof this === "object") {
if (symbols.toStringTag in this) return "[object " + (this as any)[symbols.toStringTag] + "]";
else if (object.isArray(this)) return "[object Array]";
else return "[object Object]";
}
else if (typeof this === "number" || this instanceof Object) return "[object Object]";
else if (typeof this === "number" || this instanceof Number) return "[object Number]";
else if (typeof this === "symbol" || this instanceof Symbol) return "[object Symbol]";
else if (typeof this === "string" || this instanceof String) return "[object String]";
else if (typeof this === "boolean" || this instanceof Boolean) return "[object Boolean]";
@ -46,6 +47,12 @@ export const Object = (() => {
public static getOwnPropertyDescriptor(obj: object, key: any) {
return object.getOwnMember(obj, key);
}
public static getOwnPropertyNames(obj: object): string[] {
return object.getOwnMembers(obj, false);
}
public static getOwnPropertySymbols(obj: object): symbol[] {
return object.getOwnSymbolMembers(obj, false);
}
public static defineProperty(obj: object, key: string | symbol, desc: PropertyDescriptor) {
if (obj === null || typeof obj !== "function" && typeof obj !== "object") {
@ -84,8 +91,8 @@ export const Object = (() => {
return obj;
}
public static defineProperties(obj: object, desc: PropertyDescriptorMap) {
const keys = object.getOwnMembers(desc, false) as ((keyof typeof obj) & string)[];
const symbols = object.getOwnSymbolMembers(desc, false) as ((keyof typeof obj) & symbol)[];
const keys = object.getOwnMembers(desc, true) as ((keyof typeof obj) & string)[];
const symbols = object.getOwnSymbolMembers(desc, true) as ((keyof typeof obj) & symbol)[];
for (let i = 0; i < keys.length; i++) {
Object.defineProperty(obj, keys[i], desc[keys[i]]);
@ -119,7 +126,6 @@ export const Object = (() => {
return target;
}
public static setPrototypeOf(obj: object, proto: object | null) {
object.setPrototype(obj, proto!);
}
@ -129,8 +135,8 @@ export const Object = (() => {
public static keys(obj: any) {
const res: any[] = [];
const keys = object.getOwnMembers(obj, false);
const symbols = object.getOwnSymbolMembers(obj, false);
const keys = object.getOwnMembers(obj, true);
const symbols = object.getOwnSymbolMembers(obj, true);
for (let i = 0; i < keys.length; i++) {
res[res.length] = keys[i];
@ -143,8 +149,8 @@ export const Object = (() => {
}
public static values(obj: any) {
const res: any[] = [];
const keys = object.getOwnMembers(obj, false);
const symbols = object.getOwnSymbolMembers(obj, false);
const keys = object.getOwnMembers(obj, true);
const symbols = object.getOwnSymbolMembers(obj, true);
for (let i = 0; i < keys.length; i++) {
res[res.length] = obj[keys[i]];
@ -157,8 +163,8 @@ export const Object = (() => {
}
public static entries(obj: any) {
const res: [any, any][] = [];
const keys = object.getOwnMembers(obj, false);
const symbols = object.getOwnSymbolMembers(obj, false);
const keys = object.getOwnMembers(obj, true);
const symbols = object.getOwnSymbolMembers(obj, true);
for (let i = 0; i < keys.length; i++) {
res[res.length] = [keys[i], obj[keys[i]]];
@ -172,12 +178,15 @@ export const Object = (() => {
public static preventExtensions(obj: object) {
object.preventExt(obj);
return obj;
}
public static seal(obj: object) {
object.seal(obj);
return obj;
}
public static freeze(obj: object) {
object.freeze(obj);
return obj;
}
}

View File

@ -5,19 +5,28 @@ function _defineProperties(target, arr) {
for (var i = 0; i < arr.length; i++) {
var desc = arr[i];
var res;
var w, e, c;
c = desc.configurable;
if (c == null) c = true;
e = desc.enumerable;
if (e == null) e = false;
if ("value" in desc) {
res = object.defineField(target, desc.key, { w: desc.writable || true, e: desc.enumerable || true, c: desc.configurable || true, v: desc.value });
w = desc.writable;
if (w == null) w = true;
if (desc.writable == null)
res = object.defineField(target, desc.key, { w: !!w, e: !!e, c: !!c, v: desc.value });
}
else {
res = object.defineProperty(target, desc.key, { e: desc.enumerable || true, c: desc.configurable || true, g: desc.get, s: desc.set });
res = object.defineProperty(target, desc.key, { e: !!e, c: !!c, g: desc.get, s: desc.set });
}
if (!res) throw "Couldn't set property";
}
}
/* __#PURE__ */
export default function _createClass(clazz, instance, nonInstance) {
_defineProperties(clazz.prototype, instance);
_defineProperties(clazz, nonInstance);

View File

@ -5,13 +5,17 @@ export interface SymbolPrimordials {
getSymbolDescription(symbol: symbol): string;
}
export interface NumberPrimordials {
NaN: number;
Infinity: number;
PI: number;
E: number;
parseInt(raw: string | number, radix?: number): number;
parseFloat(raw: string | number): number;
isNaN(num: number): boolean;
NaN: number;
Infinity: number;
pow(a: number, b: number): number;
log(val: number): number;
}
export interface StringPrimordials {
stringBuild(parts: string[]): string;
@ -32,12 +36,13 @@ export interface ObjectPrimordials {
getOwnSymbolMembers(obj: object, onlyEnumerable: boolean): symbol[];
getPrototype(obj: object): object | undefined;
setPrototype(obj: object, proto?: object): object;
isArray(obj: any[]): boolean;
preventExt(obj: object): void;
seal(obj: object): void;
freeze(obj: object): void;
isArray(obj: any): obj is any[];
subarray(arr: any[], start: number, end: number): any[];
memcpy(src: any[], dst: any[], srcI: number, dstI: number, n: number): void;
sort(arr: any[], cb: Function): any[];
}

View File

@ -72,6 +72,13 @@ export const String = (() => {
return string.substring(self, 0, endI);
}
public trimLeft() {
return func.invoke(String.prototype.trimStart, this, []);
}
public trimRight() {
return func.invoke(String.prototype.trimEnd, this, []);
}
public charAt(i: number) {
const self = unwrapThis(this, "string", String, "String.prototype.charAt");
return self[i];
@ -133,6 +140,16 @@ export const String = (() => {
return applyReplaces(self, matches, replacer, false);
}
public repeat(n: number) {
const self = unwrapThis(this, "string", String, "String.prototype.replaceAll");
const res: string[] = [];
for (let i = 0; i < n; i++) {
res[i] = self;
}
return string.stringBuild(res);
}
public slice(start = 0, end?: number) {
const self = unwrapThis(this, "string", String, "String.prototype.slice");
@ -143,32 +160,63 @@ export const String = (() => {
if (end <= start) return "";
return string.substring(self, start, end);
}
public substring(this: string, start = 0, end = this.length) {
public substring(start = 0, end?: number) {
const self = unwrapThis(this, "string", String, "String.prototype.substring");
start = limitI(start, this.length);
end = limitI(end, this.length);
if (end === undefined) end = self.length;
start = limitI(start, self.length);
end = limitI(end, self.length);
if (end <= start) return "";
return string.substring(self, start, end);
}
public substr(this: string, start = 0, count = this.length - start) {
public substr(start = 0, count?: number) {
const self = unwrapThis(this, "string", String, "String.prototype.substr");
start = limitI(start, this.length);
count = limitI(count, this.length - start);
count = self.length - start;
start = limitI(start, self.length);
count = limitI(count, self.length - start);
if (count <= 0) return "";
return string.substring(self, start, count + start);
}
public concat() {
const self = unwrapThis(this, "string", String, "String.prototype.concat");
const parts = [self];
for (let i = 0; i < arguments.length; i++) {
parts[i + 1] = (String as any)(arguments[i]);
}
public toLowerCase(this: string) {
return string.stringBuild(parts);
}
public toLowerCase() {
const self = unwrapThis(this, "string", String, "String.prototype.toLowerCase");
return string.lower(self);
}
public toUpperCase(this: string) {
public toUpperCase() {
const self = unwrapThis(this, "string", String, "String.prototype.toLowerCase");
return string.upper(self);
}
public match(regex: RegExp) {
const self = unwrapThis(this, "string", String, "String.prototype.match");
if (!(regex instanceof RegExp)) throw new TypeError("Regexp expected for String.prototype.match");
if (regex.global) {
let matches: string[] | null = null;
while (true) {
const match = regex.exec(self);
if (match == null) break;
matches ||= [];
matches[matches.length] = match[0];
}
return matches;
}
else return regex.exec(self);
}
public [symbols.iterator]() {
var i = 0;
var arr: string | undefined = unwrapThis(this, "string", String, "String.prototype[Symbol.iterator]");

View File

@ -0,0 +1,4 @@
import coffeescript from "./coffeescript.ts";
import babel from "./babel.ts";
register(v => coffeescript(babel(v)));

View File

@ -0,0 +1,25 @@
import { SourceMap } from "./map.ts";
import { transform } from "@babel/standalone";
// import presetEnv from "@babel/preset-env";
export default function babel(next: Compiler): Compiler {
print("Loaded babel!");
return (filename, code, prevMap) => {
const res = transform(code, {
filename,
sourceMaps: true,
});
const map = SourceMap.parse({
file: "babel-internal://" + filename,
mappings: res.map!.mappings,
sources: [filename],
});
const compiled = next("babel-internal://" + filename, res.code!, SourceMap.chain(map, prevMap));
registerSource(filename, code);
return compiled;
};
}

View File

@ -0,0 +1,28 @@
import { compile } from "coffeescript";
import { SourceMap } from "./map.ts";
export default function coffee(next: Compiler): Compiler {
print("Loaded coffeescript!");
return (filename, code, prevMap) => {
const {
js: result,
v3SourceMap: rawMap,
} = compile(code, {
filename,
sourceMap: true,
bare: true,
});
const map = SourceMap.parse({
file: "coffee-internal://" + filename,
mappings: JSON.parse(rawMap).mappings,
sources: [filename],
});
const compiled = next("coffee-internal://" + filename, result, SourceMap.chain(map, prevMap));
registerSource(filename, code);
return compiled;
};
}

View File

@ -123,13 +123,16 @@ export class VLQSourceMap {
public static parseVLQ(compiled: string, filenames: string[], raw: string): VLQSourceMap {
const mapping = decodeVLQ(raw);
const res = new Map<string, [start: number, dst: Location][][]>();
const file: [start: number, dst: Location][][] = [];
const res = new Map<string, [start: number, dst: Location][][]>([[compiled, file]]);
let originalRow = 0;
let originalCol = 0;
let originalFile = 0;
const lastCols = new Set<number>();
for (let compiledRow = 0; compiledRow < mapping.length; compiledRow++) {
const line = file[compiledRow] ??= [];
let compiledCol = 0;
for (const rawSeg of mapping[compiledRow]) {
@ -138,12 +141,13 @@ export class VLQSourceMap {
originalRow += rawSeg.length > 2 ? rawSeg[2] : 0;
originalCol += rawSeg.length > 3 ? rawSeg[3] : 0;
let file = res.get(compiled);
if (file == null) res.set(compiled, file = []);
const line = file[compiledRow] ??= [];
line[line.length] = [compiledCol, [filenames[originalFile], originalRow, originalCol]];
if (!lastCols.has(compiledCol)) {
line[line.length] = [compiledCol, [filenames[originalFile], originalRow, originalCol]];
}
lastCols.add(compiledCol);
}
line.sort((a, b) => a[0] - b[0]);
}
return new VLQSourceMap(res);

View File

@ -0,0 +1,10 @@
import { type SourceMap } from "./map.ts";
declare global {
type CompilerFactory = (next: Compiler) => Compiler;
type Compiler = (filename: string, src: string, mapper: SourceMap) => Function;
function print(...args: any[]): void;
function register(factory: CompilerFactory): void;
function registerSource(filename: string, src: string): void;
}

View File

@ -17,7 +17,7 @@ function resource(name: string) {
else return resources[name] = getResource(name);
}
register(next => {
export default function typescript(next: Compiler): Compiler {
const files: Record<string, IScriptSnapshot> = {};
const versions: Record<string, number> = {};
let declI = 0;
@ -34,6 +34,7 @@ register(next => {
forceConsistentCasingInFileNames: true,
declaration: true,
sourceMap: true,
downlevelIteration: true,
};
let service: LanguageService;
@ -114,7 +115,7 @@ register(next => {
const result = outputs["/src.js"];
const declaration = outputs["/src.d.ts"];
const compiled = next("ts-internal://" + filename, result, SourceMap.chain(prevMap, map));
const compiled = next("ts-internal://" + filename, result, SourceMap.chain(map, prevMap));
registerSource(filename, code);
return function (this: any) {
@ -123,4 +124,4 @@ register(next => {
return res;
};
};
});
}

View File

@ -30,9 +30,22 @@ public class VariableDeclareNode extends Node {
}
@Override public void compile(CompileResult target, boolean pollute) {
for (var entry : values) {
var index = target.scope.get(entry.var.name, false);
if (entry.value != null) {
entry.value.compile(target, true);
target.add(VariableNode.toSet(target, loc(), entry.var.name, false, true)).setLocation(loc());
}
if (index == null) {
if (entry.value == null) {
target.add(Instruction.globDef(entry.var.name));
}
else {
target.add(Instruction.globSet(entry.var.name, false, true));
}
}
else if (entry.value != null) {
target.add(index.index().toSet(false));
}
}

View File

@ -32,6 +32,7 @@ public class RegexNode extends Node {
var inBrackets = false;
loop: while (true) {
if (i + n >= src.size()) break;
switch (src.at(i + n)) {
case '[':
inBrackets = true;

View File

@ -159,7 +159,9 @@ public class DebugServer {
}
public void awaitConnection() throws InterruptedException {
connNotifier.wait();
synchronized (connNotifier) {
connNotifier.wait();
}
}
public void run(InetSocketAddress address) {

View File

@ -1032,24 +1032,29 @@ public class SimpleDebugger implements Debugger {
}
mappings.put(body, map);
}
private boolean instructionLock;
@Override public boolean onInstruction(Environment env, Frame cf, Instruction instruction, Value returnVal, EngineException error, boolean caught) {
if (!enabled) return false;
if (instructionLock) return false;
instructionLock = true;
boolean isBreakpointable;
Location loc;
DebugFrame frame;
BreakpointType bptType;
synchronized (this) {
try {
boolean isBreakpointable;
Location loc;
DebugFrame frame;
BreakpointType bptType;
frame = getFrame(cf);
var map = DebugContext.get(env).getMap(frame.frame.function);
frame.updateLoc(map.toLocation(frame.frame.codePtr));
loc = frame.location;
bptType = map.getBreakpoint(frame.frame.codePtr);
isBreakpointable = loc != null && (bptType.shouldStepIn());
if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) {
pauseException(env, error);
}
@ -1077,49 +1082,55 @@ public class SimpleDebugger implements Debugger {
instruction.params.length == 1 &&
instruction.get(0).equals("debug")
) pauseDebug(env, null);
}
while (enabled) {
synchronized (this) {
switch (state) {
case PAUSED_EXCEPTION:
case PAUSED_NORMAL: break;
case STEPPING_OUT:
case RESUMED: return false;
case STEPPING_IN:
case STEPPING_OVER:
if (stepOutFrame.frame == frame.frame) {
if (returnVal != null || error != null) {
state = State.STEPPING_OUT;
continue;
}
else if (stepOutPtr != frame.frame.codePtr) {
if (state == State.STEPPING_IN && bptType.shouldStepIn()) {
pauseDebug(env, null);
break;
}
while (enabled) {
synchronized (this) {
switch (state) {
case PAUSED_EXCEPTION:
case PAUSED_NORMAL: break;
case STEPPING_OUT:
case RESUMED: return false;
case STEPPING_IN:
case STEPPING_OVER:
if (stepOutFrame.frame == frame.frame) {
if (returnVal != null || error != null) {
state = State.STEPPING_OUT;
continue;
}
else if (state == State.STEPPING_OVER && bptType.shouldStepOver()) {
pauseDebug(env, null);
break;
else if (stepOutPtr != frame.frame.codePtr) {
if (state == State.STEPPING_IN && bptType.shouldStepIn()) {
pauseDebug(env, null);
break;
}
else if (state == State.STEPPING_OVER && bptType.shouldStepOver()) {
pauseDebug(env, null);
break;
}
}
}
}
return false;
return false;
}
}
try {
synchronized (updateNotifier) {
updateNotifier.wait();
}
}
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
try {
synchronized (updateNotifier) {
updateNotifier.wait();
}
}
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
return false;
}
finally {
instructionLock = false;
}
return false;
}
@Override public synchronized void onFramePush(Environment env, Frame frame) {
var prevFrame = currFrame;

View File

@ -32,16 +32,17 @@ public class Arguments {
public Value self() {
return get(-1);
}
@SuppressWarnings("unchecked")
public <T> T self(Class<T> clazz) {
if (self instanceof UserValue user && clazz.isInstance(user.value)) return (T)user.value;
else return null;
return UserValue.unwrap(clazz, self);
}
public Value get(int i) {
if (i >= args.length || i < -1) return Value.UNDEFINED;
else if (i == -1) return self;
else return args[i];
}
public <T> T get(Class<T> clazz, int i) {
return UserValue.unwrap(clazz, get(i));
}
public Value getOrDefault(int i, Value def) {
if (i < -1 || i >= args.length) return def;
else return get(i);

View File

@ -115,7 +115,7 @@ public abstract class FunctionValue extends ObjectValue {
this.length = length;
this.name = name;
prototype.defineOwnField(null, "constructor", this);
prototype.defineOwnField(null, "constructor", this, true, false, true);
}
}

View File

@ -77,4 +77,10 @@ public final class UserValue<T> extends Value {
public static <T> UserValue<T> of(T value, ObjectValue prototype) {
return new UserValue<T>(value, prototype);
}
@SuppressWarnings("unchecked")
public static <T> T unwrap(Class<T> clazz, Value val) {
if (val instanceof UserValue user && clazz.isInstance(user.value)) return (T)user.value;
else return null;
}
}