Core library reprogramming #5

Merged
TopchetoEU merged 23 commits from TopchetoEU/corelib-reprogramming into master 2023-10-04 05:50:26 +00:00
33 changed files with 1330 additions and 718 deletions
Showing only changes of commit 356a5a5b78 - Show all commits

View File

@ -2,12 +2,17 @@ interface Environment {
global: typeof globalThis & Record<string, any>;
proto(name: string): object;
setProto(name: string, val: object): void;
symbol(name: string): symbol;
}
interface Internals {
object: ObjectConstructor;
function: FunctionConstructor;
promise: typeof Promise;
markSpecial(...funcs: Function[]): void;
getEnv(func: Function): Environment | undefined;
setEnv<T>(func: T, env: Environment): T;
apply(func: Function, thisArg: any, args: any[]): any;
apply(func: Function, thisArg: any, args: any[], env?: Environment): any;
delay(timeout: number, callback: Function): () => void;
pushMessage(micro: boolean, func: Function, thisArg: any, args: any[]): void;
@ -55,6 +60,7 @@ try {
run('timeout');
env.global.log = log;
env.global.NewObject = internals.object;
log('Loaded polyfills!');
}

View File

@ -1,203 +1,208 @@
define("promise", () => {
const syms = {
callbacks: internals.symbol('Promise.callbacks'),
state: internals.symbol('Promise.state'),
value: internals.symbol('Promise.value'),
handled: internals.symbol('Promise.handled'),
} as {
readonly callbacks: unique symbol,
readonly state: unique symbol,
readonly value: unique symbol,
readonly handled: unique symbol,
}
type Callback<T> = [ PromiseFulfillFunc<T>, PromiseRejectFunc ];
enum State {
Pending,
Fulfilled,
Rejected,
}
function isAwaitable(val: unknown): val is Thenable<any> {
return (
typeof val === 'object' &&
val !== null &&
'then' in val &&
typeof val.then === 'function'
);
}
function resolve(promise: Promise<any>, v: any, state: State) {
if (promise[syms.state] === State.Pending) {
if (typeof v === 'object' && v !== null && 'then' in v && typeof v.then === 'function') {
v.then(
(res: any) => resolve(promise, res, state),
(res: any) => resolve(promise, res, State.Rejected)
);
var Promise = env.global.Promise = internals.promise
return;
}
promise[syms.value] = v;
promise[syms.state] = state;
for (let i = 0; i < promise[syms.callbacks]!.length; i++) {
promise[syms.handled] = true;
promise[syms.callbacks]![i][state - 1](v);
}
// const syms = {
// callbacks: internals.symbol('Promise.callbacks'),
// state: internals.symbol('Promise.state'),
// value: internals.symbol('Promise.value'),
// handled: internals.symbol('Promise.handled'),
// } as {
// readonly callbacks: unique symbol,
// readonly state: unique symbol,
// readonly value: unique symbol,
// readonly handled: unique symbol,
// }
promise[syms.callbacks] = undefined;
// type Callback<T> = [ PromiseFulfillFunc<T>, PromiseRejectFunc ];
// enum State {
// Pending,
// Fulfilled,
// Rejected,
// }
internals.pushMessage(true, internals.setEnv(() => {
if (!promise[syms.handled] && state === State.Rejected) {
log('Uncaught (in promise) ' + promise[syms.value]);
}
}, env), undefined, []);
}
}
// function isAwaitable(val: unknown): val is Thenable<any> {
// return (
// typeof val === 'object' &&
// val !== null &&
// 'then' in val &&
// typeof val.then === 'function'
// );
// }
// function resolve(promise: Promise<any>, v: any, state: State) {
// if (promise[syms.state] === State.Pending) {
// if (typeof v === 'object' && v !== null && 'then' in v && typeof v.then === 'function') {
// v.then(
// (res: any) => resolve(promise, res, state),
// (res: any) => resolve(promise, res, State.Rejected)
// );
// return;
// }
// promise[syms.value] = v;
// promise[syms.state] = state;
class Promise<T> {
public static isAwaitable(val: unknown): val is Thenable<any> {
return isAwaitable(val);
}
// for (let i = 0; i < promise[syms.callbacks]!.length; i++) {
// promise[syms.handled] = true;
// promise[syms.callbacks]![i][state - 1](v);
// }
public static resolve<T>(val: T): Promise<Awaited<T>> {
return new Promise(res => res(val as any));
}
public static reject<T>(val: T): Promise<Awaited<T>> {
return new Promise((_, rej) => rej(val as any));
}
// promise[syms.callbacks] = undefined;
public static race<T>(vals: T[]): Promise<Awaited<T>> {
if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.race is not variadic.');
return new Promise((res, rej) => {
for (let i = 0; i < vals.length; i++) {
const val = vals[i];
if (this.isAwaitable(val)) val.then(res, rej);
else res(val as any);
}
});
}
public static any<T>(vals: T[]): Promise<Awaited<T>> {
if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.any is not variadic.');
return new Promise((res, rej) => {
let n = 0;
for (let i = 0; i < vals.length; i++) {
const val = vals[i];
if (this.isAwaitable(val)) val.then(res, (err) => {
n++;
if (n === vals.length) throw Error('No promise resolved.');
});
else res(val as any);
}
if (vals.length === 0) throw Error('No promise resolved.');
});
}
public static all(vals: any[]): Promise<any[]> {
if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.all is not variadic.');
return new Promise((res, rej) => {
const result: any[] = [];
let n = 0;
for (let i = 0; i < vals.length; i++) {
const val = vals[i];
if (this.isAwaitable(val)) val.then(
val => {
n++;
result[i] = val;
if (n === vals.length) res(result);
},
rej
);
else {
n++;
result[i] = val;
}
}
if (vals.length === n) res(result);
});
}
public static allSettled(vals: any[]): Promise<any[]> {
if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.allSettled is not variadic.');
return new Promise((res, rej) => {
const result: any[] = [];
let n = 0;
for (let i = 0; i < vals.length; i++) {
const value = vals[i];
if (this.isAwaitable(value)) value.then(
value => {
n++;
result[i] = { status: 'fulfilled', value };
if (n === vals.length) res(result);
},
reason => {
n++;
result[i] = { status: 'rejected', reason };
if (n === vals.length) res(result);
},
);
else {
n++;
result[i] = { status: 'fulfilled', value };
}
}
if (vals.length === n) res(result);
});
}
[syms.callbacks]?: Callback<T>[] = [];
[syms.handled] = false;
[syms.state] = State.Pending;
[syms.value]?: T | unknown;
public then(onFulfil?: PromiseFulfillFunc<T>, onReject?: PromiseRejectFunc) {
return new Promise((resolve, reject) => {
onFulfil ??= v => v;
onReject ??= v => v;
const callback = (func: (val: any) => any) => (v: any) => {
try { resolve(func(v)); }
catch (e) { reject(e); }
}
switch (this[syms.state]) {
case State.Pending:
this[syms.callbacks]![this[syms.callbacks]!.length] = [callback(onFulfil), callback(onReject)];
break;
case State.Fulfilled:
this[syms.handled] = true;
callback(onFulfil)(this[syms.value]);
break;
case State.Rejected:
this[syms.handled] = true;
callback(onReject)(this[syms.value]);
break;
}
})
}
public catch(func: PromiseRejectFunc) {
return this.then(undefined, func);
}
public finally(func: () => void) {
return this.then(
v => {
func();
return v;
},
v => {
func();
throw v;
}
);
}
public constructor(func: PromiseFunc<T>) {
internals.pushMessage(true, func, undefined, [
((v) => resolve(this, v, State.Fulfilled)) as PromiseFulfillFunc<T>,
((err) => resolve(this, err, State.Rejected)) as PromiseRejectFunc
]);
}
}
env.global.Promise = Promise as any;
// internals.pushMessage(true, internals.setEnv(() => {
// if (!promise[syms.handled] && state === State.Rejected) {
// log('Uncaught (in promise) ' + promise[syms.value]);
// }
// }, env), undefined, []);
// }
// }
// class _Promise<T> {
// public static isAwaitable(val: unknown): val is Thenable<any> {
// return isAwaitable(val);
// }
// public static resolve<T>(val: T): Promise<Awaited<T>> {
// return new Promise(res => res(val as any));
// }
// public static reject<T>(val: T): Promise<Awaited<T>> {
// return new Promise((_, rej) => rej(val as any));
// }
// public static race<T>(vals: T[]): Promise<Awaited<T>> {
// if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.race is not variadic.');
// return new Promise((res, rej) => {
// for (let i = 0; i < vals.length; i++) {
// const val = vals[i];
// if (this.isAwaitable(val)) val.then(res, rej);
// else res(val as any);
// }
// });
// }
// public static any<T>(vals: T[]): Promise<Awaited<T>> {
// if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.any is not variadic.');
// return new Promise((res, rej) => {
// let n = 0;
// for (let i = 0; i < vals.length; i++) {
// const val = vals[i];
// if (this.isAwaitable(val)) val.then(res, (err) => {
// n++;
// if (n === vals.length) throw Error('No promise resolved.');
// });
// else res(val as any);
// }
// if (vals.length === 0) throw Error('No promise resolved.');
// });
// }
// public static all(vals: any[]): Promise<any[]> {
// if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.all is not variadic.');
// return new Promise((res, rej) => {
// const result: any[] = [];
// let n = 0;
// for (let i = 0; i < vals.length; i++) {
// const val = vals[i];
// if (this.isAwaitable(val)) val.then(
// val => {
// n++;
// result[i] = val;
// if (n === vals.length) res(result);
// },
// rej
// );
// else {
// n++;
// result[i] = val;
// }
// }
// if (vals.length === n) res(result);
// });
// }
// public static allSettled(vals: any[]): Promise<any[]> {
// if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.allSettled is not variadic.');
// return new Promise((res, rej) => {
// const result: any[] = [];
// let n = 0;
// for (let i = 0; i < vals.length; i++) {
// const value = vals[i];
// if (this.isAwaitable(value)) value.then(
// value => {
// n++;
// result[i] = { status: 'fulfilled', value };
// if (n === vals.length) res(result);
// },
// reason => {
// n++;
// result[i] = { status: 'rejected', reason };
// if (n === vals.length) res(result);
// },
// );
// else {
// n++;
// result[i] = { status: 'fulfilled', value };
// }
// }
// if (vals.length === n) res(result);
// });
// }
// [syms.callbacks]?: Callback<T>[] = [];
// [syms.handled] = false;
// [syms.state] = State.Pending;
// [syms.value]?: T | unknown;
// public then(onFulfil?: PromiseFulfillFunc<T>, onReject?: PromiseRejectFunc) {
// return new Promise((resolve, reject) => {
// onFulfil ??= v => v;
// onReject ??= v => v;
// const callback = (func: (val: any) => any) => (v: any) => {
// try {
// resolve(func(v));
// }
// catch (e) { reject(e); }
// }
// switch (this[syms.state]) {
// case State.Pending:
// this[syms.callbacks]![this[syms.callbacks]!.length] = [callback(onFulfil), callback(onReject)];
// break;
// case State.Fulfilled:
// this[syms.handled] = true;
// callback(onFulfil)(this[syms.value]);
// break;
// case State.Rejected:
// this[syms.handled] = true;
// callback(onReject)(this[syms.value]);
// break;
// }
// })
// }
// public catch(func: PromiseRejectFunc) {
// return this.then(undefined, func);
// }
// public finally(func: () => void) {
// return this.then(
// v => {
// func();
// return v;
// },
// v => {
// func();
// throw v;
// }
// );
// }
// public constructor(func: PromiseFunc<T>) {
// internals.pushMessage(true, func, undefined, [
// ((v) => resolve(this, v, State.Fulfilled)) as PromiseFulfillFunc<T>,
// ((err) => resolve(this, err, State.Rejected)) as PromiseRejectFunc
// ]);
// }
// }
// env.global.Promise = Promise as any;
});

View File

@ -4,14 +4,14 @@ define("timeout", () => {
let timeoutI = 0, intervalI = 0;
env.global.setTimeout = (func, delay, ...args) => {
if (typeof func !== 'function') throw new TypeError("func must be a function.");
if (typeof func !== 'function') throw new env.global.TypeError("func must be a function.");
delay = (delay ?? 0) - 0;
const cancelFunc = internals.delay(delay, () => internals.apply(func, undefined, args));
timeouts[++timeoutI] = cancelFunc;
return timeoutI;
};
env.global.setInterval = (func, delay, ...args) => {
if (typeof func !== 'function') throw new TypeError("func must be a function.");
if (typeof func !== 'function') throw new env.global.TypeError("func must be a function.");
delay = (delay ?? 0) - 0;
const i = ++intervalI;

View File

@ -1,140 +1,5 @@
define("values/function", () => {
var Function = env.global.Function = function() {
throw 'Using the constructor Function() is forbidden.';
} as unknown as FunctionConstructor;
var Function = env.global.Function = internals.function;
env.setProto('function', Function.prototype);
setConstr(Function.prototype, Function);
setProps(Function.prototype, {
apply(thisArg, args) {
if (typeof args !== 'object') throw 'Expected arguments to be an array-like object.';
var len = args.length - 0;
let newArgs: any[];
if (internals.isArray(args)) newArgs = args;
else {
newArgs = [];
while (len >= 0) {
len--;
newArgs[len] = args[len];
}
}
return internals.apply(this, thisArg, newArgs);
},
call(thisArg, ...args) {
return this.apply(thisArg, args);
},
bind(thisArg, ...args) {
const func = this;
const res = function() {
const resArgs = [];
for (let i = 0; i < args.length; i++) {
resArgs[i] = args[i];
}
for (let i = 0; i < arguments.length; i++) {
resArgs[i + args.length] = arguments[i];
}
return func.apply(thisArg, resArgs);
};
res.name = "<bound> " + func.name;
return res;
},
toString() {
return 'function (...) { ... }';
},
});
setProps(Function, {
async(func) {
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
return function (this: any) {
const args = arguments;
return new Promise((res, rej) => {
const gen = internals.apply(internals.generator(func as any), this, args as any);
(function next(type: 'none' | 'err' | 'ret', val?: any) {
try {
let result;
switch (type) {
case 'err': result = gen.throw(val); break;
case 'ret': result = gen.next(val); break;
case 'none': result = gen.next(); break;
}
if (result.done) res(result.value);
else Promise.resolve(result.value).then(
v => next('ret', v),
v => next('err', v)
)
}
catch (e) {
rej(e);
}
})('none');
});
};
},
asyncGenerator(func) {
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
return function(this: any, ...args: any[]) {
const gen = internals.apply(internals.generator((_yield) => func(
val => _yield(['await', val]) as any,
val => _yield(['yield', val])
)), this, args) as Generator<['await' | 'yield', any]>;
const next = (resolve: Function, reject: Function, type: 'none' | 'val' | 'ret' | 'err', val?: any) => {
let res;
try {
switch (type) {
case 'val': res = gen.next(val); break;
case 'ret': res = gen.return(val); break;
case 'err': res = gen.throw(val); break;
default: res = gen.next(); break;
}
}
catch (e) { return reject(e); }
if (res.done) return { done: true, res: <any>res };
else if (res.value[0] === 'await') Promise.resolve(res.value[1]).then(
v => next(resolve, reject, 'val', v),
v => next(resolve, reject, 'err', v),
)
else resolve({ done: false, value: res.value[1] });
};
return {
next() {
const args = arguments;
if (arguments.length === 0) return new Promise((res, rej) => next(res, rej, 'none'));
else return new Promise((res, rej) => next(res, rej, 'val', args[0]));
},
return: (value) => new Promise((res, rej) => next(res, rej, 'ret', value)),
throw: (value) => new Promise((res, rej) => next(res, rej, 'err', value)),
[env.global.Symbol.asyncIterator]() { return this; }
}
}
},
generator(func) {
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
const gen = internals.generator(func);
return function(this: any, ...args: any[]) {
const it = internals.apply(gen, this, args);
return {
next: (...args) => internals.apply(it.next, it, args),
return: (val) => internals.apply(it.next, it, [val]),
throw: (val) => internals.apply(it.next, it, [val]),
[env.global.Symbol.iterator]() { return this; }
}
}
}
})
internals.markSpecial(Function);
});

View File

@ -1,226 +1,5 @@
define("values/object", () => {
var Object = env.global.Object = function(arg: any) {
if (arg === undefined || arg === null) return {};
else if (typeof arg === 'boolean') return new Boolean(arg);
else if (typeof arg === 'number') return new Number(arg);
else if (typeof arg === 'string') return new String(arg);
return arg;
} as ObjectConstructor;
env.setProto('object', Object.prototype);
var Object = env.global.Object = internals.object;
(Object.prototype as any).__proto__ = null;
setConstr(Object.prototype, Object as any);
function throwNotObject(obj: any, name: string) {
if (obj === null || typeof obj !== 'object' && typeof obj !== 'function') {
throw new TypeError(`Object.${name} may only be used for objects.`);
}
}
function check(obj: any) {
return typeof obj === 'object' && obj !== null || typeof obj === 'function';
}
setProps(Object, {
assign(dst, ...src) {
throwNotObject(dst, 'assign');
for (let i = 0; i < src.length; i++) {
const obj = src[i];
throwNotObject(obj, 'assign');
for (const key of Object.keys(obj)) {
(dst as any)[key] = (obj as any)[key];
}
}
return dst;
},
create(obj, props) {
props ??= {};
return Object.defineProperties({ __proto__: obj }, props as any) as any;
},
defineProperty(obj, key, attrib) {
throwNotObject(obj, 'defineProperty');
if (typeof attrib !== 'object') throw new TypeError('Expected attributes to be an object.');
if ('value' in attrib) {
if ('get' in attrib || 'set' in attrib) throw new TypeError('Cannot specify a value and accessors for a property.');
if (!internals.defineField(
obj, key,
attrib.value,
!!attrib.writable,
!!attrib.enumerable,
!!attrib.configurable
)) throw new TypeError('Can\'t define property \'' + key + '\'.');
}
else {
if (typeof attrib.get !== 'function' && attrib.get !== undefined) throw new TypeError('Get accessor must be a function.');
if (typeof attrib.set !== 'function' && attrib.set !== undefined) throw new TypeError('Set accessor must be a function.');
if (!internals.defineProp(
obj, key,
attrib.get,
attrib.set,
!!attrib.enumerable,
!!attrib.configurable
)) throw new TypeError('Can\'t define property \'' + key + '\'.');
}
return obj;
},
defineProperties(obj, attrib) {
throwNotObject(obj, 'defineProperties');
if (typeof attrib !== 'object' && typeof attrib !== 'function') throw 'Expected second argument to be an object.';
for (var key in attrib) {
Object.defineProperty(obj, key, attrib[key]);
}
return obj;
},
keys(obj, onlyString) {
return internals.keys(obj, !!(onlyString ?? true));
},
entries(obj, onlyString) {
const res = [];
const keys = internals.keys(obj, !!(onlyString ?? true));
for (let i = 0; i < keys.length; i++) {
res[i] = [ keys[i], (obj as any)[keys[i]] ];
}
return keys;
},
values(obj, onlyString) {
const res = [];
const keys = internals.keys(obj, !!(onlyString ?? true));
for (let i = 0; i < keys.length; i++) {
res[i] = (obj as any)[keys[i]];
}
return keys;
},
getOwnPropertyDescriptor(obj, key) {
return internals.ownProp(obj, key) as any;
},
getOwnPropertyDescriptors(obj) {
const res = [];
const keys = internals.ownPropKeys(obj);
for (let i = 0; i < keys.length; i++) {
res[i] = internals.ownProp(obj, keys[i]);
}
return res;
},
getOwnPropertyNames(obj) {
const arr = internals.ownPropKeys(obj);
const res = [];
for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] === 'symbol') continue;
res[res.length] = arr[i];
}
return res as any;
},
getOwnPropertySymbols(obj) {
const arr = internals.ownPropKeys(obj);
const res = [];
for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] !== 'symbol') continue;
res[res.length] = arr[i];
}
return res as any;
},
hasOwn(obj, key) {
const keys = internals.ownPropKeys(obj);
for (let i = 0; i < keys.length; i++) {
if (keys[i] === key) return true;
}
return false;
},
getPrototypeOf(obj) {
return obj.__proto__;
},
setPrototypeOf(obj, proto) {
(obj as any).__proto__ = proto;
return obj;
},
fromEntries(iterable) {
const res = {} as any;
for (const el of iterable) {
res[el[0]] = el[1];
}
return res;
},
preventExtensions(obj) {
throwNotObject(obj, 'preventExtensions');
internals.lock(obj, 'ext');
return obj;
},
seal(obj) {
throwNotObject(obj, 'seal');
internals.lock(obj, 'seal');
return obj;
},
freeze(obj) {
throwNotObject(obj, 'freeze');
internals.lock(obj, 'freeze');
return obj;
},
isExtensible(obj) {
if (!check(obj)) return false;
return internals.extensible(obj);
},
isSealed(obj) {
if (!check(obj)) return true;
if (internals.extensible(obj)) return false;
const keys = internals.ownPropKeys(obj);
for (let i = 0; i < keys.length; i++) {
if (internals.ownProp(obj, keys[i]).configurable) return false;
}
return true;
},
isFrozen(obj) {
if (!check(obj)) return true;
if (internals.extensible(obj)) return false;
const keys = internals.ownPropKeys(obj);
for (let i = 0; i < keys.length; i++) {
const prop = internals.ownProp(obj, keys[i]);
if (prop.configurable) return false;
if ('writable' in prop && prop.writable) return false;
}
return true;
}
});
setProps(Object.prototype, {
valueOf() {
return this;
},
toString() {
return '[object ' + (this[env.global.Symbol.typeName] ?? 'Unknown') + ']';
},
hasOwnProperty(key) {
return Object.hasOwn(this, key);
},
});
internals.markSpecial(Object);
env.setProto('object', Object.prototype);
});

View File

@ -2,8 +2,8 @@ define("values/symbol", () => {
const symbols: Record<string, symbol> = { };
var Symbol = env.global.Symbol = function(this: any, val?: string) {
if (this !== undefined && this !== null) throw new TypeError("Symbol may not be called with 'new'.");
if (typeof val !== 'string' && val !== undefined) throw new TypeError('val must be a string or undefined.');
if (this !== undefined && this !== null) throw new env.global.TypeError("Symbol may not be called with 'new'.");
if (typeof val !== 'string' && val !== undefined) throw new env.global.TypeError('val must be a string or undefined.');
return internals.symbol(val);
} as SymbolConstructor;
@ -12,23 +12,23 @@ define("values/symbol", () => {
setProps(Symbol, {
for(key) {
if (typeof key !== 'string' && key !== undefined) throw new TypeError('key must be a string or undefined.');
if (typeof key !== 'string' && key !== undefined) throw new env.global.TypeError('key must be a string or undefined.');
if (key in symbols) return symbols[key];
else return symbols[key] = internals.symbol(key);
},
keyFor(sym) {
if (typeof sym !== 'symbol') throw new TypeError('sym must be a symbol.');
if (typeof sym !== 'symbol') throw new env.global.TypeError('sym must be a symbol.');
return internals.symbolToString(sym);
},
typeName: Symbol("Symbol.name") as any,
replace: Symbol('Symbol.replace') as any,
match: Symbol('Symbol.match') as any,
matchAll: Symbol('Symbol.matchAll') as any,
split: Symbol('Symbol.split') as any,
search: Symbol('Symbol.search') as any,
iterator: Symbol('Symbol.iterator') as any,
asyncIterator: Symbol('Symbol.asyncIterator') as any,
typeName: env.symbol("Symbol.typeName") as any,
replace: env.symbol('Symbol.replace') as any,
match: env.symbol('Symbol.match') as any,
matchAll: env.symbol('Symbol.matchAll') as any,
split: env.symbol('Symbol.split') as any,
search: env.symbol('Symbol.search') as any,
iterator: env.symbol('Symbol.iterator') as any,
asyncIterator: env.symbol('Symbol.asyncIterator') as any,
});
internals.defineField(env.global.Object.prototype, Symbol.typeName, 'Object', false, false, false);

View File

@ -10,7 +10,7 @@ import java.nio.file.Path;
import me.topchetoeu.jscript.engine.MessageContext;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.FunctionContext;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException;
@ -21,7 +21,7 @@ import me.topchetoeu.jscript.polyfills.Internals;
public class Main {
static Thread task;
static Engine engine;
static FunctionContext env;
static Environment env;
public static String streamToString(InputStream in) {
try {
@ -47,37 +47,14 @@ public class Main {
private static Observer<Object> valuePrinter = new Observer<Object>() {
public void next(Object data) {
try {
Values.printValue(null, data);
}
try { Values.printValue(null, data); }
catch (InterruptedException e) { }
System.out.println();
}
public void error(RuntimeException err) {
try {
try {
if (err instanceof EngineException) {
System.out.println("Uncaught " + ((EngineException)err).toString(new Context(null, new MessageContext(engine))));
}
else if (err instanceof SyntaxException) {
System.out.println("Syntax error:" + ((SyntaxException)err).msg);
}
else if (err.getCause() instanceof InterruptedException) return;
else {
System.out.println("Internal error ocurred:");
err.printStackTrace();
}
}
catch (EngineException ex) {
System.out.println("Uncaught ");
Values.printValue(null, ((EngineException)err).value);
System.out.println();
}
}
catch (InterruptedException ex) {
return;
}
try { Values.printError(err, null); }
catch (InterruptedException ex) { return; }
}
};
@ -85,8 +62,10 @@ public class Main {
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
var in = new BufferedReader(new InputStreamReader(System.in));
engine = new Engine();
env = new FunctionContext(null, null, null);
var builderEnv = new FunctionContext(null, new NativeTypeRegister(), null);
// TODO: Replace type register with safer accessor
env = new Environment(null, new NativeTypeRegister(), null);
var builderEnv = new Environment(null, new NativeTypeRegister(), null);
var exited = new boolean[1];
env.global.define("exit", ctx -> {

View File

@ -5,16 +5,16 @@ import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.parsing.Parsing;
public class Context {
public final FunctionContext function;
public final Environment env;
public final MessageContext message;
public FunctionValue compile(String filename, String raw) throws InterruptedException {
var res = Values.toString(this, function.compile.call(this, null, raw, filename));
return Parsing.compile(function, filename, res);
var res = Values.toString(this, env.compile.call(this, null, raw, filename));
return Parsing.compile(env, filename, res);
}
public Context(FunctionContext funcCtx, MessageContext msgCtx) {
this.function = funcCtx;
public Context(Environment funcCtx, MessageContext msgCtx) {
this.env = funcCtx;
this.message = msgCtx;
}
}

View File

@ -11,7 +11,7 @@ public class Engine {
private class UncompiledFunction extends FunctionValue {
public final String filename;
public final String raw;
public final FunctionContext ctx;
public final Environment ctx;
@Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
@ -19,7 +19,7 @@ public class Engine {
return ctx.compile(filename, raw).call(ctx, thisArg, args);
}
public UncompiledFunction(FunctionContext ctx, String filename, String raw) {
public UncompiledFunction(Environment ctx, String filename, String raw) {
super(filename, 0);
this.filename = filename;
this.raw = raw;
@ -109,7 +109,7 @@ public class Engine {
return msg.notifier;
}
public Awaitable<Object> pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.function, filename, raw), thisArg, args);
return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.env, filename, raw), thisArg, args);
}
// public Engine() {

View File

@ -6,15 +6,20 @@ import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter;
public class FunctionContext {
public class Environment {
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
public GlobalScope global;
public WrappersProvider wrappersProvider;
/**
* NOTE: This is not the register for Symbol.for, but for the symbols like Symbol.iterator
*/
public HashMap<String, Symbol> symbols = new HashMap<>();
@Native public FunctionValue compile;
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
@ -26,6 +31,16 @@ public class FunctionContext {
@Native public void setProto(String name, ObjectValue val) {
prototypes.put(name, val);
}
@Native public Symbol symbol(String name) {
if (symbols.containsKey(name)) return symbols.get(name);
else {
var res = new Symbol(name);
symbols.put(name, res);
return res;
}
}
// @Native public ObjectValue arrayPrototype = new ObjectValue();
// @Native public ObjectValue boolPrototype = new ObjectValue();
// @Native public ObjectValue functionPrototype = new ObjectValue();
@ -48,21 +63,21 @@ public class FunctionContext {
}
@Native
public FunctionContext fork() {
var res = new FunctionContext(compile, wrappersProvider, global);
public Environment fork() {
var res = new Environment(compile, wrappersProvider, global);
res.regexConstructor = regexConstructor;
res.prototypes = new HashMap<>(prototypes);
return res;
}
@Native
public FunctionContext child() {
public Environment child() {
var res = fork();
res.global = res.global.globalChild();
return res;
}
public FunctionContext(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
if (compile == null) compile = new NativeFunction("compile", (ctx, thisArg, args) -> args.length == 0 ? "" : args[0]);
if (nativeConverter == null) nativeConverter = new WrappersProvider() {
public ObjectValue getConstr(Class<?> obj) {

View File

@ -107,11 +107,11 @@ public class CodeFrame {
return Runners.exec(ctx, instr, this);
}
catch (EngineException e) {
throw e.add(function.name, prevLoc);
throw e.add(function.name, prevLoc).setContext(ctx);
}
}
public Object next(Context ctx, Object prevReturn, Object prevError) throws InterruptedException {
public Object next(Context ctx, Object prevValue, Object prevReturn, Object prevError) throws InterruptedException {
TryCtx tryCtx = null;
if (prevError != Runners.NO_RETURN) prevReturn = Runners.NO_RETURN;
@ -196,6 +196,7 @@ public class CodeFrame {
if (prevError != Runners.NO_RETURN) throw new EngineException(prevError);
if (prevReturn != Runners.NO_RETURN) return prevReturn;
if (prevValue != Runners.NO_RETURN) push(ctx, prevValue);
if (tryCtx == null) return nextNoTry(ctx);
else if (tryCtx.state == TryCtx.STATE_TRY) {
@ -263,7 +264,7 @@ public class CodeFrame {
try {
ctx.message.pushFrame(this);
while (true) {
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN);
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
if (res != Runners.NO_RETURN) return res;
}
}

View File

@ -48,16 +48,18 @@ public class Runners {
var callArgs = frame.take(instr.get(0));
var funcObj = frame.pop();
if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
frame.push(ctx, call(ctx, funcObj, null, callArgs));
}
else {
var proto = Values.getMember(ctx, funcObj, "prototype");
var obj = new ObjectValue();
obj.setPrototype(ctx, proto);
call(ctx, funcObj, obj, callArgs);
frame.push(ctx, obj);
}
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
// if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
// frame.push(ctx, call(ctx, funcObj, null, callArgs));
// }
// else {
// var proto = Values.getMember(ctx, funcObj, "prototype");
// var obj = new ObjectValue();
// obj.setPrototype(ctx, proto);
// call(ctx, funcObj, obj, callArgs);
// frame.push(ctx, obj);
// }
frame.codePtr++;
return NO_RETURN;
@ -65,7 +67,7 @@ public class Runners {
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
var name = (String)instr.get(0);
ctx.function.global.define(name);
ctx.env.global.define(name);
frame.codePtr++;
return NO_RETURN;
}
@ -160,7 +162,7 @@ public class Runners {
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
var i = instr.get(0);
if (i instanceof String) frame.push(ctx, ctx.function.global.get(ctx, (String)i));
if (i instanceof String) frame.push(ctx, ctx.env.global.get(ctx, (String)i));
else frame.push(ctx, frame.scope.get((int)i).get(ctx));
frame.codePtr++;
@ -172,7 +174,7 @@ public class Runners {
return NO_RETURN;
}
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, ctx.function.global.obj);
frame.push(ctx, ctx.env.global.obj);
frame.codePtr++;
return NO_RETURN;
}
@ -198,7 +200,7 @@ public class Runners {
var body = new Instruction[end - start];
System.arraycopy(frame.function.body, start, body, 0, end - start);
var func = new CodeFunction(ctx.function, "", localsN, len, captures, body);
var func = new CodeFunction(ctx.env, "", localsN, len, captures, body);
frame.push(ctx, func);
frame.codePtr += n;
@ -222,7 +224,7 @@ public class Runners {
return execLoadMember(ctx, instr, frame);
}
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
frame.push(ctx, ctx.function.regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
frame.push(ctx, ctx.env.regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
frame.codePtr++;
return NO_RETURN;
}
@ -246,7 +248,7 @@ public class Runners {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
var i = instr.get(0);
if (i instanceof String) ctx.function.global.set(ctx, (String)i, val);
if (i instanceof String) ctx.env.global.set(ctx, (String)i, val);
else frame.scope.get((int)i).set(ctx, val);
frame.codePtr++;
@ -293,8 +295,8 @@ public class Runners {
Object obj;
if (name != null) {
if (ctx.function.global.has(ctx, name)) {
obj = ctx.function.global.get(ctx, name);
if (ctx.env.global.has(ctx, name)) {
obj = ctx.env.global.get(ctx, name);
}
else obj = null;
}

View File

@ -3,7 +3,7 @@ package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.FunctionContext;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
@ -12,7 +12,7 @@ public class CodeFunction extends FunctionValue {
public final int length;
public final Instruction[] body;
public final ValueVariable[] captures;
public FunctionContext environment;
public Environment environment;
public Location loc() {
for (var instr : body) {
@ -32,7 +32,7 @@ public class CodeFunction extends FunctionValue {
return new CodeFrame(ctx, thisArg, args, this).run(new Context(environment, ctx.message));
}
public CodeFunction(FunctionContext environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) {
public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) {
super(name, length);
this.captures = captures;
this.environment = environment;

View File

@ -8,7 +8,8 @@ public class NativeWrapper extends ObjectValue {
@Override
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
if (prototype == NATIVE_PROTO) return ctx.function.wrappersProvider.getProto(wrapped.getClass());
if (prototype == NATIVE_PROTO)
return ctx.env.wrappersProvider.getProto(wrapped.getClass());
else return super.getPrototype(ctx);
}

View File

@ -55,7 +55,7 @@ public class ObjectValue {
public final boolean memberWritable(Object key) {
if (state == State.FROZEN) return false;
return !nonWritableSet.contains(key);
return values.containsKey(key) && !nonWritableSet.contains(key);
}
public final boolean memberConfigurable(Object key) {
if (state == State.SEALED || state == State.FROZEN) return false;
@ -147,13 +147,13 @@ public class ObjectValue {
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
try {
if (prototype == OBJ_PROTO) return ctx.function.proto("object");
if (prototype == ARR_PROTO) return ctx.function.proto("array");
if (prototype == FUNC_PROTO) return ctx.function.proto("function");
if (prototype == ERR_PROTO) return ctx.function.proto("error");
if (prototype == RANGE_ERR_PROTO) return ctx.function.proto("rangeErr");
if (prototype == SYNTAX_ERR_PROTO) return ctx.function.proto("syntaxErr");
if (prototype == TYPE_ERR_PROTO) return ctx.function.proto("typeErr");
if (prototype == OBJ_PROTO) return ctx.env.proto("object");
if (prototype == ARR_PROTO) return ctx.env.proto("array");
if (prototype == FUNC_PROTO) return ctx.env.proto("function");
if (prototype == ERR_PROTO) return ctx.env.proto("error");
if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr");
if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr");
if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr");
}
catch (NullPointerException e) {
return null;
@ -172,14 +172,14 @@ public class ObjectValue {
else if (Values.isObject(val)) {
var obj = Values.object(val);
if (ctx != null && ctx.function != null) {
if (obj == ctx.function.proto("object")) prototype = OBJ_PROTO;
else if (obj == ctx.function.proto("array")) prototype = ARR_PROTO;
else if (obj == ctx.function.proto("function")) prototype = FUNC_PROTO;
else if (obj == ctx.function.proto("error")) prototype = ERR_PROTO;
else if (obj == ctx.function.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.function.proto("typeErr")) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.function.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
if (ctx != null && ctx.env != null) {
if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO;
else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO;
else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO;
else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO;
else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
else prototype = obj;
}
else prototype = obj;

View File

@ -13,7 +13,9 @@ import java.util.Map;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.frame.ConvertHint;
import me.topchetoeu.jscript.exceptions.ConvertException;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
public class Values {
public static final Object NULL = new Object();
@ -321,10 +323,10 @@ public class Values {
if (isObject(obj)) return object(obj).getPrototype(ctx);
if (ctx == null) return null;
if (obj instanceof String) return ctx.function.proto("string");
else if (obj instanceof Number) return ctx.function.proto("number");
else if (obj instanceof Boolean) return ctx.function.proto("bool");
else if (obj instanceof Symbol) return ctx.function.proto("symbol");
if (obj instanceof String) return ctx.env.proto("string");
else if (obj instanceof Number) return ctx.env.proto("number");
else if (obj instanceof Boolean) return ctx.env.proto("bool");
else if (obj instanceof Symbol) return ctx.env.proto("symbol");
return null;
}
@ -352,12 +354,39 @@ public class Values {
return res;
}
public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) throws InterruptedException {
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key);
else if (obj instanceof String && key instanceof Number) {
var i = ((Number)key).intValue();
var _i = ((Number)key).doubleValue();
if (i - _i != 0) return null;
if (i < 0 || i >= ((String)obj).length()) return null;
return new ObjectValue(ctx, Map.of(
"value", ((String)obj).charAt(i) + "",
"writable", false,
"enumerable", true,
"configurable", false
));
}
else return null;
}
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException {
if (!isFunction(func))
throw EngineException.ofType("Tried to call a non-function value.");
if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
return function(func).call(ctx, thisArg, args);
}
public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException {
if (func instanceof FunctionValue && ((FunctionValue)func).special) return ((FunctionValue)func).call(ctx, null, args);
var res = new ObjectValue();
var proto = Values.getMember(ctx, func, "prototype");
res.setPrototype(ctx, proto);
call(ctx, func, res, args);
return res;
}
public static boolean strictEquals(Context ctx, Object a, Object b) {
a = normalize(ctx, a); b = normalize(ctx, b);
@ -420,7 +449,7 @@ public class Values {
if (val instanceof Class) {
if (ctx == null) return null;
else return ctx.function.wrappersProvider.getConstr((Class<?>)val);
else return ctx.env.wrappersProvider.getConstr((Class<?>)val);
}
return new NativeWrapper(val);
@ -429,17 +458,15 @@ public class Values {
@SuppressWarnings("unchecked")
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) throws InterruptedException {
if (clazz == Void.class) return null;
if (clazz == null || clazz == Object.class) return (T)obj;
var err = new IllegalArgumentException(String.format("Cannot convert '%s' to '%s'.", type(obj), clazz.getName()));
if (obj instanceof NativeWrapper) {
var res = ((NativeWrapper)obj).wrapped;
if (clazz.isInstance(res)) return (T)res;
}
if (obj instanceof ArrayValue) {
if (clazz == null || clazz == Object.class) return (T)obj;
if (obj instanceof ArrayValue) {
if (clazz.isAssignableFrom(ArrayList.class)) {
var raw = array(obj).toArray();
var res = new ArrayList<>();
@ -480,11 +507,9 @@ public class Values {
if (clazz == Character.class || clazz == char.class) {
if (obj instanceof Number) return (T)(Character)(char)number(obj);
else if (obj == NULL) throw new IllegalArgumentException("Cannot convert null to character.");
else if (obj == null) throw new IllegalArgumentException("Cannot convert undefined to character.");
else {
var res = toString(ctx, obj);
if (res.length() == 0) throw new IllegalArgumentException("Cannot convert empty string to character.");
if (res.length() == 0) throw new ConvertException("\"\"", "Character");
else return (T)(Character)res.charAt(0);
}
}
@ -492,20 +517,22 @@ public class Values {
if (obj == null) return null;
if (clazz.isInstance(obj)) return (T)obj;
throw err;
throw new ConvertException(type(obj), clazz.getName());
}
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) throws InterruptedException {
return () -> {
try {
var constr = getMember(ctx, ctx.function.proto("symbol"), "constructor");
var symbol = getMember(ctx, constr, "iterator");
var symbol = ctx.env.symbol("Symbol.iterator");
var iteratorFunc = getMember(ctx, obj, symbol);
if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
var iterator = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
if (!isFunction(iterator)) return Collections.emptyIterator();
var iterable = obj;
var iterator = iteratorFunc instanceof FunctionValue ?
((FunctionValue)iteratorFunc).call(ctx, iteratorFunc, obj) :
iteratorFunc;
var nextFunc = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
if (!isFunction(nextFunc)) return Collections.emptyIterator();
return new Iterator<Object>() {
private Object value = null;
@ -515,7 +542,7 @@ public class Values {
private void loadNext() throws InterruptedException {
if (next == null) value = null;
else if (consumed) {
var curr = object(next.call(ctx, iterable));
var curr = object(next.call(ctx, iterator));
if (curr == null) { next = null; value = null; }
if (toBoolean(curr.getMember(ctx, "done"))) { next = null; value = null; }
else {
@ -567,7 +594,7 @@ public class Values {
var it = iterable.iterator();
try {
var key = getMember(ctx, getMember(ctx, ctx.function.proto("symbol"), "constructor"), "iterator");
var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator");
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
}
catch (IllegalArgumentException | NullPointerException e) { }
@ -655,4 +682,25 @@ public class Values {
public static void printValue(Context ctx, Object val) throws InterruptedException {
printValue(ctx, val, new HashSet<>(), 0);
}
public static void printError(RuntimeException err, String prefix) throws InterruptedException {
prefix = prefix == null ? "Uncauthg" : "Uncaught " + prefix;
try {
if (err instanceof EngineException) {
System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx));
}
else if (err instanceof SyntaxException) {
System.out.println("Syntax error:" + ((SyntaxException)err).msg);
}
else if (err.getCause() instanceof InterruptedException) return;
else {
System.out.println("Internal error ocurred:");
err.printStackTrace();
}
}
catch (EngineException ex) {
System.out.println("Uncaught ");
Values.printValue(null, ((EngineException)err).value);
System.out.println();
}
}
}

View File

@ -0,0 +1,11 @@
package me.topchetoeu.jscript.exceptions;
public class ConvertException extends RuntimeException {
public final String source, target;
public ConvertException(String source, String target) {
super(String.format("Cannot convert '%s' to '%s'.", source, target));
this.source = source;
this.target = target;
}
}

View File

@ -12,6 +12,7 @@ import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
public class EngineException extends RuntimeException {
public final Object value;
public EngineException cause;
public Context ctx = null;
public final List<String> stackTrace = new ArrayList<>();
public EngineException add(String name, Location location) {
@ -27,6 +28,10 @@ public class EngineException extends RuntimeException {
this.cause = cause;
return this;
}
public EngineException setContext(Context ctx) {
this.ctx = ctx;
return this;
}
public String toString(Context ctx) throws InterruptedException {
var ss = new StringBuilder();

View File

@ -9,4 +9,5 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
public @interface Native {
public String value() default "";
public boolean raw() default false;
}

View File

@ -0,0 +1,13 @@
package me.topchetoeu.jscript.interop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeConstructor {
public boolean raw() default false;
}

View File

@ -9,4 +9,5 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeGetter {
public String value();
public boolean raw() default false;
}

View File

@ -9,4 +9,5 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeSetter {
public String value();
public boolean raw() default false;
}

View File

@ -13,25 +13,35 @@ public class NativeTypeRegister implements WrappersProvider {
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
private final HashMap<Class<?>, ObjectValue> prototypes = new HashMap<>();
private static boolean isMember(Class<?>[] args) {
return args.length == 3;
}
private static void applyMethods(boolean member, ObjectValue target, Class<?> clazz) {
for (var method : clazz.getDeclaredMethods()) {
if (!Modifier.isStatic(method.getModifiers()) != member) continue;
var nat = method.getAnnotation(Native.class);
var get = method.getAnnotation(NativeGetter.class);
var set = method.getAnnotation(NativeSetter.class);
var params = method.getParameterTypes();
var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member;
if (nat != null) {
if (nat.raw()) {
if (isMember(params) != member) continue;
}
else if (memberMismatch) continue;
var name = nat.value();
var val = target.values.get(name);
if (name.equals("")) name = method.getName();
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name));
((OverloadFunction)val).overloads.add(Overload.fromMethod(method));
((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.raw()));
}
else {
if (get != null) {
if (get.raw() && isMember(params) != member || memberMismatch) continue;
var name = get.value();
var prop = target.properties.get(name);
OverloadFunction getter = null;
@ -40,10 +50,11 @@ public class NativeTypeRegister implements WrappersProvider {
if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter;
else getter = new OverloadFunction("get " + name);
getter.overloads.add(Overload.fromMethod(method));
getter.overloads.add(Overload.fromMethod(method, get.raw()));
target.defineProperty(null, name, getter, setter, true, true);
}
if (set != null) {
if (set.raw() && isMember(params) != member || memberMismatch) continue;
var name = set.value();
var prop = target.properties.get(name);
var getter = prop == null ? null : prop.getter;
@ -52,7 +63,7 @@ public class NativeTypeRegister implements WrappersProvider {
if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter;
else setter = new OverloadFunction("set " + name);
setter.overloads.add(Overload.fromMethod(method));
setter.overloads.add(Overload.fromMethod(method, set.raw()));
target.defineProperty(null, name, getter, setter, true, true);
}
}
@ -66,8 +77,8 @@ public class NativeTypeRegister implements WrappersProvider {
if (nat != null) {
var name = nat.value();
if (name.equals("")) name = field.getName();
var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field));
var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field));
var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field, nat.raw()));
var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field, nat.raw()));
target.defineProperty(null, name, getter, setter, true, false);
}
}
@ -113,8 +124,14 @@ public class NativeTypeRegister implements WrappersProvider {
FunctionValue func = new OverloadFunction(clazz.getName());
for (var overload : clazz.getConstructors()) {
if (overload.getAnnotation(Native.class) == null) continue;
((OverloadFunction)func).add(Overload.fromConstructor(overload));
var nat = overload.getAnnotation(Native.class);
if (nat == null) continue;
((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.raw()));
}
for (var overload : clazz.getMethods()) {
var constr = overload.getAnnotation(NativeConstructor.class);
if (constr == null) continue;
((OverloadFunction)func).add(Overload.fromMethod(overload, constr.raw()));
}
if (((OverloadFunction)func).overloads.size() == 0) {

View File

@ -17,53 +17,63 @@ public class Overload {
public final OverloadRunner runner;
public final boolean variadic;
public final boolean raw;
public final Class<?> thisArg;
public final Class<?>[] params;
public static Overload fromMethod(Method method) {
public static Overload fromMethod(Method method, boolean raw) {
return new Overload(
(ctx, th, args) -> method.invoke(th, args),
method.isVarArgs(),
method.isVarArgs(), raw,
Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(),
method.getParameterTypes()
);
}
public static Overload fromConstructor(Constructor<?> method) {
public static Overload fromConstructor(Constructor<?> method, boolean raw) {
return new Overload(
(ctx, th, args) -> method.newInstance(args),
method.isVarArgs(),
method.isVarArgs(), raw,
Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(),
method.getParameterTypes()
);
}
public static Overload getterFromField(Field field) {
public static Overload getterFromField(Field field, boolean raw) {
return new Overload(
(ctx, th, args) -> field.get(th), false,
(ctx, th, args) -> field.get(th), false, raw,
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
new Class[0]
);
}
public static Overload setterFromField(Field field) {
public static Overload setterFromField(Field field, boolean raw) {
if (Modifier.isFinal(field.getModifiers())) return null;
return new Overload(
(ctx, th, args) -> { field.set(th, args[0]); return null; }, false,
(ctx, th, args) -> { field.set(th, args[0]); return null; }, false, raw,
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
new Class[0]
);
}
public static Overload getter(Class<?> thisArg, OverloadRunner runner) {
public static Overload getter(Class<?> thisArg, OverloadRunner runner, boolean raw) {
return new Overload(
(ctx, th, args) -> runner.run(ctx, th, args), false,
(ctx, th, args) -> runner.run(ctx, th, args), false, raw,
thisArg,
new Class[0]
);
}
public Overload(OverloadRunner runner, boolean variadic, Class<?> thisArg, Class<?> args[]) {
public Overload(OverloadRunner runner, boolean variadic, boolean raw, Class<?> thisArg, Class<?> args[]) {
this.runner = runner;
this.variadic = variadic;
this.raw = raw;
this.thisArg = thisArg;
this.params = args;
if (raw) {
if (!(
thisArg == null && (
args.length == 3 && args[0] == Context.class && args[1] == Object.class && args[2] == Object[].class ||
args.length == 2 && args[0] == Context.class && args[1] == Object[].class
))) throw new IllegalArgumentException("Invalid signature for raw method.");
}
}
}

View File

@ -8,27 +8,40 @@ import java.util.List;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.ConvertException;
import me.topchetoeu.jscript.exceptions.EngineException;
public class OverloadFunction extends FunctionValue {
public final List<Overload> overloads = new ArrayList<>();
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
for (var overload : overloads) {
loop: for (var overload : overloads) {
Object[] newArgs = new Object[overload.params.length];
if (overload.raw) {
newArgs[0] = ctx;
newArgs[1] = thisArg;
newArgs[2] = args;
}
else {
boolean consumesEngine = overload.params.length > 0 && overload.params[0] == Context.class;
int start = consumesEngine ? 1 : 0;
int end = overload.params.length - (overload.variadic ? 1 : 0);
Object[] newArgs = new Object[overload.params.length];
for (var i = start; i < end; i++) {
Object val;
if (i - start >= args.length) val = null;
else val = args[i - start];
try {
newArgs[i] = Values.convert(ctx, val, overload.params[i]);
}
catch (ConvertException e) {
if (overloads.size() > 1) continue loop;
else throw EngineException.ofType(String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target));
}
}
if (overload.variadic) {
var type = overload.params[overload.params.length - 1].getComponentType();
@ -36,13 +49,20 @@ public class OverloadFunction extends FunctionValue {
Object varArg = Array.newInstance(type, n);
for (var i = 0; i < n; i++) {
try {
Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type));
}
catch (ConvertException e) {
if (overloads.size() > 1) continue loop;
else throw EngineException.ofType(String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target));
}
}
newArgs[newArgs.length - 1] = varArg;
}
if (consumesEngine) newArgs[0] = ctx;
}
Object _this = overload.thisArg == null ? null : Values.convert(ctx, thisArg, overload.thisArg);

View File

@ -13,7 +13,7 @@ import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair;
import me.topchetoeu.jscript.compilation.control.*;
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
import me.topchetoeu.jscript.compilation.values.*;
import me.topchetoeu.jscript.engine.FunctionContext;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.CodeFunction;
@ -1875,7 +1875,7 @@ public class Parsing {
return list.toArray(Statement[]::new);
}
public static CodeFunction compile(FunctionContext environment, Statement ...statements) {
public static CodeFunction compile(Environment environment, Statement ...statements) {
var target = environment.global.globalChild();
var subscope = target.child();
var res = new ArrayList<Instruction>();
@ -1905,7 +1905,7 @@ public class Parsing {
return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], res.toArray(Instruction[]::new));
}
public static CodeFunction compile(FunctionContext environment, String filename, String raw) {
public static CodeFunction compile(Environment environment, String filename, String raw) {
try {
return compile(environment, parse(filename, raw));
}

View File

@ -0,0 +1,76 @@
package me.topchetoeu.jscript.polyfills;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.exceptions.EngineException;
public class AsyncFunctionPolyfill extends FunctionValue {
public final FunctionValue factory;
public static class AsyncHelper {
public PromisePolyfill promise = new PromisePolyfill();
public CodeFrame frame;
private boolean awaiting = false;
private void next(Context ctx, Object inducedValue, Object inducedError) throws InterruptedException {
Object res = null;
ctx.message.pushFrame(frame);
awaiting = false;
while (!awaiting) {
try {
res = frame.next(ctx, inducedValue, Runners.NO_RETURN, inducedError);
inducedValue = inducedError = Runners.NO_RETURN;
if (res != Runners.NO_RETURN) {
promise.fulfill(ctx, res);
break;
}
}
catch (EngineException e) {
promise.reject(ctx, e.value);
break;
}
}
ctx.message.popFrame(frame);
if (awaiting) {
PromisePolyfill.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
}
}
public Object fulfill(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN);
return null;
}
public Object reject(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null);
return null;
}
public Object await(Context ctx, Object thisArg, Object[] args) {
this.awaiting = true;
return args.length > 0 ? args[0] : null;
}
}
@Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
var handler = new AsyncHelper();
var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await));
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func);
handler.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN);
return handler.promise;
}
public AsyncFunctionPolyfill(FunctionValue factory) {
super(factory.name, factory.length);
this.factory = factory;
}
}

View File

@ -0,0 +1,133 @@
package me.topchetoeu.jscript.polyfills;
import java.util.Map;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
public class AsyncGeneratorPolyfill extends FunctionValue {
public final FunctionValue factory;
public static class AsyncGenerator {
private int state = 0;
private boolean done = false;
private PromisePolyfill currPromise;
public CodeFrame frame;
private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) throws InterruptedException {
if (done) {
if (inducedError != Runners.NO_RETURN)
throw new EngineException(inducedError);
currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of(
"done", true,
"value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn
)));
return;
}
Object res = null;
ctx.message.pushFrame(frame);
state = 0;
while (state == 0) {
try {
res = frame.next(ctx, inducedValue, inducedReturn, inducedError);
inducedValue = inducedReturn = inducedError = Runners.NO_RETURN;
if (res != Runners.NO_RETURN) {
var obj = new ObjectValue();
obj.defineProperty(ctx, "done", true);
obj.defineProperty(ctx, "value", res);
currPromise.fulfill(ctx, obj);
break;
}
}
catch (EngineException e) {
currPromise.reject(ctx, e.value);
break;
}
}
ctx.message.popFrame(frame);
if (state == 1) {
PromisePolyfill.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
}
else if (state == 2) {
var obj = new ObjectValue();
obj.defineProperty(ctx, "done", false);
obj.defineProperty(ctx, "value", frame.pop());
currPromise.fulfill(ctx, obj);
}
}
@Override
public String toString() {
if (done) return "Generator [closed]";
if (state != 0) return "Generator [suspended]";
return "Generator [running]";
}
public Object fulfill(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN, Runners.NO_RETURN);
return null;
}
public Object reject(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null, Runners.NO_RETURN);
return null;
}
@Native
public PromisePolyfill next(Context ctx, Object ...args) throws InterruptedException {
this.currPromise = new PromisePolyfill();
if (args.length == 0) next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
else next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN);
return this.currPromise;
}
@Native("throw")
public PromisePolyfill _throw(Context ctx, Object error) throws InterruptedException {
this.currPromise = new PromisePolyfill();
next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
return this.currPromise;
}
@Native("return")
public PromisePolyfill _return(Context ctx, Object value) throws InterruptedException {
this.currPromise = new PromisePolyfill();
next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
return this.currPromise;
}
public Object await(Context ctx, Object thisArg, Object[] args) {
this.state = 1;
return args.length > 0 ? args[0] : null;
}
public Object yield(Context ctx, Object thisArg, Object[] args) {
this.state = 2;
return args.length > 0 ? args[0] : null;
}
}
@Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
var handler = new AsyncGenerator();
var func = factory.call(ctx, thisArg,
new NativeFunction("await", handler::await),
new NativeFunction("yield", handler::yield)
);
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func);
return handler;
}
public AsyncGeneratorPolyfill(FunctionValue factory) {
super(factory.name, factory.length);
this.factory = factory;
}
}

View File

@ -0,0 +1,60 @@
package me.topchetoeu.jscript.polyfills;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
public class FunctionPolyfill {
@Native(raw=true) public static Object apply(Context ctx, Object func, Object[] args) throws InterruptedException {
var thisArg = args.length > 0 ? args[0] : null;
var _args = args.length > 1 ? args[1] : null;
if (!(_args instanceof ArrayValue)) throw EngineException.ofError("Expected arguments to be an array.");
if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function.");
return ((FunctionValue)func).call(ctx, thisArg, ((ArrayValue)_args).toArray());
}
@Native(raw=true) public static Object call(Context ctx, Object func, Object[] args) throws InterruptedException {
var thisArg = args.length > 0 ? args[0] : null;
var _args = new Object[args.length > 1 ? args.length - 1 : 0];
if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function.");
if (_args.length != 0) System.arraycopy(args, 1, _args, 0, _args.length);
return ((FunctionValue)func).call(ctx, thisArg, _args);
}
@Native(raw=true) public static Object bind(Context ctx, Object func, Object[] args) {
var thisArg = args.length > 0 ? args[0] : null;
var _args = new Object[args.length > 1 ? args.length - 1 : 0];
FunctionValue _func = (FunctionValue)func;
if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function.");
if (_args.length != 0) System.arraycopy(args, 1, _args, 0, _args.length);
return new NativeFunction(_func.name + " (bound)", (callCtx, _0, callArgs) -> {
var resArgs = new Object[_args.length + callArgs.length];
System.arraycopy(_args, 0, resArgs, 0, _args.length);
System.arraycopy(callArgs, 0, resArgs, _args.length, resArgs.length - _args.length);
return _func.call(ctx, thisArg, resArgs);
});
}
@Native(raw=true) public static String toString(Context ctx, Object func, Object[] args) {
return "function (...) { ... }";
}
@Native public static FunctionValue async(FunctionValue func) {
return new AsyncFunctionPolyfill(func);
}
@Native public static FunctionValue asyncGenerator(FunctionValue func) {
return new AsyncGeneratorPolyfill(func);
}
@Native public static FunctionValue generator(FunctionValue func) {
return new GeneratorPolyfill(func);
}
}

View File

@ -10,7 +10,7 @@ import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
public class GeneratorFunction extends FunctionValue {
public class GeneratorPolyfill extends FunctionValue {
public final FunctionValue factory;
public static class Generator {
@ -28,12 +28,12 @@ public class GeneratorFunction extends FunctionValue {
}
Object res = null;
if (inducedValue != Runners.NO_RETURN) frame.push(ctx, inducedValue);
ctx.message.pushFrame(frame);
yielding = false;
while (!yielding) {
try {
res = frame.next(ctx, inducedReturn, inducedError);
res = frame.next(ctx, inducedValue, inducedReturn, inducedError);
inducedReturn = inducedError = Runners.NO_RETURN;
if (res != Runners.NO_RETURN) {
done = true;
@ -74,7 +74,7 @@ public class GeneratorFunction extends FunctionValue {
public String toString() {
if (done) return "Generator [closed]";
if (yielding) return "Generator [suspended]";
return "Generator " + (done ? "[closed]" : "[suspended]");
return "Generator [running]";
}
public Object yield(Context ctx, Object thisArg, Object[] args) {
@ -92,7 +92,7 @@ public class GeneratorFunction extends FunctionValue {
return handler;
}
public GeneratorFunction(FunctionValue factory) {
public GeneratorPolyfill(FunctionValue factory) {
super(factory.name, factory.length);
this.factory = factory;
}

View File

@ -1,7 +1,7 @@
package me.topchetoeu.jscript.polyfills;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.FunctionContext;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
@ -12,20 +12,25 @@ import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.interop.Native;
public class Internals {
@Native public final Class<ObjectPolyfill> object = ObjectPolyfill.class;
@Native public final Class<FunctionPolyfill> function = FunctionPolyfill.class;
@Native public final Class<PromisePolyfill> promise = PromisePolyfill.class;
@Native public void markSpecial(FunctionValue ...funcs) {
for (var func : funcs) {
func.special = true;
}
}
@Native public FunctionContext getEnv(Object func) {
@Native public Environment getEnv(Object func) {
if (func instanceof CodeFunction) return ((CodeFunction)func).environment;
else return null;
}
@Native public Object setEnv(Object func, FunctionContext env) {
@Native public Object setEnv(Object func, Environment env) {
if (func instanceof CodeFunction) ((CodeFunction)func).environment = env;
return func;
}
@Native public Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException {
@Native public Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args, Environment env) throws InterruptedException {
if (env != null) ctx = new Context(env, ctx.message);
return func.call(ctx, thisArg, args.toArray());
}
@Native public FunctionValue delay(Context ctx, double delay, FunctionValue callback) throws InterruptedException {
@ -84,8 +89,8 @@ public class Internals {
@Native public boolean isArray(Object obj) {
return obj instanceof ArrayValue;
}
@Native public GeneratorFunction generator(FunctionValue obj) {
return new GeneratorFunction(obj);
@Native public GeneratorPolyfill generator(FunctionValue obj) {
return new GeneratorPolyfill(obj);
}
@Native public boolean defineField(Context ctx, ObjectValue obj, Object key, Object val, boolean writable, boolean enumerable, boolean configurable) {

View File

@ -0,0 +1,212 @@
package me.topchetoeu.jscript.polyfills;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor;
public class ObjectPolyfill {
@Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) throws InterruptedException {
for (var obj : src) {
for (var key : Values.getMembers(ctx, obj, true, true)) {
Values.setMember(ctx, dst, key, Values.getMember(ctx, obj, key));
}
}
return dst;
}
@Native public static ObjectValue create(Context ctx, ObjectValue proto, ObjectValue props) throws InterruptedException {
var obj = new ObjectValue();
obj.setPrototype(ctx, proto);
return defineProperties(ctx, obj, props);
}
@Native public static ObjectValue defineProperty(Context ctx, ObjectValue obj, Object key, ObjectValue attrib) throws InterruptedException {
var hasVal = attrib.hasMember(ctx, "value", false);
var hasGet = attrib.hasMember(ctx, "get", false);
var hasSet = attrib.hasMember(ctx, "set", false);
if (hasVal) {
if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property.");
if (!obj.defineProperty(
ctx, key,
attrib.getMember(ctx, "value"),
Values.toBoolean(attrib.getMember(ctx, "writable")),
Values.toBoolean(attrib.getMember(ctx, "configurable")),
Values.toBoolean(attrib.getMember(ctx, "enumerable"))
)) throw EngineException.ofType("Can't define property '" + key + "'.");
}
else {
var get = attrib.getMember(ctx, "get");
var set = attrib.getMember(ctx, "set");
if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType("Get accessor must be a function.");
if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType("Set accessor must be a function.");
if (!obj.defineProperty(
ctx, key,
(FunctionValue)get, (FunctionValue)set,
Values.toBoolean(attrib.getMember(ctx, "configurable")),
Values.toBoolean(attrib.getMember(ctx, "enumerable"))
)) throw EngineException.ofType("Can't define property '" + key + "'.");
}
return obj;
}
@Native public static ObjectValue defineProperties(Context ctx, ObjectValue obj, ObjectValue attrib) throws InterruptedException {
for (var key : Values.getMembers(null, obj, false, false)) {
obj.defineProperty(ctx, key, attrib.getMember(ctx, key));
}
return obj;
}
@Native public static ArrayValue keys(Context ctx, Object obj, Object all) throws InterruptedException {
var res = new ArrayValue();
var _all = Values.toBoolean(all);
for (var key : Values.getMembers(ctx, obj, true, false)) {
if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), key);
}
return res;
}
@Native public static ArrayValue entries(Context ctx, Object obj, Object all) throws InterruptedException {
var res = new ArrayValue();
var _all = Values.toBoolean(all);
for (var key : Values.getMembers(ctx, obj, true, false)) {
if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), new ArrayValue(ctx, key, Values.getMember(ctx, obj, key)));
}
return res;
}
@Native public static ArrayValue values(Context ctx, Object obj, Object all) throws InterruptedException {
var res = new ArrayValue();
var _all = Values.toBoolean(all);
for (var key : Values.getMembers(ctx, obj, true, false)) {
if (_all || key instanceof String) res.set(ctx, res.size(), Values.getMember(ctx, obj, key));
}
return res;
}
@Native public static ObjectValue getOwnPropertyDescriptor(Context ctx, Object obj, Object key) throws InterruptedException {
return Values.getMemberDescriptor(ctx, obj, key);
}
@Native public static ObjectValue getOwnPropertyDescriptors(Context ctx, Object obj) throws InterruptedException {
var res = new ObjectValue();
for (var key : Values.getMembers(ctx, obj, true, true)) {
res.defineProperty(ctx, key, getOwnPropertyDescriptor(ctx, obj, key));
}
return res;
}
@Native public static ArrayValue getOwnPropertyNames(Context ctx, Object obj, Object all) throws InterruptedException {
var res = new ArrayValue();
var _all = Values.toBoolean(all);
for (var key : Values.getMembers(ctx, obj, true, true)) {
if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), key);
}
return res;
}
@Native public static ArrayValue getOwnPropertySymbols(Context ctx, Object obj) throws InterruptedException {
var res = new ArrayValue();
for (var key : Values.getMembers(ctx, obj, true, true)) {
if (key instanceof Symbol) res.set(ctx, res.size(), key);
}
return res;
}
@Native public static boolean hasOwn(Context ctx, Object obj, Object key) throws InterruptedException {
return Values.hasMember(ctx, obj, key, true);
}
@Native public static ObjectValue getPrototypeOf(Context ctx, Object obj) throws InterruptedException {
return Values.getPrototype(ctx, obj);
}
@Native public static Object setPrototypeOf(Context ctx, Object obj, Object proto) throws InterruptedException {
Values.setPrototype(ctx, obj, proto);
return obj;
}
@Native public static ObjectValue fromEntries(Context ctx, Object iterable) throws InterruptedException {
var res = new ObjectValue();
for (var el : Values.toJavaIterable(ctx, iterable)) {
if (el instanceof ArrayValue) {
res.defineProperty(ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1));
}
}
return res;
}
@Native public static Object preventExtensions(Context ctx, Object obj) throws InterruptedException {
if (obj instanceof ObjectValue) ((ObjectValue)obj).preventExtensions();
return obj;
}
@Native public static Object seal(Context ctx, Object obj) throws InterruptedException {
if (obj instanceof ObjectValue) ((ObjectValue)obj).seal();
return obj;
}
@Native public static Object freeze(Context ctx, Object obj) throws InterruptedException {
if (obj instanceof ObjectValue) ((ObjectValue)obj).freeze();
return obj;
}
@Native public static boolean isExtensible(Context ctx, Object obj) throws InterruptedException {
return obj instanceof ObjectValue && ((ObjectValue)obj).extensible();
}
@Native public static boolean isSealed(Context ctx, Object obj) throws InterruptedException {
if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) {
var _obj = (ObjectValue)obj;
for (var key : _obj.keys(true)) {
if (_obj.memberConfigurable(key)) return false;
}
}
return true;
}
@Native public static boolean isFrozen(Context ctx, Object obj) throws InterruptedException {
if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) {
var _obj = (ObjectValue)obj;
for (var key : _obj.keys(true)) {
if (_obj.memberConfigurable(key)) return false;
if (_obj.memberWritable(key)) return false;
}
}
return true;
}
@Native(raw = true) public static Object valueOf(Context ctx, Object thisArg, Object[] args) {
return thisArg;
}
@Native(raw = true) public static String toString(Context ctx, Object thisArg, Object[] args) throws InterruptedException {
var name = Values.getMember(ctx, thisArg, ctx.env.symbol("Symbol.typeName"));
if (name == null) name = "Unknown";
else name = Values.toString(ctx, name);
return "[object " + name + "]";
}
@Native(raw = true) public static boolean hasOwnProperty(Context ctx, Object thisArg, Object[] args) throws InterruptedException {
return ObjectPolyfill.hasOwn(ctx, thisArg, Values.convert(ctx, args.length == 0 ? null : args[0], String.class));
}
@NativeConstructor public static Object constructor(Context ctx, Object arg) throws InterruptedException {
if (arg == null || arg == Values.NULL) return new ObjectValue();
else if (arg instanceof Boolean) return Values.callNew(ctx, ctx.env.global.get(ctx, "Boolean"), arg);
else if (arg instanceof Number) return Values.callNew(ctx, ctx.env.global.get(ctx, "Number"), arg);
else if (arg instanceof String) return Values.callNew(ctx, ctx.env.global.get(ctx, "String"), arg);
else if (arg instanceof Symbol) return Values.callNew(ctx, ctx.env.global.get(ctx, "Symbol"), arg);
else return arg;
}
}

View File

@ -0,0 +1,346 @@
package me.topchetoeu.jscript.polyfills;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.MessageContext;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.NativeWrapper;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
public class PromisePolyfill {
private static class Handle {
public final Context ctx;
public final FunctionValue fulfilled;
public final FunctionValue rejected;
public Handle(Context ctx, FunctionValue fulfilled, FunctionValue rejected) {
this.ctx = ctx;
this.fulfilled = fulfilled;
this.rejected = rejected;
}
}
@Native("resolve")
public static PromisePolyfill ofResolved(Context ctx, Object val) throws InterruptedException {
var res = new PromisePolyfill();
res.fulfill(ctx, val);
return res;
}
@Native("reject")
public static PromisePolyfill ofRejected(Context ctx, Object val) throws InterruptedException {
var res = new PromisePolyfill();
res.reject(ctx, val);
return res;
}
@Native public static PromisePolyfill any(Context ctx, Object _promises) throws InterruptedException {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
var n = new int[] { promises.size() };
var res = new PromisePolyfill();
var errors = new ArrayValue();
for (var i = 0; i < promises.size(); i++) {
var index = i;
var val = promises.get(i);
then(ctx, val,
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }),
new NativeFunction(null, (e, th, args) -> {
errors.set(ctx, index, args[0]);
n[0]--;
if (n[0] <= 0) res.reject(e, errors);
return null;
})
);
}
return res;
}
@Native public static PromisePolyfill race(Context ctx, Object _promises) throws InterruptedException {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
var res = new PromisePolyfill();
for (var i = 0; i < promises.size(); i++) {
var val = promises.get(i);
then(ctx, val,
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }),
new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
);
}
return res;
}
@Native public static PromisePolyfill all(Context ctx, Object _promises) throws InterruptedException {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
var n = new int[] { promises.size() };
var res = new PromisePolyfill();
var result = new ArrayValue();
for (var i = 0; i < promises.size(); i++) {
var index = i;
var val = promises.get(i);
then(ctx, val,
new NativeFunction(null, (e, th, args) -> {
result.set(ctx, index, args[0]);
n[0]--;
if (n[0] <= 0) res.fulfill(e, result);
return null;
}),
new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
);
}
if (n[0] <= 0) res.fulfill(ctx, result);
return res;
}
@Native public static PromisePolyfill allSettled(Context ctx, Object _promises) throws InterruptedException {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
var n = new int[] { promises.size() };
var res = new PromisePolyfill();
var result = new ArrayValue();
for (var i = 0; i < promises.size(); i++) {
var index = i;
var val = promises.get(i);
then(ctx, val,
new NativeFunction(null, (e, th, args) -> {
result.set(ctx, index, new ObjectValue(ctx, Map.of(
"status", "fulfilled",
"value", args[0]
)));
n[0]--;
if (n[0] <= 0) res.fulfill(e, result);
return null;
}),
new NativeFunction(null, (e, th, args) -> {
result.set(ctx, index, new ObjectValue(ctx, Map.of(
"status", "rejected",
"reason", args[0]
)));
n[0]--;
if (n[0] <= 0) res.fulfill(e, result);
return null;
})
);
}
if (n[0] <= 0) res.fulfill(ctx, result);
return res;
}
/**
* Thread safe - you can call this from anywhere
* HOWEVER, it's strongly recommended to use this only in javascript
*/
@Native(raw = true) public static Object then(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
var onFulfill = args.length > 0 ? args[0] : null;
var onReject = args.length > 1 ? args[1]: null;
if (!(onFulfill instanceof FunctionValue)) onFulfill = null;
if (!(onReject instanceof FunctionValue)) onReject = null;
var res = new PromisePolyfill();
var fulfill = onFulfill == null ? new NativeFunction((_ctx, _thisArg, _args) -> _args.length > 0 ? _args[0] : null) : (FunctionValue)onFulfill;
var reject = onReject == null ? new NativeFunction((_ctx, _thisArg, _args) -> {
throw new EngineException(_args.length > 0 ? _args[0] : null);
}) : (FunctionValue)onReject;
if (thisArg instanceof NativeWrapper && ((NativeWrapper)thisArg).wrapped instanceof PromisePolyfill) {
thisArg = ((NativeWrapper)thisArg).wrapped;
}
var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> {
try {
res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class));
}
catch (EngineException err) { res.reject(ctx, err.value); }
return null;
});
var rejectHandle = new NativeFunction(null, (_ctx, th, a) -> {
try { res.fulfill(ctx, reject.call(ctx, null, a[0])); }
catch (EngineException err) { res.reject(ctx, err.value); }
return null;
});
if (thisArg instanceof PromisePolyfill) ((PromisePolyfill)thisArg).handle(ctx, fulfillHandle, rejectHandle);
else {
Object next;
try {
next = Values.getMember(ctx, thisArg, "then");
}
catch (IllegalArgumentException e) { next = null; }
try {
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, thisArg, fulfillHandle, rejectHandle);
else res.fulfill(ctx, fulfill.call(ctx, null, thisArg));
}
catch (EngineException err) {
res.reject(ctx, fulfill.call(ctx, null, err.value));
}
}
return res;
}
/**
* Thread safe - you can call this from anywhere
* HOWEVER, it's strongly recommended to use this only in javascript
*/
@Native(value = "catch", raw = true) public static Object _catch(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
return then(ctx, thisArg, null, args.length > 0 ? args[0] : null);
}
/**
* Thread safe - you can call this from anywhere
* HOWEVER, it's strongly recommended to use this only in javascript
*/
@Native(value = "finally", raw = true) public static Object _finally(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
var handleFunc = args.length > 0 ? args[0] : null;
return then(ctx, thisArg,
new NativeFunction(null, (e, th, _args) -> {
if (handleFunc instanceof FunctionValue) ((FunctionValue)handleFunc).call(ctx);
return args[0];
}),
new NativeFunction(null, (e, th, _args) -> {
if (handleFunc instanceof FunctionValue) ((FunctionValue)handleFunc).call(ctx);
throw new EngineException(_args[0]);
})
);
}
private List<Handle> handles = new ArrayList<>();
private static final int STATE_PENDING = 0;
private static final int STATE_FULFILLED = 1;
private static final int STATE_REJECTED = 2;
private int state = STATE_PENDING;
private boolean handled = false;
private Object val;
private void resolve(Context ctx, Object val, int state) throws InterruptedException {
if (this.state != STATE_PENDING) return;
if (val instanceof PromisePolyfill) ((PromisePolyfill)val).handle(ctx,
new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], state); return null; }),
new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], STATE_REJECTED); return null; })
);
else {
Object next;
try { next = Values.getMember(ctx, val, "next"); }
catch (IllegalArgumentException e) { next = null; }
try {
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val,
new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, state); return null; }),
new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, STATE_REJECTED); return null; })
);
else {
this.val = val;
this.state = state;
if (state == STATE_FULFILLED) {
for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val);
}
else if (state == STATE_REJECTED) {
for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
if (handles.size() == 0) {
ctx.message.engine.pushMsg(true, ctx.message, new NativeFunction((_ctx, _thisArg, _args) -> {
if (!handled) {
try { Values.printError(new EngineException(val), "(in promise)"); }
catch (InterruptedException ex) { }
}
return null;
}), null);
}
}
handles = null;
}
}
catch (EngineException err) {
this.reject(ctx, err.value);
}
}
}
/**
* Thread safe - call from any thread
*/
public void fulfill(Context ctx, Object val) throws InterruptedException {
resolve(ctx, val, STATE_FULFILLED);
}
/**
* Thread safe - call from any thread
*/
public void reject(Context ctx, Object val) throws InterruptedException {
resolve(ctx, val, STATE_REJECTED);
}
private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) {
if (state == STATE_FULFILLED) ctx.message.engine.pushMsg(true, new MessageContext(ctx.message.engine), fulfill, null, val);
else if (state == STATE_REJECTED) {
ctx.message.engine.pushMsg(true, new MessageContext(ctx.message.engine), reject, null, val);
handled = true;
}
else handles.add(new Handle(ctx, fulfill, reject));
}
@Override @Native public String toString() {
if (state == STATE_PENDING) return "Promise (pending)";
else if (state == STATE_FULFILLED) return "Promise (fulfilled)";
else return "Promise (rejected)";
}
/**
* NOT THREAD SAFE - must be called from the engine executor thread
*/
@Native public PromisePolyfill(Context ctx, FunctionValue func) throws InterruptedException {
if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor.");
try {
func.call(
ctx, null,
new NativeFunction(null, (e, th, args) -> {
fulfill(e, args.length > 0 ? args[0] : null);
return null;
}),
new NativeFunction(null, (e, th, args) -> {
reject(e, args.length > 0 ? args[0] : null);
return null;
})
);
}
catch (EngineException e) {
reject(ctx, e.value);
}
}
private PromisePolyfill(int state, Object val) {
this.state = state;
this.val = val;
}
public PromisePolyfill() {
this(STATE_PENDING, null);
}
}