Core library reprogramming #5
@ -2,12 +2,17 @@ interface Environment {
|
|||||||
global: typeof globalThis & Record<string, any>;
|
global: typeof globalThis & Record<string, any>;
|
||||||
proto(name: string): object;
|
proto(name: string): object;
|
||||||
setProto(name: string, val: object): void;
|
setProto(name: string, val: object): void;
|
||||||
|
symbol(name: string): symbol;
|
||||||
}
|
}
|
||||||
interface Internals {
|
interface Internals {
|
||||||
|
object: ObjectConstructor;
|
||||||
|
function: FunctionConstructor;
|
||||||
|
promise: typeof Promise;
|
||||||
|
|
||||||
markSpecial(...funcs: Function[]): void;
|
markSpecial(...funcs: Function[]): void;
|
||||||
getEnv(func: Function): Environment | undefined;
|
getEnv(func: Function): Environment | undefined;
|
||||||
setEnv<T>(func: T, env: Environment): T;
|
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;
|
delay(timeout: number, callback: Function): () => void;
|
||||||
pushMessage(micro: boolean, func: Function, thisArg: any, args: any[]): void;
|
pushMessage(micro: boolean, func: Function, thisArg: any, args: any[]): void;
|
||||||
|
|
||||||
@ -55,6 +60,7 @@ try {
|
|||||||
run('timeout');
|
run('timeout');
|
||||||
|
|
||||||
env.global.log = log;
|
env.global.log = log;
|
||||||
|
env.global.NewObject = internals.object;
|
||||||
|
|
||||||
log('Loaded polyfills!');
|
log('Loaded polyfills!');
|
||||||
}
|
}
|
||||||
|
371
lib/promise.ts
371
lib/promise.ts
@ -1,203 +1,208 @@
|
|||||||
define("promise", () => {
|
define("promise", () => {
|
||||||
const syms = {
|
var Promise = env.global.Promise = internals.promise
|
||||||
callbacks: internals.symbol('Promise.callbacks'),
|
return;
|
||||||
state: internals.symbol('Promise.state'),
|
|
||||||
value: internals.symbol('Promise.value'),
|
|
||||||
handled: internals.symbol('Promise.handled'),
|
|
||||||
} as {
|
|
||||||
readonly callbacks: unique symbol,
|
|
||||||
readonly state: unique symbol,
|
|
||||||
readonly value: unique symbol,
|
|
||||||
readonly handled: unique symbol,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Callback<T> = [ PromiseFulfillFunc<T>, PromiseRejectFunc ];
|
// const syms = {
|
||||||
enum State {
|
// callbacks: internals.symbol('Promise.callbacks'),
|
||||||
Pending,
|
// state: internals.symbol('Promise.state'),
|
||||||
Fulfilled,
|
// value: internals.symbol('Promise.value'),
|
||||||
Rejected,
|
// 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> {
|
// type Callback<T> = [ PromiseFulfillFunc<T>, PromiseRejectFunc ];
|
||||||
return (
|
// enum State {
|
||||||
typeof val === 'object' &&
|
// Pending,
|
||||||
val !== null &&
|
// Fulfilled,
|
||||||
'then' in val &&
|
// Rejected,
|
||||||
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;
|
|
||||||
|
|
||||||
for (let i = 0; i < promise[syms.callbacks]!.length; i++) {
|
// function isAwaitable(val: unknown): val is Thenable<any> {
|
||||||
promise[syms.handled] = true;
|
// return (
|
||||||
promise[syms.callbacks]![i][state - 1](v);
|
// 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(() => {
|
// promise[syms.callbacks] = undefined;
|
||||||
if (!promise[syms.handled] && state === State.Rejected) {
|
|
||||||
log('Uncaught (in promise) ' + promise[syms.value]);
|
|
||||||
}
|
|
||||||
}, env), undefined, []);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Promise<T> {
|
// internals.pushMessage(true, internals.setEnv(() => {
|
||||||
public static isAwaitable(val: unknown): val is Thenable<any> {
|
// if (!promise[syms.handled] && state === State.Rejected) {
|
||||||
return isAwaitable(val);
|
// log('Uncaught (in promise) ' + promise[syms.value]);
|
||||||
}
|
// }
|
||||||
|
// }, env), undefined, []);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
public static resolve<T>(val: T): Promise<Awaited<T>> {
|
// class _Promise<T> {
|
||||||
return new Promise(res => res(val as any));
|
// public static isAwaitable(val: unknown): val is Thenable<any> {
|
||||||
}
|
// return isAwaitable(val);
|
||||||
public static reject<T>(val: T): Promise<Awaited<T>> {
|
// }
|
||||||
return new Promise((_, rej) => rej(val as any));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static race<T>(vals: T[]): Promise<Awaited<T>> {
|
// public static resolve<T>(val: 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 => res(val as any));
|
||||||
return new Promise((res, rej) => {
|
// }
|
||||||
for (let i = 0; i < vals.length; i++) {
|
// public static reject<T>(val: T): Promise<Awaited<T>> {
|
||||||
const val = vals[i];
|
// return new Promise((_, rej) => rej(val as any));
|
||||||
if (this.isAwaitable(val)) val.then(res, rej);
|
// }
|
||||||
else res(val as any);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public static any<T>(vals: T[]): Promise<Awaited<T>> {
|
|
||||||
if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.any is not variadic.');
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
let n = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < vals.length; i++) {
|
// public static race<T>(vals: T[]): Promise<Awaited<T>> {
|
||||||
const val = vals[i];
|
// if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.race is not variadic.');
|
||||||
if (this.isAwaitable(val)) val.then(res, (err) => {
|
// return new Promise((res, rej) => {
|
||||||
n++;
|
// for (let i = 0; i < vals.length; i++) {
|
||||||
if (n === vals.length) throw Error('No promise resolved.');
|
// const val = vals[i];
|
||||||
});
|
// if (this.isAwaitable(val)) val.then(res, rej);
|
||||||
else res(val as any);
|
// 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.');
|
// for (let i = 0; i < vals.length; i++) {
|
||||||
});
|
// const val = vals[i];
|
||||||
}
|
// if (this.isAwaitable(val)) val.then(res, (err) => {
|
||||||
public static all(vals: any[]): Promise<any[]> {
|
// n++;
|
||||||
if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.all is not variadic.');
|
// if (n === vals.length) throw Error('No promise resolved.');
|
||||||
return new Promise((res, rej) => {
|
// });
|
||||||
const result: any[] = [];
|
// else res(val as any);
|
||||||
let n = 0;
|
// }
|
||||||
|
|
||||||
for (let i = 0; i < vals.length; i++) {
|
// if (vals.length === 0) throw Error('No promise resolved.');
|
||||||
const val = vals[i];
|
// });
|
||||||
if (this.isAwaitable(val)) val.then(
|
// }
|
||||||
val => {
|
// public static all(vals: any[]): Promise<any[]> {
|
||||||
n++;
|
// if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.all is not variadic.');
|
||||||
result[i] = val;
|
// return new Promise((res, rej) => {
|
||||||
if (n === vals.length) res(result);
|
// const result: any[] = [];
|
||||||
},
|
// let n = 0;
|
||||||
rej
|
|
||||||
);
|
|
||||||
else {
|
|
||||||
n++;
|
|
||||||
result[i] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vals.length === n) res(result);
|
// for (let i = 0; i < vals.length; i++) {
|
||||||
});
|
// const val = vals[i];
|
||||||
}
|
// if (this.isAwaitable(val)) val.then(
|
||||||
public static allSettled(vals: any[]): Promise<any[]> {
|
// val => {
|
||||||
if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.allSettled is not variadic.');
|
// n++;
|
||||||
return new Promise((res, rej) => {
|
// result[i] = val;
|
||||||
const result: any[] = [];
|
// if (n === vals.length) res(result);
|
||||||
let n = 0;
|
// },
|
||||||
|
// rej
|
||||||
|
// );
|
||||||
|
// else {
|
||||||
|
// n++;
|
||||||
|
// result[i] = val;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
for (let i = 0; i < vals.length; i++) {
|
// if (vals.length === n) res(result);
|
||||||
const value = vals[i];
|
// });
|
||||||
if (this.isAwaitable(value)) value.then(
|
// }
|
||||||
value => {
|
// public static allSettled(vals: any[]): Promise<any[]> {
|
||||||
n++;
|
// if (typeof vals.length !== 'number') throw new env.global.TypeError('vals must be an array. Note that Promise.allSettled is not variadic.');
|
||||||
result[i] = { status: 'fulfilled', value };
|
// return new Promise((res, rej) => {
|
||||||
if (n === vals.length) res(result);
|
// const result: any[] = [];
|
||||||
},
|
// let n = 0;
|
||||||
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);
|
// 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>[] = [];
|
// if (vals.length === n) res(result);
|
||||||
[syms.handled] = false;
|
// });
|
||||||
[syms.state] = State.Pending;
|
// }
|
||||||
[syms.value]?: T | unknown;
|
|
||||||
|
|
||||||
public then(onFulfil?: PromiseFulfillFunc<T>, onReject?: PromiseRejectFunc) {
|
// [syms.callbacks]?: Callback<T>[] = [];
|
||||||
return new Promise((resolve, reject) => {
|
// [syms.handled] = false;
|
||||||
onFulfil ??= v => v;
|
// [syms.state] = State.Pending;
|
||||||
onReject ??= v => v;
|
// [syms.value]?: T | unknown;
|
||||||
|
|
||||||
const callback = (func: (val: any) => any) => (v: any) => {
|
// public then(onFulfil?: PromiseFulfillFunc<T>, onReject?: PromiseRejectFunc) {
|
||||||
try { resolve(func(v)); }
|
// return new Promise((resolve, reject) => {
|
||||||
catch (e) { reject(e); }
|
// onFulfil ??= v => v;
|
||||||
}
|
// onReject ??= v => v;
|
||||||
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>) {
|
// const callback = (func: (val: any) => any) => (v: any) => {
|
||||||
internals.pushMessage(true, func, undefined, [
|
// try {
|
||||||
((v) => resolve(this, v, State.Fulfilled)) as PromiseFulfillFunc<T>,
|
// resolve(func(v));
|
||||||
((err) => resolve(this, err, State.Rejected)) as PromiseRejectFunc
|
// }
|
||||||
]);
|
// catch (e) { reject(e); }
|
||||||
}
|
// }
|
||||||
}
|
// switch (this[syms.state]) {
|
||||||
env.global.Promise = Promise as any;
|
// 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;
|
let timeoutI = 0, intervalI = 0;
|
||||||
|
|
||||||
env.global.setTimeout = (func, delay, ...args) => {
|
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;
|
delay = (delay ?? 0) - 0;
|
||||||
const cancelFunc = internals.delay(delay, () => internals.apply(func, undefined, args));
|
const cancelFunc = internals.delay(delay, () => internals.apply(func, undefined, args));
|
||||||
timeouts[++timeoutI] = cancelFunc;
|
timeouts[++timeoutI] = cancelFunc;
|
||||||
return timeoutI;
|
return timeoutI;
|
||||||
};
|
};
|
||||||
env.global.setInterval = (func, delay, ...args) => {
|
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;
|
delay = (delay ?? 0) - 0;
|
||||||
|
|
||||||
const i = ++intervalI;
|
const i = ++intervalI;
|
||||||
|
@ -1,140 +1,5 @@
|
|||||||
define("values/function", () => {
|
define("values/function", () => {
|
||||||
var Function = env.global.Function = function() {
|
var Function = env.global.Function = internals.function;
|
||||||
throw 'Using the constructor Function() is forbidden.';
|
|
||||||
} as unknown as FunctionConstructor;
|
|
||||||
|
|
||||||
env.setProto('function', Function.prototype);
|
env.setProto('function', Function.prototype);
|
||||||
setConstr(Function.prototype, Function);
|
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", () => {
|
define("values/object", () => {
|
||||||
var Object = env.global.Object = function(arg: any) {
|
var Object = env.global.Object = internals.object;
|
||||||
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);
|
|
||||||
(Object.prototype as any).__proto__ = null;
|
(Object.prototype as any).__proto__ = null;
|
||||||
setConstr(Object.prototype, Object as any);
|
env.setProto('object', Object.prototype);
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
@ -2,8 +2,8 @@ define("values/symbol", () => {
|
|||||||
const symbols: Record<string, symbol> = { };
|
const symbols: Record<string, symbol> = { };
|
||||||
|
|
||||||
var Symbol = env.global.Symbol = function(this: any, val?: string) {
|
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 (this !== undefined && this !== null) throw new env.global.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 (typeof val !== 'string' && val !== undefined) throw new env.global.TypeError('val must be a string or undefined.');
|
||||||
return internals.symbol(val);
|
return internals.symbol(val);
|
||||||
} as SymbolConstructor;
|
} as SymbolConstructor;
|
||||||
|
|
||||||
@ -12,23 +12,23 @@ define("values/symbol", () => {
|
|||||||
|
|
||||||
setProps(Symbol, {
|
setProps(Symbol, {
|
||||||
for(key) {
|
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];
|
if (key in symbols) return symbols[key];
|
||||||
else return symbols[key] = internals.symbol(key);
|
else return symbols[key] = internals.symbol(key);
|
||||||
},
|
},
|
||||||
keyFor(sym) {
|
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);
|
return internals.symbolToString(sym);
|
||||||
},
|
},
|
||||||
|
|
||||||
typeName: Symbol("Symbol.name") as any,
|
typeName: env.symbol("Symbol.typeName") as any,
|
||||||
replace: Symbol('Symbol.replace') as any,
|
replace: env.symbol('Symbol.replace') as any,
|
||||||
match: Symbol('Symbol.match') as any,
|
match: env.symbol('Symbol.match') as any,
|
||||||
matchAll: Symbol('Symbol.matchAll') as any,
|
matchAll: env.symbol('Symbol.matchAll') as any,
|
||||||
split: Symbol('Symbol.split') as any,
|
split: env.symbol('Symbol.split') as any,
|
||||||
search: Symbol('Symbol.search') as any,
|
search: env.symbol('Symbol.search') as any,
|
||||||
iterator: Symbol('Symbol.iterator') as any,
|
iterator: env.symbol('Symbol.iterator') as any,
|
||||||
asyncIterator: Symbol('Symbol.asyncIterator') as any,
|
asyncIterator: env.symbol('Symbol.asyncIterator') as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
internals.defineField(env.global.Object.prototype, Symbol.typeName, 'Object', false, false, false);
|
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.MessageContext;
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
import me.topchetoeu.jscript.engine.Context;
|
||||||
import me.topchetoeu.jscript.engine.Engine;
|
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.engine.values.Values;
|
||||||
import me.topchetoeu.jscript.events.Observer;
|
import me.topchetoeu.jscript.events.Observer;
|
||||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||||
@ -21,7 +21,7 @@ import me.topchetoeu.jscript.polyfills.Internals;
|
|||||||
public class Main {
|
public class Main {
|
||||||
static Thread task;
|
static Thread task;
|
||||||
static Engine engine;
|
static Engine engine;
|
||||||
static FunctionContext env;
|
static Environment env;
|
||||||
|
|
||||||
public static String streamToString(InputStream in) {
|
public static String streamToString(InputStream in) {
|
||||||
try {
|
try {
|
||||||
@ -47,37 +47,14 @@ public class Main {
|
|||||||
|
|
||||||
private static Observer<Object> valuePrinter = new Observer<Object>() {
|
private static Observer<Object> valuePrinter = new Observer<Object>() {
|
||||||
public void next(Object data) {
|
public void next(Object data) {
|
||||||
try {
|
try { Values.printValue(null, data); }
|
||||||
Values.printValue(null, data);
|
|
||||||
}
|
|
||||||
catch (InterruptedException e) { }
|
catch (InterruptedException e) { }
|
||||||
System.out.println();
|
System.out.println();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void error(RuntimeException err) {
|
public void error(RuntimeException err) {
|
||||||
try {
|
try { Values.printError(err, null); }
|
||||||
try {
|
catch (InterruptedException ex) { return; }
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,8 +62,10 @@ public class Main {
|
|||||||
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
|
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));
|
var in = new BufferedReader(new InputStreamReader(System.in));
|
||||||
engine = new Engine();
|
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];
|
var exited = new boolean[1];
|
||||||
|
|
||||||
env.global.define("exit", ctx -> {
|
env.global.define("exit", ctx -> {
|
||||||
|
@ -5,16 +5,16 @@ import me.topchetoeu.jscript.engine.values.Values;
|
|||||||
import me.topchetoeu.jscript.parsing.Parsing;
|
import me.topchetoeu.jscript.parsing.Parsing;
|
||||||
|
|
||||||
public class Context {
|
public class Context {
|
||||||
public final FunctionContext function;
|
public final Environment env;
|
||||||
public final MessageContext message;
|
public final MessageContext message;
|
||||||
|
|
||||||
public FunctionValue compile(String filename, String raw) throws InterruptedException {
|
public FunctionValue compile(String filename, String raw) throws InterruptedException {
|
||||||
var res = Values.toString(this, function.compile.call(this, null, raw, filename));
|
var res = Values.toString(this, env.compile.call(this, null, raw, filename));
|
||||||
return Parsing.compile(function, filename, res);
|
return Parsing.compile(env, filename, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context(FunctionContext funcCtx, MessageContext msgCtx) {
|
public Context(Environment funcCtx, MessageContext msgCtx) {
|
||||||
this.function = funcCtx;
|
this.env = funcCtx;
|
||||||
this.message = msgCtx;
|
this.message = msgCtx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ public class Engine {
|
|||||||
private class UncompiledFunction extends FunctionValue {
|
private class UncompiledFunction extends FunctionValue {
|
||||||
public final String filename;
|
public final String filename;
|
||||||
public final String raw;
|
public final String raw;
|
||||||
public final FunctionContext ctx;
|
public final Environment ctx;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
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);
|
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);
|
super(filename, 0);
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
this.raw = raw;
|
this.raw = raw;
|
||||||
@ -109,7 +109,7 @@ public class Engine {
|
|||||||
return msg.notifier;
|
return msg.notifier;
|
||||||
}
|
}
|
||||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) {
|
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() {
|
// 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.FunctionValue;
|
||||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||||
|
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||||
import me.topchetoeu.jscript.interop.Native;
|
import me.topchetoeu.jscript.interop.Native;
|
||||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||||
|
|
||||||
public class FunctionContext {
|
public class Environment {
|
||||||
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
|
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
|
||||||
public GlobalScope global;
|
public GlobalScope global;
|
||||||
public WrappersProvider wrappersProvider;
|
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 compile;
|
||||||
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
|
@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) {
|
@Native public void setProto(String name, ObjectValue val) {
|
||||||
prototypes.put(name, 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 arrayPrototype = new ObjectValue();
|
||||||
// @Native public ObjectValue boolPrototype = new ObjectValue();
|
// @Native public ObjectValue boolPrototype = new ObjectValue();
|
||||||
// @Native public ObjectValue functionPrototype = new ObjectValue();
|
// @Native public ObjectValue functionPrototype = new ObjectValue();
|
||||||
@ -48,21 +63,21 @@ public class FunctionContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Native
|
@Native
|
||||||
public FunctionContext fork() {
|
public Environment fork() {
|
||||||
var res = new FunctionContext(compile, wrappersProvider, global);
|
var res = new Environment(compile, wrappersProvider, global);
|
||||||
res.regexConstructor = regexConstructor;
|
res.regexConstructor = regexConstructor;
|
||||||
res.prototypes = new HashMap<>(prototypes);
|
res.prototypes = new HashMap<>(prototypes);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Native
|
@Native
|
||||||
public FunctionContext child() {
|
public Environment child() {
|
||||||
var res = fork();
|
var res = fork();
|
||||||
res.global = res.global.globalChild();
|
res.global = res.global.globalChild();
|
||||||
return res;
|
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 (compile == null) compile = new NativeFunction("compile", (ctx, thisArg, args) -> args.length == 0 ? "" : args[0]);
|
||||||
if (nativeConverter == null) nativeConverter = new WrappersProvider() {
|
if (nativeConverter == null) nativeConverter = new WrappersProvider() {
|
||||||
public ObjectValue getConstr(Class<?> obj) {
|
public ObjectValue getConstr(Class<?> obj) {
|
@ -107,11 +107,11 @@ public class CodeFrame {
|
|||||||
return Runners.exec(ctx, instr, this);
|
return Runners.exec(ctx, instr, this);
|
||||||
}
|
}
|
||||||
catch (EngineException e) {
|
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;
|
TryCtx tryCtx = null;
|
||||||
if (prevError != Runners.NO_RETURN) prevReturn = Runners.NO_RETURN;
|
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 (prevError != Runners.NO_RETURN) throw new EngineException(prevError);
|
||||||
if (prevReturn != Runners.NO_RETURN) return prevReturn;
|
if (prevReturn != Runners.NO_RETURN) return prevReturn;
|
||||||
|
if (prevValue != Runners.NO_RETURN) push(ctx, prevValue);
|
||||||
|
|
||||||
if (tryCtx == null) return nextNoTry(ctx);
|
if (tryCtx == null) return nextNoTry(ctx);
|
||||||
else if (tryCtx.state == TryCtx.STATE_TRY) {
|
else if (tryCtx.state == TryCtx.STATE_TRY) {
|
||||||
@ -263,7 +264,7 @@ public class CodeFrame {
|
|||||||
try {
|
try {
|
||||||
ctx.message.pushFrame(this);
|
ctx.message.pushFrame(this);
|
||||||
while (true) {
|
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;
|
if (res != Runners.NO_RETURN) return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,16 +48,18 @@ public class Runners {
|
|||||||
var callArgs = frame.take(instr.get(0));
|
var callArgs = frame.take(instr.get(0));
|
||||||
var funcObj = frame.pop();
|
var funcObj = frame.pop();
|
||||||
|
|
||||||
if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
|
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
|
||||||
frame.push(ctx, call(ctx, funcObj, null, callArgs));
|
|
||||||
}
|
// if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
|
||||||
else {
|
// frame.push(ctx, call(ctx, funcObj, null, callArgs));
|
||||||
var proto = Values.getMember(ctx, funcObj, "prototype");
|
// }
|
||||||
var obj = new ObjectValue();
|
// else {
|
||||||
obj.setPrototype(ctx, proto);
|
// var proto = Values.getMember(ctx, funcObj, "prototype");
|
||||||
call(ctx, funcObj, obj, callArgs);
|
// var obj = new ObjectValue();
|
||||||
frame.push(ctx, obj);
|
// obj.setPrototype(ctx, proto);
|
||||||
}
|
// call(ctx, funcObj, obj, callArgs);
|
||||||
|
// frame.push(ctx, obj);
|
||||||
|
// }
|
||||||
|
|
||||||
frame.codePtr++;
|
frame.codePtr++;
|
||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
@ -65,7 +67,7 @@ public class Runners {
|
|||||||
|
|
||||||
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||||
var name = (String)instr.get(0);
|
var name = (String)instr.get(0);
|
||||||
ctx.function.global.define(name);
|
ctx.env.global.define(name);
|
||||||
frame.codePtr++;
|
frame.codePtr++;
|
||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
}
|
}
|
||||||
@ -160,7 +162,7 @@ public class Runners {
|
|||||||
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||||
var i = instr.get(0);
|
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));
|
else frame.push(ctx, frame.scope.get((int)i).get(ctx));
|
||||||
|
|
||||||
frame.codePtr++;
|
frame.codePtr++;
|
||||||
@ -172,7 +174,7 @@ public class Runners {
|
|||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
}
|
}
|
||||||
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
|
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++;
|
frame.codePtr++;
|
||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
}
|
}
|
||||||
@ -198,7 +200,7 @@ public class Runners {
|
|||||||
var body = new Instruction[end - start];
|
var body = new Instruction[end - start];
|
||||||
System.arraycopy(frame.function.body, start, body, 0, 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.push(ctx, func);
|
||||||
|
|
||||||
frame.codePtr += n;
|
frame.codePtr += n;
|
||||||
@ -222,7 +224,7 @@ public class Runners {
|
|||||||
return execLoadMember(ctx, instr, frame);
|
return execLoadMember(ctx, instr, frame);
|
||||||
}
|
}
|
||||||
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
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++;
|
frame.codePtr++;
|
||||||
return NO_RETURN;
|
return NO_RETURN;
|
||||||
}
|
}
|
||||||
@ -246,7 +248,7 @@ public class Runners {
|
|||||||
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
|
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
|
||||||
var i = instr.get(0);
|
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);
|
else frame.scope.get((int)i).set(ctx, val);
|
||||||
|
|
||||||
frame.codePtr++;
|
frame.codePtr++;
|
||||||
@ -293,8 +295,8 @@ public class Runners {
|
|||||||
Object obj;
|
Object obj;
|
||||||
|
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
if (ctx.function.global.has(ctx, name)) {
|
if (ctx.env.global.has(ctx, name)) {
|
||||||
obj = ctx.function.global.get(ctx, name);
|
obj = ctx.env.global.get(ctx, name);
|
||||||
}
|
}
|
||||||
else obj = null;
|
else obj = null;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package me.topchetoeu.jscript.engine.values;
|
|||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
import me.topchetoeu.jscript.compilation.Instruction;
|
import me.topchetoeu.jscript.compilation.Instruction;
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
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.frame.CodeFrame;
|
||||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ public class CodeFunction extends FunctionValue {
|
|||||||
public final int length;
|
public final int length;
|
||||||
public final Instruction[] body;
|
public final Instruction[] body;
|
||||||
public final ValueVariable[] captures;
|
public final ValueVariable[] captures;
|
||||||
public FunctionContext environment;
|
public Environment environment;
|
||||||
|
|
||||||
public Location loc() {
|
public Location loc() {
|
||||||
for (var instr : body) {
|
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));
|
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);
|
super(name, length);
|
||||||
this.captures = captures;
|
this.captures = captures;
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
|
@ -8,7 +8,8 @@ public class NativeWrapper extends ObjectValue {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
|
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);
|
else return super.getPrototype(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ public class ObjectValue {
|
|||||||
|
|
||||||
public final boolean memberWritable(Object key) {
|
public final boolean memberWritable(Object key) {
|
||||||
if (state == State.FROZEN) return false;
|
if (state == State.FROZEN) return false;
|
||||||
return !nonWritableSet.contains(key);
|
return values.containsKey(key) && !nonWritableSet.contains(key);
|
||||||
}
|
}
|
||||||
public final boolean memberConfigurable(Object key) {
|
public final boolean memberConfigurable(Object key) {
|
||||||
if (state == State.SEALED || state == State.FROZEN) return false;
|
if (state == State.SEALED || state == State.FROZEN) return false;
|
||||||
@ -147,13 +147,13 @@ public class ObjectValue {
|
|||||||
|
|
||||||
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
|
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
|
||||||
try {
|
try {
|
||||||
if (prototype == OBJ_PROTO) return ctx.function.proto("object");
|
if (prototype == OBJ_PROTO) return ctx.env.proto("object");
|
||||||
if (prototype == ARR_PROTO) return ctx.function.proto("array");
|
if (prototype == ARR_PROTO) return ctx.env.proto("array");
|
||||||
if (prototype == FUNC_PROTO) return ctx.function.proto("function");
|
if (prototype == FUNC_PROTO) return ctx.env.proto("function");
|
||||||
if (prototype == ERR_PROTO) return ctx.function.proto("error");
|
if (prototype == ERR_PROTO) return ctx.env.proto("error");
|
||||||
if (prototype == RANGE_ERR_PROTO) return ctx.function.proto("rangeErr");
|
if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr");
|
||||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.function.proto("syntaxErr");
|
if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr");
|
||||||
if (prototype == TYPE_ERR_PROTO) return ctx.function.proto("typeErr");
|
if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr");
|
||||||
}
|
}
|
||||||
catch (NullPointerException e) {
|
catch (NullPointerException e) {
|
||||||
return null;
|
return null;
|
||||||
@ -172,14 +172,14 @@ public class ObjectValue {
|
|||||||
else if (Values.isObject(val)) {
|
else if (Values.isObject(val)) {
|
||||||
var obj = Values.object(val);
|
var obj = Values.object(val);
|
||||||
|
|
||||||
if (ctx != null && ctx.function != null) {
|
if (ctx != null && ctx.env != null) {
|
||||||
if (obj == ctx.function.proto("object")) prototype = OBJ_PROTO;
|
if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO;
|
||||||
else if (obj == ctx.function.proto("array")) prototype = ARR_PROTO;
|
else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO;
|
||||||
else if (obj == ctx.function.proto("function")) prototype = FUNC_PROTO;
|
else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO;
|
||||||
else if (obj == ctx.function.proto("error")) prototype = ERR_PROTO;
|
else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO;
|
||||||
else if (obj == ctx.function.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
|
else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
|
||||||
else if (obj == ctx.function.proto("typeErr")) prototype = TYPE_ERR_PROTO;
|
else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO;
|
||||||
else if (obj == ctx.function.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
|
else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
|
||||||
else prototype = obj;
|
else prototype = obj;
|
||||||
}
|
}
|
||||||
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.Context;
|
||||||
import me.topchetoeu.jscript.engine.Operation;
|
import me.topchetoeu.jscript.engine.Operation;
|
||||||
import me.topchetoeu.jscript.engine.frame.ConvertHint;
|
import me.topchetoeu.jscript.engine.frame.ConvertHint;
|
||||||
|
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||||
|
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||||
|
|
||||||
public class Values {
|
public class Values {
|
||||||
public static final Object NULL = new Object();
|
public static final Object NULL = new Object();
|
||||||
@ -321,10 +323,10 @@ public class Values {
|
|||||||
if (isObject(obj)) return object(obj).getPrototype(ctx);
|
if (isObject(obj)) return object(obj).getPrototype(ctx);
|
||||||
if (ctx == null) return null;
|
if (ctx == null) return null;
|
||||||
|
|
||||||
if (obj instanceof String) return ctx.function.proto("string");
|
if (obj instanceof String) return ctx.env.proto("string");
|
||||||
else if (obj instanceof Number) return ctx.function.proto("number");
|
else if (obj instanceof Number) return ctx.env.proto("number");
|
||||||
else if (obj instanceof Boolean) return ctx.function.proto("bool");
|
else if (obj instanceof Boolean) return ctx.env.proto("bool");
|
||||||
else if (obj instanceof Symbol) return ctx.function.proto("symbol");
|
else if (obj instanceof Symbol) return ctx.env.proto("symbol");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -352,12 +354,39 @@ public class Values {
|
|||||||
|
|
||||||
return res;
|
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 {
|
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException {
|
||||||
if (!isFunction(func))
|
if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
|
||||||
throw EngineException.ofType("Tried to call a non-function value.");
|
|
||||||
return function(func).call(ctx, thisArg, args);
|
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) {
|
public static boolean strictEquals(Context ctx, Object a, Object b) {
|
||||||
a = normalize(ctx, a); b = normalize(ctx, b);
|
a = normalize(ctx, a); b = normalize(ctx, b);
|
||||||
@ -420,7 +449,7 @@ public class Values {
|
|||||||
|
|
||||||
if (val instanceof Class) {
|
if (val instanceof Class) {
|
||||||
if (ctx == null) return null;
|
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);
|
return new NativeWrapper(val);
|
||||||
@ -429,17 +458,15 @@ public class Values {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) throws InterruptedException {
|
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) throws InterruptedException {
|
||||||
if (clazz == Void.class) return null;
|
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) {
|
if (obj instanceof NativeWrapper) {
|
||||||
var res = ((NativeWrapper)obj).wrapped;
|
var res = ((NativeWrapper)obj).wrapped;
|
||||||
if (clazz.isInstance(res)) return (T)res;
|
if (clazz.isInstance(res)) return (T)res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clazz == null || clazz == Object.class) return (T)obj;
|
||||||
|
|
||||||
if (obj instanceof ArrayValue) {
|
if (obj instanceof ArrayValue) {
|
||||||
|
|
||||||
if (clazz.isAssignableFrom(ArrayList.class)) {
|
if (clazz.isAssignableFrom(ArrayList.class)) {
|
||||||
var raw = array(obj).toArray();
|
var raw = array(obj).toArray();
|
||||||
var res = new ArrayList<>();
|
var res = new ArrayList<>();
|
||||||
@ -480,11 +507,9 @@ public class Values {
|
|||||||
|
|
||||||
if (clazz == Character.class || clazz == char.class) {
|
if (clazz == Character.class || clazz == char.class) {
|
||||||
if (obj instanceof Number) return (T)(Character)(char)number(obj);
|
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 {
|
else {
|
||||||
var res = toString(ctx, obj);
|
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);
|
else return (T)(Character)res.charAt(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -492,20 +517,22 @@ public class Values {
|
|||||||
if (obj == null) return null;
|
if (obj == null) return null;
|
||||||
if (clazz.isInstance(obj)) return (T)obj;
|
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 {
|
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) throws InterruptedException {
|
||||||
return () -> {
|
return () -> {
|
||||||
try {
|
try {
|
||||||
var constr = getMember(ctx, ctx.function.proto("symbol"), "constructor");
|
var symbol = ctx.env.symbol("Symbol.iterator");
|
||||||
var symbol = getMember(ctx, constr, "iterator");
|
|
||||||
|
|
||||||
var iteratorFunc = getMember(ctx, obj, symbol);
|
var iteratorFunc = getMember(ctx, obj, symbol);
|
||||||
if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
|
if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
|
||||||
var iterator = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
|
var iterator = iteratorFunc instanceof FunctionValue ?
|
||||||
if (!isFunction(iterator)) return Collections.emptyIterator();
|
((FunctionValue)iteratorFunc).call(ctx, iteratorFunc, obj) :
|
||||||
var iterable = obj;
|
iteratorFunc;
|
||||||
|
var nextFunc = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
|
||||||
|
|
||||||
|
if (!isFunction(nextFunc)) return Collections.emptyIterator();
|
||||||
|
|
||||||
return new Iterator<Object>() {
|
return new Iterator<Object>() {
|
||||||
private Object value = null;
|
private Object value = null;
|
||||||
@ -515,7 +542,7 @@ public class Values {
|
|||||||
private void loadNext() throws InterruptedException {
|
private void loadNext() throws InterruptedException {
|
||||||
if (next == null) value = null;
|
if (next == null) value = null;
|
||||||
else if (consumed) {
|
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 (curr == null) { next = null; value = null; }
|
||||||
if (toBoolean(curr.getMember(ctx, "done"))) { next = null; value = null; }
|
if (toBoolean(curr.getMember(ctx, "done"))) { next = null; value = null; }
|
||||||
else {
|
else {
|
||||||
@ -567,7 +594,7 @@ public class Values {
|
|||||||
var it = iterable.iterator();
|
var it = iterable.iterator();
|
||||||
|
|
||||||
try {
|
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));
|
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException | NullPointerException e) { }
|
catch (IllegalArgumentException | NullPointerException e) { }
|
||||||
@ -655,4 +682,25 @@ public class Values {
|
|||||||
public static void printValue(Context ctx, Object val) throws InterruptedException {
|
public static void printValue(Context ctx, Object val) throws InterruptedException {
|
||||||
printValue(ctx, val, new HashSet<>(), 0);
|
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 class EngineException extends RuntimeException {
|
||||||
public final Object value;
|
public final Object value;
|
||||||
public EngineException cause;
|
public EngineException cause;
|
||||||
|
public Context ctx = null;
|
||||||
public final List<String> stackTrace = new ArrayList<>();
|
public final List<String> stackTrace = new ArrayList<>();
|
||||||
|
|
||||||
public EngineException add(String name, Location location) {
|
public EngineException add(String name, Location location) {
|
||||||
@ -27,6 +28,10 @@ public class EngineException extends RuntimeException {
|
|||||||
this.cause = cause;
|
this.cause = cause;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public EngineException setContext(Context ctx) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public String toString(Context ctx) throws InterruptedException {
|
public String toString(Context ctx) throws InterruptedException {
|
||||||
var ss = new StringBuilder();
|
var ss = new StringBuilder();
|
||||||
|
@ -9,4 +9,5 @@ import java.lang.annotation.Target;
|
|||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface Native {
|
public @interface Native {
|
||||||
public String value() default "";
|
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)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface NativeGetter {
|
public @interface NativeGetter {
|
||||||
public String value();
|
public String value();
|
||||||
|
public boolean raw() default false;
|
||||||
}
|
}
|
||||||
|
@ -9,4 +9,5 @@ import java.lang.annotation.Target;
|
|||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface NativeSetter {
|
public @interface NativeSetter {
|
||||||
public String value();
|
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<?>, FunctionValue> constructors = new HashMap<>();
|
||||||
private final HashMap<Class<?>, ObjectValue> prototypes = 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) {
|
private static void applyMethods(boolean member, ObjectValue target, Class<?> clazz) {
|
||||||
for (var method : clazz.getDeclaredMethods()) {
|
for (var method : clazz.getDeclaredMethods()) {
|
||||||
if (!Modifier.isStatic(method.getModifiers()) != member) continue;
|
|
||||||
|
|
||||||
var nat = method.getAnnotation(Native.class);
|
var nat = method.getAnnotation(Native.class);
|
||||||
var get = method.getAnnotation(NativeGetter.class);
|
var get = method.getAnnotation(NativeGetter.class);
|
||||||
var set = method.getAnnotation(NativeSetter.class);
|
var set = method.getAnnotation(NativeSetter.class);
|
||||||
|
var params = method.getParameterTypes();
|
||||||
|
var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member;
|
||||||
|
|
||||||
if (nat != null) {
|
if (nat != null) {
|
||||||
|
if (nat.raw()) {
|
||||||
|
if (isMember(params) != member) continue;
|
||||||
|
}
|
||||||
|
else if (memberMismatch) continue;
|
||||||
|
|
||||||
var name = nat.value();
|
var name = nat.value();
|
||||||
var val = target.values.get(name);
|
var val = target.values.get(name);
|
||||||
|
|
||||||
if (name.equals("")) name = method.getName();
|
if (name.equals("")) name = method.getName();
|
||||||
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name));
|
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 {
|
else {
|
||||||
if (get != null) {
|
if (get != null) {
|
||||||
|
if (get.raw() && isMember(params) != member || memberMismatch) continue;
|
||||||
var name = get.value();
|
var name = get.value();
|
||||||
var prop = target.properties.get(name);
|
var prop = target.properties.get(name);
|
||||||
OverloadFunction getter = null;
|
OverloadFunction getter = null;
|
||||||
@ -40,10 +50,11 @@ public class NativeTypeRegister implements WrappersProvider {
|
|||||||
if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter;
|
if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter;
|
||||||
else getter = new OverloadFunction("get " + name);
|
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);
|
target.defineProperty(null, name, getter, setter, true, true);
|
||||||
}
|
}
|
||||||
if (set != null) {
|
if (set != null) {
|
||||||
|
if (set.raw() && isMember(params) != member || memberMismatch) continue;
|
||||||
var name = set.value();
|
var name = set.value();
|
||||||
var prop = target.properties.get(name);
|
var prop = target.properties.get(name);
|
||||||
var getter = prop == null ? null : prop.getter;
|
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;
|
if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter;
|
||||||
else setter = new OverloadFunction("set " + name);
|
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);
|
target.defineProperty(null, name, getter, setter, true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,8 +77,8 @@ public class NativeTypeRegister implements WrappersProvider {
|
|||||||
if (nat != null) {
|
if (nat != null) {
|
||||||
var name = nat.value();
|
var name = nat.value();
|
||||||
if (name.equals("")) name = field.getName();
|
if (name.equals("")) name = field.getName();
|
||||||
var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field));
|
var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field, nat.raw()));
|
||||||
var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field));
|
var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field, nat.raw()));
|
||||||
target.defineProperty(null, name, getter, setter, true, false);
|
target.defineProperty(null, name, getter, setter, true, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,8 +124,14 @@ public class NativeTypeRegister implements WrappersProvider {
|
|||||||
FunctionValue func = new OverloadFunction(clazz.getName());
|
FunctionValue func = new OverloadFunction(clazz.getName());
|
||||||
|
|
||||||
for (var overload : clazz.getConstructors()) {
|
for (var overload : clazz.getConstructors()) {
|
||||||
if (overload.getAnnotation(Native.class) == null) continue;
|
var nat = overload.getAnnotation(Native.class);
|
||||||
((OverloadFunction)func).add(Overload.fromConstructor(overload));
|
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) {
|
if (((OverloadFunction)func).overloads.size() == 0) {
|
||||||
|
@ -17,53 +17,63 @@ public class Overload {
|
|||||||
|
|
||||||
public final OverloadRunner runner;
|
public final OverloadRunner runner;
|
||||||
public final boolean variadic;
|
public final boolean variadic;
|
||||||
|
public final boolean raw;
|
||||||
public final Class<?> thisArg;
|
public final Class<?> thisArg;
|
||||||
public final Class<?>[] params;
|
public final Class<?>[] params;
|
||||||
|
|
||||||
public static Overload fromMethod(Method method) {
|
public static Overload fromMethod(Method method, boolean raw) {
|
||||||
return new Overload(
|
return new Overload(
|
||||||
(ctx, th, args) -> method.invoke(th, args),
|
(ctx, th, args) -> method.invoke(th, args),
|
||||||
method.isVarArgs(),
|
method.isVarArgs(), raw,
|
||||||
Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(),
|
Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(),
|
||||||
method.getParameterTypes()
|
method.getParameterTypes()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public static Overload fromConstructor(Constructor<?> method) {
|
public static Overload fromConstructor(Constructor<?> method, boolean raw) {
|
||||||
return new Overload(
|
return new Overload(
|
||||||
(ctx, th, args) -> method.newInstance(args),
|
(ctx, th, args) -> method.newInstance(args),
|
||||||
method.isVarArgs(),
|
method.isVarArgs(), raw,
|
||||||
Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(),
|
Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(),
|
||||||
method.getParameterTypes()
|
method.getParameterTypes()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public static Overload getterFromField(Field field) {
|
public static Overload getterFromField(Field field, boolean raw) {
|
||||||
return new Overload(
|
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(),
|
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
|
||||||
new Class[0]
|
new Class[0]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public static Overload setterFromField(Field field) {
|
public static Overload setterFromField(Field field, boolean raw) {
|
||||||
if (Modifier.isFinal(field.getModifiers())) return null;
|
if (Modifier.isFinal(field.getModifiers())) return null;
|
||||||
return new Overload(
|
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(),
|
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
|
||||||
new Class[0]
|
new Class[0]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Overload getter(Class<?> thisArg, OverloadRunner runner) {
|
public static Overload getter(Class<?> thisArg, OverloadRunner runner, boolean raw) {
|
||||||
return new Overload(
|
return new Overload(
|
||||||
(ctx, th, args) -> runner.run(ctx, th, args), false,
|
(ctx, th, args) -> runner.run(ctx, th, args), false, raw,
|
||||||
thisArg,
|
thisArg,
|
||||||
new Class[0]
|
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.runner = runner;
|
||||||
this.variadic = variadic;
|
this.variadic = variadic;
|
||||||
|
this.raw = raw;
|
||||||
this.thisArg = thisArg;
|
this.thisArg = thisArg;
|
||||||
this.params = args;
|
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.Context;
|
||||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
import me.topchetoeu.jscript.engine.values.Values;
|
||||||
|
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||||
|
|
||||||
public class OverloadFunction extends FunctionValue {
|
public class OverloadFunction extends FunctionValue {
|
||||||
public final List<Overload> overloads = new ArrayList<>();
|
public final List<Overload> overloads = new ArrayList<>();
|
||||||
|
|
||||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||||
for (var overload : overloads) {
|
loop: 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);
|
|
||||||
|
|
||||||
Object[] newArgs = new Object[overload.params.length];
|
Object[] newArgs = new Object[overload.params.length];
|
||||||
|
|
||||||
for (var i = start; i < end; i++) {
|
if (overload.raw) {
|
||||||
Object val;
|
newArgs[0] = ctx;
|
||||||
|
newArgs[1] = thisArg;
|
||||||
if (i - start >= args.length) val = null;
|
newArgs[2] = args;
|
||||||
else val = args[i - start];
|
|
||||||
|
|
||||||
newArgs[i] = Values.convert(ctx, val, overload.params[i]);
|
|
||||||
}
|
}
|
||||||
|
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) {
|
for (var i = start; i < end; i++) {
|
||||||
var type = overload.params[overload.params.length - 1].getComponentType();
|
Object val;
|
||||||
var n = Math.max(args.length - end + start, 0);
|
|
||||||
Object varArg = Array.newInstance(type, n);
|
|
||||||
|
|
||||||
for (var i = 0; i < n; i++) {
|
if (i - start >= args.length) val = null;
|
||||||
Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type));
|
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);
|
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.*;
|
||||||
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
|
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
|
||||||
import me.topchetoeu.jscript.compilation.values.*;
|
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.Operation;
|
||||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||||
@ -1875,7 +1875,7 @@ public class Parsing {
|
|||||||
return list.toArray(Statement[]::new);
|
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 target = environment.global.globalChild();
|
||||||
var subscope = target.child();
|
var subscope = target.child();
|
||||||
var res = new ArrayList<Instruction>();
|
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));
|
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 {
|
try {
|
||||||
return compile(environment, parse(filename, raw));
|
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.exceptions.EngineException;
|
||||||
import me.topchetoeu.jscript.interop.Native;
|
import me.topchetoeu.jscript.interop.Native;
|
||||||
|
|
||||||
public class GeneratorFunction extends FunctionValue {
|
public class GeneratorPolyfill extends FunctionValue {
|
||||||
public final FunctionValue factory;
|
public final FunctionValue factory;
|
||||||
|
|
||||||
public static class Generator {
|
public static class Generator {
|
||||||
@ -28,12 +28,12 @@ public class GeneratorFunction extends FunctionValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object res = null;
|
Object res = null;
|
||||||
if (inducedValue != Runners.NO_RETURN) frame.push(ctx, inducedValue);
|
|
||||||
ctx.message.pushFrame(frame);
|
ctx.message.pushFrame(frame);
|
||||||
yielding = false;
|
yielding = false;
|
||||||
|
|
||||||
while (!yielding) {
|
while (!yielding) {
|
||||||
try {
|
try {
|
||||||
res = frame.next(ctx, inducedReturn, inducedError);
|
res = frame.next(ctx, inducedValue, inducedReturn, inducedError);
|
||||||
inducedReturn = inducedError = Runners.NO_RETURN;
|
inducedReturn = inducedError = Runners.NO_RETURN;
|
||||||
if (res != Runners.NO_RETURN) {
|
if (res != Runners.NO_RETURN) {
|
||||||
done = true;
|
done = true;
|
||||||
@ -74,7 +74,7 @@ public class GeneratorFunction extends FunctionValue {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
if (done) return "Generator [closed]";
|
if (done) return "Generator [closed]";
|
||||||
if (yielding) return "Generator [suspended]";
|
if (yielding) return "Generator [suspended]";
|
||||||
return "Generator " + (done ? "[closed]" : "[suspended]");
|
return "Generator [running]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object yield(Context ctx, Object thisArg, Object[] args) {
|
public Object yield(Context ctx, Object thisArg, Object[] args) {
|
||||||
@ -92,7 +92,7 @@ public class GeneratorFunction extends FunctionValue {
|
|||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GeneratorFunction(FunctionValue factory) {
|
public GeneratorPolyfill(FunctionValue factory) {
|
||||||
super(factory.name, factory.length);
|
super(factory.name, factory.length);
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package me.topchetoeu.jscript.polyfills;
|
package me.topchetoeu.jscript.polyfills;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
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.ArrayValue;
|
||||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||||
@ -12,20 +12,25 @@ import me.topchetoeu.jscript.engine.values.Values;
|
|||||||
import me.topchetoeu.jscript.interop.Native;
|
import me.topchetoeu.jscript.interop.Native;
|
||||||
|
|
||||||
public class Internals {
|
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) {
|
@Native public void markSpecial(FunctionValue ...funcs) {
|
||||||
for (var func : funcs) {
|
for (var func : funcs) {
|
||||||
func.special = true;
|
func.special = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Native public FunctionContext getEnv(Object func) {
|
@Native public Environment getEnv(Object func) {
|
||||||
if (func instanceof CodeFunction) return ((CodeFunction)func).environment;
|
if (func instanceof CodeFunction) return ((CodeFunction)func).environment;
|
||||||
else return null;
|
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;
|
if (func instanceof CodeFunction) ((CodeFunction)func).environment = env;
|
||||||
return func;
|
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());
|
return func.call(ctx, thisArg, args.toArray());
|
||||||
}
|
}
|
||||||
@Native public FunctionValue delay(Context ctx, double delay, FunctionValue callback) throws InterruptedException {
|
@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) {
|
@Native public boolean isArray(Object obj) {
|
||||||
return obj instanceof ArrayValue;
|
return obj instanceof ArrayValue;
|
||||||
}
|
}
|
||||||
@Native public GeneratorFunction generator(FunctionValue obj) {
|
@Native public GeneratorPolyfill generator(FunctionValue obj) {
|
||||||
return new GeneratorFunction(obj);
|
return new GeneratorPolyfill(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Native public boolean defineField(Context ctx, ObjectValue obj, Object key, Object val, boolean writable, boolean enumerable, boolean configurable) {
|
@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