Core library reprogramming #5
@ -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!');
|
||||
}
|
||||
|
371
lib/promise.ts
371
lib/promise.ts
@ -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,
|
||||
}
|
||||
var Promise = env.global.Promise = internals.promise
|
||||
return;
|
||||
|
||||
type Callback<T> = [ PromiseFulfillFunc<T>, PromiseRejectFunc ];
|
||||
enum State {
|
||||
Pending,
|
||||
Fulfilled,
|
||||
Rejected,
|
||||
}
|
||||
// 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,
|
||||
// }
|
||||
|
||||
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;
|
||||
// type Callback<T> = [ PromiseFulfillFunc<T>, PromiseRejectFunc ];
|
||||
// enum State {
|
||||
// Pending,
|
||||
// Fulfilled,
|
||||
// Rejected,
|
||||
// }
|
||||
|
||||
for (let i = 0; i < promise[syms.callbacks]!.length; i++) {
|
||||
promise[syms.handled] = true;
|
||||
promise[syms.callbacks]![i][state - 1](v);
|
||||
}
|
||||
// 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;
|
||||
|
||||
promise[syms.callbacks] = undefined;
|
||||
// for (let i = 0; i < promise[syms.callbacks]!.length; i++) {
|
||||
// promise[syms.handled] = true;
|
||||
// promise[syms.callbacks]![i][state - 1](v);
|
||||
// }
|
||||
|
||||
internals.pushMessage(true, internals.setEnv(() => {
|
||||
if (!promise[syms.handled] && state === State.Rejected) {
|
||||
log('Uncaught (in promise) ' + promise[syms.value]);
|
||||
}
|
||||
}, env), undefined, []);
|
||||
}
|
||||
}
|
||||
// promise[syms.callbacks] = undefined;
|
||||
|
||||
class Promise<T> {
|
||||
public static isAwaitable(val: unknown): val is Thenable<any> {
|
||||
return isAwaitable(val);
|
||||
}
|
||||
// internals.pushMessage(true, internals.setEnv(() => {
|
||||
// if (!promise[syms.handled] && state === State.Rejected) {
|
||||
// log('Uncaught (in promise) ' + promise[syms.value]);
|
||||
// }
|
||||
// }, env), undefined, []);
|
||||
// }
|
||||
// }
|
||||
|
||||
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));
|
||||
}
|
||||
// class _Promise<T> {
|
||||
// public static isAwaitable(val: unknown): val is Thenable<any> {
|
||||
// return isAwaitable(val);
|
||||
// }
|
||||
|
||||
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;
|
||||
// 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));
|
||||
// }
|
||||
|
||||
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);
|
||||
}
|
||||
// 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;
|
||||
|
||||
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(res, (err) => {
|
||||
// n++;
|
||||
// if (n === vals.length) throw Error('No promise resolved.');
|
||||
// });
|
||||
// else res(val as any);
|
||||
// }
|
||||
|
||||
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 === 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;
|
||||
|
||||
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 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;
|
||||
// }
|
||||
// }
|
||||
|
||||
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);
|
||||
// });
|
||||
// }
|
||||
// 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;
|
||||
|
||||
if (vals.length === n) res(result);
|
||||
});
|
||||
}
|
||||
// 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 };
|
||||
// }
|
||||
// }
|
||||
|
||||
[syms.callbacks]?: Callback<T>[] = [];
|
||||
[syms.handled] = false;
|
||||
[syms.state] = State.Pending;
|
||||
[syms.value]?: T | unknown;
|
||||
// if (vals.length === n) res(result);
|
||||
// });
|
||||
// }
|
||||
|
||||
public then(onFulfil?: PromiseFulfillFunc<T>, onReject?: PromiseRejectFunc) {
|
||||
return new Promise((resolve, reject) => {
|
||||
onFulfil ??= v => v;
|
||||
onReject ??= v => v;
|
||||
// [syms.callbacks]?: Callback<T>[] = [];
|
||||
// [syms.handled] = false;
|
||||
// [syms.state] = State.Pending;
|
||||
// [syms.value]?: T | unknown;
|
||||
|
||||
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 then(onFulfil?: PromiseFulfillFunc<T>, onReject?: PromiseRejectFunc) {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// onFulfil ??= v => v;
|
||||
// onReject ??= v => 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;
|
||||
// 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;
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
});
|
@ -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);
|
||||
});
|
@ -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);
|
||||
|
@ -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 -> {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 (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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
src/me/topchetoeu/jscript/exceptions/ConvertException.java
Normal file
11
src/me/topchetoeu/jscript/exceptions/ConvertException.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -9,4 +9,5 @@ import java.lang.annotation.Target;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Native {
|
||||
public String value() default "";
|
||||
public boolean raw() default false;
|
||||
}
|
||||
|
13
src/me/topchetoeu/jscript/interop/NativeConstructor.java
Normal file
13
src/me/topchetoeu/jscript/interop/NativeConstructor.java
Normal 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;
|
||||
}
|
||||
|
@ -9,4 +9,5 @@ import java.lang.annotation.Target;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NativeGetter {
|
||||
public String value();
|
||||
public boolean raw() default false;
|
||||
}
|
||||
|
@ -9,4 +9,5 @@ import java.lang.annotation.Target;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NativeSetter {
|
||||
public String value();
|
||||
public boolean raw() default false;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
@ -8,41 +8,61 @@ 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) {
|
||||
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);
|
||||
|
||||
loop: for (var overload : overloads) {
|
||||
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];
|
||||
|
||||
newArgs[i] = Values.convert(ctx, val, overload.params[i]);
|
||||
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);
|
||||
|
||||
if (overload.variadic) {
|
||||
var type = overload.params[overload.params.length - 1].getComponentType();
|
||||
var n = Math.max(args.length - end + start, 0);
|
||||
Object varArg = Array.newInstance(type, n);
|
||||
for (var i = start; i < end; i++) {
|
||||
Object val;
|
||||
|
||||
for (var i = 0; i < n; i++) {
|
||||
Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
newArgs[newArgs.length - 1] = varArg;
|
||||
}
|
||||
if (overload.variadic) {
|
||||
var type = overload.params[overload.params.length - 1].getComponentType();
|
||||
var n = Math.max(args.length - end + start, 0);
|
||||
Object varArg = Array.newInstance(type, n);
|
||||
|
||||
if (consumesEngine) newArgs[0] = ctx;
|
||||
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);
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
133
src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java
Normal file
133
src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java
Normal 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;
|
||||
}
|
||||
}
|
60
src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java
Normal file
60
src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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) {
|
||||
|
212
src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java
Normal file
212
src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java
Normal 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;
|
||||
}
|
||||
}
|
346
src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java
Normal file
346
src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user