From bed4014bef24c01c59802ce05a5319c3bffa4e9e Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:53:02 +0200 Subject: [PATCH] feat: implement simple env + ts loader --- src/lib/libs/_entry.ts | 56 +++++++++ src/lib/libs/array.ts | 53 +++++++++ src/lib/libs/boolean.ts | 29 +++++ src/lib/libs/errors.ts | 39 +++++++ src/lib/libs/function.ts | 61 ++++++++++ src/lib/libs/map.ts | 61 ++++++++++ src/lib/libs/number.ts | 77 +++++++++++++ src/lib/libs/object.ts | 90 +++++++++++++++ src/lib/libs/polyfills/callSuper.js | 5 + src/lib/libs/polyfills/classCallCheck.js | 5 + src/lib/libs/polyfills/createClass.js | 25 +++++ src/lib/libs/polyfills/defineProperty.js | 7 ++ src/lib/libs/polyfills/getPrototypeOf.js | 5 + src/lib/libs/polyfills/inherits.js | 11 ++ .../polyfills/possibleConstructorReturn.js | 2 + src/lib/libs/polyfills/setPrototypeOf.js | 5 + src/lib/libs/polyfills/typeof.js | 3 + src/lib/libs/primordials.ts | 82 ++++++++++++++ src/lib/libs/regex.ts | 54 +++++++++ src/lib/libs/string.ts | 84 ++++++++++++++ src/lib/libs/symbol.ts | 52 +++++++++ src/lib/libs/utils.ts | 21 ++++ src/lib/ts/_entry.ts | 106 ++++++++++++++++++ .../jscript/runtime/SimpleRepl.java | 76 +++++++++---- 24 files changed, 989 insertions(+), 20 deletions(-) create mode 100644 src/lib/libs/_entry.ts create mode 100644 src/lib/libs/array.ts create mode 100644 src/lib/libs/boolean.ts create mode 100644 src/lib/libs/errors.ts create mode 100644 src/lib/libs/function.ts create mode 100644 src/lib/libs/map.ts create mode 100644 src/lib/libs/number.ts create mode 100644 src/lib/libs/object.ts create mode 100644 src/lib/libs/polyfills/callSuper.js create mode 100644 src/lib/libs/polyfills/classCallCheck.js create mode 100644 src/lib/libs/polyfills/createClass.js create mode 100644 src/lib/libs/polyfills/defineProperty.js create mode 100644 src/lib/libs/polyfills/getPrototypeOf.js create mode 100644 src/lib/libs/polyfills/inherits.js create mode 100644 src/lib/libs/polyfills/possibleConstructorReturn.js create mode 100644 src/lib/libs/polyfills/setPrototypeOf.js create mode 100644 src/lib/libs/polyfills/typeof.js create mode 100644 src/lib/libs/primordials.ts create mode 100644 src/lib/libs/regex.ts create mode 100644 src/lib/libs/string.ts create mode 100644 src/lib/libs/symbol.ts create mode 100644 src/lib/libs/utils.ts create mode 100644 src/lib/ts/_entry.ts diff --git a/src/lib/libs/_entry.ts b/src/lib/libs/_entry.ts new file mode 100644 index 0000000..2357ee4 --- /dev/null +++ b/src/lib/libs/_entry.ts @@ -0,0 +1,56 @@ +import { Boolean } from "./boolean.ts"; +import { Error, RangeError, SyntaxError, TypeError } from "./errors.ts"; +import { Function } from "./function.ts"; +import { Number } from "./number.ts"; +import { Object } from "./object.ts"; +import { object, setGlobalPrototypes, target } from "./primordials.ts"; +import { String } from "./string.ts"; +import { Symbol } from "./symbol.ts"; +import { Array } from "./array.ts"; +import { Map } from "./map.ts"; +import { RegExp } from "./regex.ts"; + +declare global { + function print(...args: any[]): void; +} + +object.defineField(target, "undefined", false, false, false, undefined); + +target.Symbol = Symbol; +target.Number = Number; +target.String = String; +target.Boolean = Boolean; + +target.Object = Object; +target.Function = Function; +target.Array = Array; + +target.Error = Error; +target.RangeError = RangeError; +target.SyntaxError = SyntaxError; +target.TypeError = TypeError; + +target.Map = Map; + +target.parseInt = Number.parseInt; +target.parseFloat = Number.parseFloat; +target.NaN = Number.NaN; +target.Infinity = Number.POSITIVE_INFINITY; + + +target.RegExp = RegExp; + +setGlobalPrototypes({ + string: String.prototype, + number: Number.prototype, + boolean: Boolean.prototype, + symbol: Symbol.prototype, + object: Object.prototype, + array: Array.prototype, + function: Function.prototype, + error: Error.prototype, + syntax: SyntaxError.prototype, + range: RangeError.prototype, + type: TypeError.prototype, + regex: RegExp, +}); diff --git a/src/lib/libs/array.ts b/src/lib/libs/array.ts new file mode 100644 index 0000000..dc64700 --- /dev/null +++ b/src/lib/libs/array.ts @@ -0,0 +1,53 @@ +import { func, object } from "./primordials.ts"; +import { Symbol } from "./symbol.ts"; + +export const Array = (() => { + class Array { + public forEach(this: any[], cb: (val: any, i: number, self: this[]) => void, self?: any) { + for (let i = 0; i < this.length; i++) { + if (i in this) func.invoke(cb, self, [this[i], i, this]); + } + } + + public [Symbol.iterator](this: any[]) { + let i = 0; + let arr: any[] | undefined = this; + + return { + next() { + if (arr == null) return { done: true, value: undefined }; + if (i > arr.length) { + arr = undefined; + return { done: true, value: undefined }; + } + else { + const val = arr[i++]; + if (i >= arr.length) arr = undefined; + return { done: false, value: val }; + } + }, + [Symbol.iterator]() { return this; } + }; + } + + public constructor (len: unknown) { + if (arguments.length === 1 && typeof len === "number") { + const res: any[] = []; + res.length = len; + return res as any; + } + // TODO: Implement spreading + else throw new Error("Spreading not implemented"); + } + + public static isArray(val: any[]) { + object.isArray(val); + } + } + + func.setCallable(Array, true); + func.setConstructable(Array, true); + + return Array as any as typeof Array & ((value?: unknown) => object); +})(); +export type Array = InstanceType; diff --git a/src/lib/libs/boolean.ts b/src/lib/libs/boolean.ts new file mode 100644 index 0000000..b7bc36f --- /dev/null +++ b/src/lib/libs/boolean.ts @@ -0,0 +1,29 @@ +import { func } from "./primordials.ts"; +import { unwrapThis, valueKey } from "./utils.ts"; + +export const Boolean = (() => { + class Boolean { + [valueKey]!: boolean; + + public toString() { + return "" + unwrapThis(this, "boolean", Boolean, "Boolean.prototype.toString"); + } + public valueOf() { + return unwrapThis(this, "boolean", Boolean, "Boolean.prototype.valueOf"); + } + + public constructor(value?: unknown) { + if (func.invokeType(arguments, this) === "call") { + if (arguments.length === 0) return false as any; + else return !!value as any; + } + this[valueKey] = (Boolean as any)(value); + } + }; + + func.setCallable(Boolean, true); + func.setConstructable(Boolean, true); + + return Boolean as any as typeof Boolean & ((value?: unknown) => symbol); +})(); +export type Boolean = InstanceType; diff --git a/src/lib/libs/errors.ts b/src/lib/libs/errors.ts new file mode 100644 index 0000000..fd08a40 --- /dev/null +++ b/src/lib/libs/errors.ts @@ -0,0 +1,39 @@ +import { func, object } from "./primordials.ts"; +import { String } from "./string.ts"; + +export class Error { + public declare name: string; + public declare message: string; + + public toString() { + let res = this.name || "Error"; + const msg = this.message; + + if (msg) res += ": " + msg; + return res; + } + + public constructor (msg = "") { + if (func.invokeType(arguments, this) === "call") return new Error(msg); + this.message = String(msg); + } +} +object.defineField(Error.prototype, "name", true, false, true, "Error"); +object.defineField(Error.prototype, "message", true, false, true, ""); +func.setCallable(Error, true); +func.setConstructable(Error, true); + +export class SyntaxError extends Error { } +object.defineField(SyntaxError.prototype, "name", true, false, true, "SyntaxError"); +func.setCallable(SyntaxError, true); +func.setConstructable(SyntaxError, true); + +export class TypeError extends Error { } +object.defineField(TypeError.prototype, "name", true, false, true, "TypeError"); +func.setCallable(TypeError, true); +func.setConstructable(TypeError, true); + +export class RangeError extends Error { } +object.defineField(RangeError.prototype, "name", true, false, true, "RangeError"); +func.setCallable(RangeError, true); +func.setConstructable(RangeError, true); diff --git a/src/lib/libs/function.ts b/src/lib/libs/function.ts new file mode 100644 index 0000000..4e19113 --- /dev/null +++ b/src/lib/libs/function.ts @@ -0,0 +1,61 @@ +import { compile, func, string } from "./primordials.ts"; +import { String } from "./string.ts"; + +export const Function = (() => { + class Function { + declare public readonly name: string; + declare public readonly length: number; + + public toString(this: Function) { + if (this.name !== "") return "function " + this.name + "(...) { ... }"; + else return "function (...) { ... }"; + } + public valueOf() { + return this; + } + + public apply(this: (...args: any) => any, self: any, args: any[]) { + return func.invoke(this, self, args); + } + public call(this: (...args: any) => any, self: any, ...args: any[]) { + return func.invoke(this, self, args); + } + + public constructor (...args: string[]) { + const parts = ["(function anonymous("]; + for (let i = 0; i < arguments.length - 1; i++) { + if (i > 0) parts[parts.length] = ","; + parts[parts.length] = arguments[i]; + } + parts[parts.length] = "){\n"; + parts[parts.length] = String(arguments[arguments.length - 1]); + parts[parts.length] = "\n})"; + var res = compile(string.stringBuild(parts))(); + return res; + } + + public static compile(src = "", { globals = [], wrap = false }: { globals?: string[], wrap?: boolean } = {}) { + const parts = []; + + if (wrap) parts[parts.length] = "return (function() {\n"; + if (globals.length > 0) { + parts[parts.length] = "let {"; + for (let i = 0; i < globals.length; i++) { + if (i > 0) parts[parts.length] = ","; + parts[parts.length] = globals[i]; + } + parts[parts.length] = "} = arguments[0];"; + } + parts[parts.length] = src; + if (wrap) parts[parts.length] = "\n})(arguments[0])"; + + const res = compile(string.stringBuild(parts)); + return res; + } + } + + func.setCallable(Function, true); + func.setConstructable(Function, true); + + return Function as any as typeof Function & ((value?: unknown) => (...args: any[]) => any); +})(); diff --git a/src/lib/libs/map.ts b/src/lib/libs/map.ts new file mode 100644 index 0000000..30cb355 --- /dev/null +++ b/src/lib/libs/map.ts @@ -0,0 +1,61 @@ +import { func, map } from "./primordials.ts"; +import { Symbol } from "./symbol.ts"; + +const mapKey: unique symbol = Symbol("Map.impl") as any; + +export class Map { + private [mapKey]: InstanceType; + + public get(key: K): V { + return this[mapKey].get(key); + } + public has(key: K): boolean { + return this[mapKey].has(key); + } + public set(key: K, val: V) { + this[mapKey].set(key, val); + return this; + } + public delete(key: K): boolean { + if (!this[mapKey].has(key)) return false; + else { + this[mapKey].delete(key); + return true; + } + } + + public keys(): K[] { + return this[mapKey].keys(); + } + public values(): V[] { + const res = this[mapKey].keys(); + for (let i = 0; i < res.length; i++) { + res[i] = this[mapKey].get(res[i]); + } + return res; + } + public entries(): [K, V][] { + const res = this[mapKey].keys(); + for (let i = 0; i < res.length; i++) { + res[i] = [res[i], this[mapKey].get(res[i])]; + } + return res; + } + + public [Symbol.iterator](): Iterator<[K, V]> { + return func.invoke(Array.prototype[Symbol.iterator as any], this.entries(), []) as any; + } + + public constructor(iterable?: Iterable<[K, V]>) { + const _map = this[mapKey] = new map(); + + if (iterable != null) { + const it = (iterable as any)[Symbol.iterator](); + for (let val = it.next(); !val.done; val = it.next()) { + _map.set(val.value[0], val.value[1]); + } + } + } +} + +func.setCallable(Map, false); diff --git a/src/lib/libs/number.ts b/src/lib/libs/number.ts new file mode 100644 index 0000000..e0a03ef --- /dev/null +++ b/src/lib/libs/number.ts @@ -0,0 +1,77 @@ +import { func, number, object } from "./primordials.ts"; +import { unwrapThis, valueKey } from "./utils.ts"; + +export const Number = (() => { + class Number { + [valueKey]!: number; + + public toString() { + return "" + unwrapThis(this, "number", Number, "Number.prototype.toString"); + } + public valueOf() { + return unwrapThis(this, "number", Number, "Number.prototype.toString"); + } + + public constructor (value?: unknown) { + if (func.invokeType(arguments, this) === "call") { + if (arguments.length === 0) return 0 as any; + else return +(value as any) as any; + } + this[valueKey] = (Number as any)(value); + } + + public static isFinite(value: number) { + value = unwrapThis(value, "number", Number, "Number.isFinite", "value"); + if (value === undefined || value !== value) return false; + if (value === Infinity || value === -Infinity) return false; + return true; + } + public static isInteger(value: number) { + value = unwrapThis(value, "number", Number, "Number.isInteger", "value"); + if (value === undefined) return false; + return number.parseInt(value) === value; + } + public static isNaN(value: number) { + return number.isNaN(value); + } + public static isSafeInteger(value: number) { + value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value"); + if (value === undefined || number.parseInt(value) !== value) return false; + return value >= -9007199254740991 && value <= 9007199254740991; + } + public static parseFloat(value: unknown) { + if (typeof value === "number") return value; + else return number.parseFloat(value + ""); + } + public static parseInt(value: unknown, radix = 10) { + radix = +radix; + if (number.isNaN(radix)) radix = 10; + + if (typeof value === "number") return number.parseInt(value, radix); + else return number.parseInt(value + "", radix); + } + + public static readonly EPSILON: number; + public static readonly MIN_SAFE_INTEGER: number; + public static readonly MAX_SAFE_INTEGER: number; + public static readonly POSITIVE_INFINITY: number; + public static readonly NEGATIVE_INFINITY: number; + public static readonly NaN: number; + public static readonly MAX_VALUE: number; + public static readonly MIN_VALUE: number; + } + + object.defineField(Number, "EPSILON", false, false, false, 2.220446049250313e-16); + object.defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991); + object.defineField(Number, "MAX_SAFE_INTEGER", false, false, false, 9007199254740991); + object.defineField(Number, "POSITIVE_INFINITY", false, false, false, +number.Infinity); + object.defineField(Number, "NEGATIVE_INFINITY", false, false, false, -number.Infinity); + object.defineField(Number, "NaN", false, false, false, number.NaN); + object.defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308); + object.defineField(Number, "MIN_VALUE", false, false, false, 5e-324); + func.setCallable(Number, true); + func.setConstructable(Number, true); + + return Number as any as typeof Number & ((value?: unknown) => number); +})(); +export type Number = InstanceType; diff --git a/src/lib/libs/object.ts b/src/lib/libs/object.ts new file mode 100644 index 0000000..127b247 --- /dev/null +++ b/src/lib/libs/object.ts @@ -0,0 +1,90 @@ +import { Boolean } from "./boolean.ts"; +import { TypeError } from "./errors.ts"; +import { Number } from "./number.ts"; +import { func, object } from "./primordials.ts"; +import { String } from "./string.ts"; +import { Symbol } from "./symbol.ts"; +import { valueKey } from "./utils.ts"; + +export const Object = (() => { + class Object { + public toString(this: unknown) { + if (this === undefined) return "[object Undefined]"; + else if (this === null) return "[object Null]"; + else if (typeof this === "object") { + if (Symbol.toStringTag in this) return "[object " + this[Symbol.toStringTag] + "]"; + else return "[object Object]"; + } + else if (typeof this === "number" || this instanceof Object) return "[object Object]"; + else if (typeof this === "symbol" || this instanceof Symbol) return "[object Symbol]"; + else if (typeof this === "string" || this instanceof String) return "[object String]"; + else if (typeof this === "boolean" || this instanceof Boolean) return "[object Boolean]"; + else if (typeof this === "function") return "[object Function]"; + } + public valueOf() { + return this; + } + + public constructor (value?: unknown) { + if (typeof value === 'object' && value !== null) return value as any; + if (typeof value === 'string') return new String(value) as any; + if (typeof value === 'number') return new Number(value) as any; + if (typeof value === 'boolean') return new Boolean(value) as any; + if (typeof value === 'symbol') { + var res: Symbol = {} as any; + object.setPrototype(res, Symbol.prototype); + res[valueKey] = value; + return res as any; + } + + return {} as any; + } + + public static defineProperty(obj: object, key: string | symbol, desc: PropertyDescriptor) { + if (obj === null || typeof obj !== "function" && typeof obj !== "object") throw new TypeError("Object.defineProperty called on non-object"); + if (desc === null || typeof desc !== "function" && typeof desc !== "object") throw new TypeError("Property description must be an object: " + desc); + if ("get" in desc || "set" in desc) { + var get = desc.get, set = desc.set; + + if (get !== undefined && typeof get !== "function") throw new TypeError("Getter must be a function: " + get); + if (set !== undefined && typeof set !== "function") throw new TypeError("Setter must be a function: " + set); + if ("value" in desc || "writable" in desc) { + throw new TypeError("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"); + } + if (!object.defineProperty(obj, key, !!desc.enumerable, !!desc.configurable, get, set)) { + throw new TypeError("Cannot redefine property: " + String(key)); + } + } + else if (!object.defineField(obj, key, !!desc.writable, !!desc.enumerable, !!desc.configurable, desc.value)) { + throw new TypeError("Cannot redefine property: " + String(key)); + } + + return obj; + } + public static defineProperties(obj: object, desc: PropertyDescriptorMap) { + const keys = object.getOwnMembers(obj, false); + const symbols = object.getOwnSymbolMembers(obj, false); + + for (let i = 0; i < keys.length; i++) { + Object.defineProperty(obj, keys[i], desc[keys[i]]); + } + for (let i = 0; i < symbols.length; i++) { + Object.defineProperty(obj, symbols[i], desc[symbols[i]]); + } + + return obj; + } + public static create(proto: object, desc?: PropertyDescriptorMap) { + let res = object.setPrototype({}, proto); + if (desc != null) this.defineProperties(res, desc); + + return res; + } + } + + func.setCallable(Object, true); + func.setConstructable(Object, true); + + return Object as any as typeof Object & ((value?: unknown) => object); +})(); +export type Object = InstanceType; diff --git a/src/lib/libs/polyfills/callSuper.js b/src/lib/libs/polyfills/callSuper.js new file mode 100644 index 0000000..5cfc655 --- /dev/null +++ b/src/lib/libs/polyfills/callSuper.js @@ -0,0 +1,5 @@ +import { func, object } from "../primordials.ts"; + +export default function _callSuper(self, constr, args) { + return func.construct(object.getPrototype(constr), func.target(1), args || []); +} diff --git a/src/lib/libs/polyfills/classCallCheck.js b/src/lib/libs/polyfills/classCallCheck.js new file mode 100644 index 0000000..0290a6f --- /dev/null +++ b/src/lib/libs/polyfills/classCallCheck.js @@ -0,0 +1,5 @@ +import { func } from "../primordials.ts"; + +export default function _classCallCheck() { + if (func.invokeTypeInfer() !== "new") throw new TypeError("Cannot call a class as a function"); +} \ No newline at end of file diff --git a/src/lib/libs/polyfills/createClass.js b/src/lib/libs/polyfills/createClass.js new file mode 100644 index 0000000..9f9e7a5 --- /dev/null +++ b/src/lib/libs/polyfills/createClass.js @@ -0,0 +1,25 @@ +import { object } from "../primordials.ts"; + +function _defineProperties(target, arr) { + if (!arr) return; + for (var t = 0; t < arr.length; t++) { + var desc = arr[t]; + + if ("value" in desc) { + object.defineField(target, desc.key, desc.writable || true, desc.enumerable || false, desc.configurable || true, desc.value); + } + else { + object.defineProperty(target, desc.key, desc.enumerable || false, desc.configurable || true, desc.get, desc.set); + } + } +} + +/* __#PURE__ */ +export default function _createClass(clazz, instance, nonInstance) { + if (instance) { + _defineProperties(clazz.prototype, instance); + _defineProperties(clazz, nonInstance); + } + + return clazz; +} \ No newline at end of file diff --git a/src/lib/libs/polyfills/defineProperty.js b/src/lib/libs/polyfills/defineProperty.js new file mode 100644 index 0000000..14cb53b --- /dev/null +++ b/src/lib/libs/polyfills/defineProperty.js @@ -0,0 +1,7 @@ +import { object } from "../primordials.ts"; + +export default function _defineProperty(obj, key, val) { + if (obj == null) return; + object.defineField(obj, key, true, true, true, val); + return obj; +} diff --git a/src/lib/libs/polyfills/getPrototypeOf.js b/src/lib/libs/polyfills/getPrototypeOf.js new file mode 100644 index 0000000..7ff675f --- /dev/null +++ b/src/lib/libs/polyfills/getPrototypeOf.js @@ -0,0 +1,5 @@ +import { object } from "../primordials.ts"; + +export default function _getPrototypeOf(obj) { + return object.getPrototype(obj) || null; +} \ No newline at end of file diff --git a/src/lib/libs/polyfills/inherits.js b/src/lib/libs/polyfills/inherits.js new file mode 100644 index 0000000..babaf4f --- /dev/null +++ b/src/lib/libs/polyfills/inherits.js @@ -0,0 +1,11 @@ +import { object } from "../primordials.ts"; + +export default function _inherits(t, e) { + if (e == null) { + object.setPrototype(t.prototype, undefined); + } + else { + object.setPrototype(t.prototype, e.prototype); + object.setPrototype(t, e); + } +} \ No newline at end of file diff --git a/src/lib/libs/polyfills/possibleConstructorReturn.js b/src/lib/libs/polyfills/possibleConstructorReturn.js new file mode 100644 index 0000000..69ed02f --- /dev/null +++ b/src/lib/libs/polyfills/possibleConstructorReturn.js @@ -0,0 +1,2 @@ +export default function _possibleConstructorReturn() { +} \ No newline at end of file diff --git a/src/lib/libs/polyfills/setPrototypeOf.js b/src/lib/libs/polyfills/setPrototypeOf.js new file mode 100644 index 0000000..63a984b --- /dev/null +++ b/src/lib/libs/polyfills/setPrototypeOf.js @@ -0,0 +1,5 @@ +import { object } from "../primordials"; + +export default function _setPrototypeOf(obj, proto) { + object.setPrototype(obj, proto); +} \ No newline at end of file diff --git a/src/lib/libs/polyfills/typeof.js b/src/lib/libs/polyfills/typeof.js new file mode 100644 index 0000000..ee978fc --- /dev/null +++ b/src/lib/libs/polyfills/typeof.js @@ -0,0 +1,3 @@ +export default function _typeof(val) { + return typeof val; +} \ No newline at end of file diff --git a/src/lib/libs/primordials.ts b/src/lib/libs/primordials.ts new file mode 100644 index 0000000..4b784ed --- /dev/null +++ b/src/lib/libs/primordials.ts @@ -0,0 +1,82 @@ +export interface SymbolPrimordials { + makeSymbol(name: string): symbol; + getSymbol(name: string): symbol; + getSymbolKey(symbol: symbol): string | undefined; + getSymbolDescription(symbol: symbol): string; +} +export interface NumberPrimordials { + parseInt(raw: string | number, radix?: number): number; + parseFloat(raw: string | number): number; + isNaN(num: number): boolean; + NaN: number; + Infinity: number; +} +export interface StringPrimordials { + stringBuild(parts: string[]): string; + fromCharCode(char: number): string; + fromCodePoint(char: number): string; +} +export interface ObjectPrimordials { + defineProperty(obj: object, key: string | number | symbol, enumerable: boolean, configurable: boolean, get?: Function, set?: Function): boolean; + defineField(obj: object, key: string | number | symbol, writable: boolean, enumerable: boolean, configurable: boolean, value: any): boolean; + getOwnMember(): any; + getOwnSymbolMember(): any; + getOwnMembers(obj: object, onlyEnumerable: boolean): string[]; + getOwnSymbolMembers(obj: object, onlyEnumerable: boolean): symbol[]; + getPrototype(obj: object): object | undefined; + setPrototype(obj: object, proto?: object): object; + isArray(obj: any[]): boolean; +} +export interface FunctionPrimordials { + invokeType(args: IArguments, self: any): "new" | "call"; + invokeTypeInfer(): "new" | "call"; + target(): Function | null | undefined; + setConstructable(func: Function, flag: boolean): void; + setCallable(func: Function, flag: boolean): void; + invoke(func: Function, self: any, args: any[]): void; + construct(func: Function, self: any, args: any[]): void; +} +export interface JSONPrimordials { + parse(data: string): any; + stringify(data: any): string; +} + +export interface Primordials { + symbol: SymbolPrimordials; + number: NumberPrimordials; + string: StringPrimordials; + object: ObjectPrimordials; + function: FunctionPrimordials; + json: JSONPrimordials; + map: new () => { + get(key: any): any; + has(key: any): boolean; + set(key: any, val: any): void; + delete(key: any): void; + keys(): any[]; + }; + regex: new (source: string) => { + exec(target: string, offset: number, indices: false): { + + }; + }; + compile(src: string): Function; + setGlobalPrototypes(prototype: Record): void; +} + +globalThis.undefined = void 0; +export const target = (globalThis as any).target; +export const primordials: Primordials = (globalThis as any).primordials; + +export const { + symbol, + number, + string, + object, + function: func, + json, + map, + regex, + setGlobalPrototypes, + compile, +} = primordials; diff --git a/src/lib/libs/regex.ts b/src/lib/libs/regex.ts new file mode 100644 index 0000000..4fed215 --- /dev/null +++ b/src/lib/libs/regex.ts @@ -0,0 +1,54 @@ +import { func, regex } from "./primordials.ts"; +import { String } from "./string.ts"; +import { Symbol } from "./symbol.ts"; + +const regexKey: unique symbol = Symbol("RegExp.impl") as any; + +export class RegExp { + private [regexKey]: InstanceType; + + public readonly source: string; + public readonly flags: string; + public lastIndex = 0; + + public constructor(source: any, flags: string) { + source = this.source = String(typeof source === "object" && "source" in source ? source.source : source); + flags = String(flags); + + const _regex = this[regexKey] = new regex(source); + + let indices = false; + let global = false; + let ignoreCase = false; + let multiline = false; + let dotall = false; + let unicode = false; + let unicodeSets = false; + let sticky = false; + + for (let i = 0; i < flags.length; i++) { + switch (flags[i]) { + case "d": indices = true; break; + case "g": global = true; break; + case "i": ignoreCase = true; break; + case "m": multiline = true; break; + case "s": dotall = true; break; + case "u": unicode = true; break; + case "v": unicodeSets = true; break; + case "y": sticky = true; break; + } + } + + flags = ""; + if (indices) flags += "d"; + if (global) flags += "g"; + if (ignoreCase) flags += "i"; + if (multiline) flags += "m"; + if (dotall) flags += "s"; + if (unicode) flags += "u"; + if (unicodeSets) flags += "v"; + if (sticky) flags += "y"; + this.flags = flags; + } +} +func.setCallable(RegExp, false); diff --git a/src/lib/libs/string.ts b/src/lib/libs/string.ts new file mode 100644 index 0000000..5aae40a --- /dev/null +++ b/src/lib/libs/string.ts @@ -0,0 +1,84 @@ +import { func, string } from "./primordials.ts"; +import { Symbol } from "./symbol.ts"; +import { unwrapThis, valueKey } from "./utils.ts"; + +export const String = (() => { + class String { + [valueKey]!: string; + + public at(index: number) { + throw "Not implemented :/"; + return unwrapThis(this, "string", String, "String.prototype.at")[index]; + }; + public toString() { + return unwrapThis(this, "string", String, "String.prototype.toString"); + } + public valueOf() { + return unwrapThis(this, "string", String, "String.prototype.valueOf"); + } + + // public split(val: string) { + // const res: string[] = []; + + // while (true) { + // val.indexOf(); + // } + // } + + public [Symbol.iterator]() { + var i = 0; + var arr: string | undefined = unwrapThis(this, "string", String, "String.prototype[Symbol.iterator]"); + + return { + next () { + if (arr == null) return { done: true, value: undefined }; + if (i > arr.length) { + arr = undefined; + return { done: true, value: undefined }; + } + else { + var val = arr[i++]; + if (i >= arr.length) arr = undefined; + return { done: false, value: val }; + } + }, + [Symbol.iterator]() { return this; } + }; + } + + public constructor (value?: unknown) { + if (func.invokeType(arguments, this) === "call") { + if (arguments.length === 0) return "" as any; + else if (typeof value === "symbol") return value.toString() as any; + else return (value as any) + "" as any; + } + this[valueKey] = (String as any)(value); + } + + public static fromCharCode(...args: number[]) { + const res: string[] = []; + res[arguments.length] = ""; + + for (let i = 0; i < arguments.length; i++) { + res[i] = string.fromCharCode(+arguments[i]); + } + + return string.stringBuild(res); + } + public static fromCodePoint(...args: number[]) { + const res: string[] = []; + res[arguments.length] = ""; + + for (var i = 0; i < arguments.length; i++) { + res[i] = string.fromCodePoint(+arguments[i]); + } + return string.stringBuild(res); + } + } + + func.setCallable(String, true); + func.setConstructable(String, true); + + return String as any as typeof String & ((value?: unknown) => string); +})(); +export type String = InstanceType; diff --git a/src/lib/libs/symbol.ts b/src/lib/libs/symbol.ts new file mode 100644 index 0000000..bb84c8d --- /dev/null +++ b/src/lib/libs/symbol.ts @@ -0,0 +1,52 @@ +import { func, object, symbol } from "./primordials.ts"; +import { unwrapThis, valueKey } from "./utils.ts"; + +export const Symbol = (() => { + class Symbol { + [valueKey]!: symbol; + + get description() { + return symbol.getSymbolDescription(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description")); + } + + public toString() { + return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")"; + } + public valueOf() { + return unwrapThis(this, "symbol", Symbol, "Symbol.prototype.valueOf"); + } + + public constructor(name = "") { + return symbol.makeSymbol(name + "") as any; + } + + public static for(name: string) { + return symbol.getSymbol(name + ""); + } + + public static readonly asyncIterator: unique symbol; + public static readonly iterator: unique symbol; + public static readonly match: unique symbol; + public static readonly matchAll: unique symbol; + public static readonly replace: unique symbol; + public static readonly search: unique symbol; + public static readonly split: unique symbol; + public static readonly toStringTag: unique symbol; + }; + + func.setCallable(Symbol, true); + func.setConstructable(Symbol, false); + + object.defineField(Symbol, "asyncIterator", false, false, false, symbol.getSymbol("Symbol.asyncIterator")); + object.defineField(Symbol, "iterator", false, false, false, symbol.getSymbol("Symbol.iterator")); + object.defineField(Symbol, "match", false, false, false, symbol.getSymbol("Symbol.match")); + object.defineField(Symbol, "matchAll", false, false, false, symbol.getSymbol("Symbol.matchAll")); + object.defineField(Symbol, "replace", false, false, false, symbol.getSymbol("Symbol.replace")); + object.defineField(Symbol, "search", false, false, false, symbol.getSymbol("Symbol.search")); + object.defineField(Symbol, "split", false, false, false, symbol.getSymbol("Symbol.split")); + object.defineField(Symbol, "toStringTag", false, false, false, symbol.getSymbol("Symbol.toStringTag")); + + return Symbol as any as typeof Symbol & ((name?: string) => symbol); +})(); +export type Symbol = InstanceType; + diff --git a/src/lib/libs/utils.ts b/src/lib/libs/utils.ts new file mode 100644 index 0000000..d2737f9 --- /dev/null +++ b/src/lib/libs/utils.ts @@ -0,0 +1,21 @@ +import { symbol } from "./primordials.ts"; + +export const valueKey: unique symbol = symbol.makeSymbol("Primitive.value") as any; + +export interface TypeMap { + undefined: undefined; + boolean: boolean; + string: string; + number: number; + symbol: symbol; + object: null | object; + function: Function; +} + +export function unwrapThis(self: any, type: T, constr: Function, name: string, arg = "this", defaultVal?: TypeMap[T]): TypeMap[T] { + if (typeof self === type) return self; + if (self instanceof constr && valueKey in self) self = (self as any)[valueKey]; + if (typeof self === type) return self; + if (defaultVal !== undefined) return defaultVal; + throw new TypeError(name + " requires that '" + arg + "' be a " + constr.name); +} \ No newline at end of file diff --git a/src/lib/ts/_entry.ts b/src/lib/ts/_entry.ts new file mode 100644 index 0000000..74f8845 --- /dev/null +++ b/src/lib/ts/_entry.ts @@ -0,0 +1,106 @@ +import { createDocumentRegistry, createLanguageService, ModuleKind, ScriptSnapshot, ScriptTarget, type Diagnostic, type CompilerOptions, type IScriptSnapshot, flattenDiagnosticMessageText, CompilerHost } from "typescript"; + +declare function getResource(name: string): string; +declare function print(...args: any[]): void; +declare function register(factory: CompilerFactory): void; + +type CompilerFactory = (next: Compiler) => Compiler; +type Compiler = (filename: string, src: string, maps: any[]) => Function; + +const resources: Record = {}; + +function resource(name: string) { + if (name in resources) return resources[name]; + else return resources[name] = getResource(name); +} + +register(next => { + const files: Record = {}; + const versions: Record = {}; + let declI = 0; + + const settings: CompilerOptions = { + target: ScriptTarget.ESNext, + module: ModuleKind.Preserve, + + allowImportingTsExtensions: true, + verbatimModuleSyntax: true, + + strict: false, + skipLibCheck: true, + forceConsistentCasingInFileNames: true, + declaration: true, + }; + + const service = createLanguageService({ + getCurrentDirectory: () => "/", + getDefaultLibFileName: () => "/lib.d.ts", + getScriptFileNames: () => { + const res = ["/src.ts", "/lib.d.ts"]; + for (let i = 0; i < declI; i++) res.push("/src." + i + ".d.ts"); + return res; + }, + getCompilationSettings: () => settings, + log: print, + fileExists: filename => filename in files || resource(filename) != null, + + getScriptSnapshot: (filename) => { + if (filename in files) return files[filename]; + else { + const src = resource(filename); + if (src == null) return undefined; + return files[filename] = ScriptSnapshot.fromString(src); + } + }, + getScriptVersion: (filename) => String(versions[filename] || 0), + + readFile: () => { throw "no"; }, + writeFile: () => { throw "no"; }, + }, createDocumentRegistry()); + + service.getEmitOutput("/lib.d.ts"); + print("Loaded typescript..."); + + return (code, filename, mapChain) => { + files["/src.ts"] = ScriptSnapshot.fromString(code); + versions["/src.ts"] ??= 0; + versions["/src.ts"]++; + + const emit = service.getEmitOutput("/src.ts"); + + const diagnostics = new Array() + .concat(service.getCompilerOptionsDiagnostics()) + .concat(service.getSyntacticDiagnostics("/src.ts")) + .concat(service.getSemanticDiagnostics("/src.ts")) + .map(function (diagnostic) { + const message = flattenDiagnosticMessageText(diagnostic.messageText, "\n"); + + if (diagnostic.file != null) { + let file = diagnostic.file.fileName.substring(1); + if (file === "src.ts") file = filename; + + if (diagnostic.start == null) return file; + + const pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); + return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message; + } + else return message; + }); + + if (diagnostics.length > 0) { + throw new SyntaxError(diagnostics.join("\n")); + } + + var map = JSON.parse(emit.outputFiles[0].text); + var result = emit.outputFiles[1].text; + var declaration = emit.outputFiles[2].text; + + var compiled = next(result, filename, mapChain.concat(map)); + + return function (this: any) { + const res = compiled.apply(this, arguments); + if (declaration !== '') files["/src." + declI++ + ".d.ts"] = ScriptSnapshot.fromString(declaration); + return res; + }; + }; +}); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index e240316..0b3cd79 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -16,7 +16,6 @@ import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.common.json.JSON; import me.topchetoeu.jscript.common.parsing.Filename; -import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; @@ -298,6 +297,9 @@ public class SimpleRepl { res.defineOwnMember(env, "getOwnSymbolMembers", new NativeFunction(args -> { return ArrayValue.of(args.get(0).getOwnSymbolMembers(env, args.get(1).toBoolean())); })); + res.defineOwnMember(env, "isArray", new NativeFunction(args -> { + return BoolValue.of(args.get(0) instanceof ArrayValue); + })); return res; } @@ -320,6 +322,16 @@ public class SimpleRepl { if (((ArgumentsValue)args.get(0)).frame.isNew) return StringValue.of("new"); else return StringValue.of("call"); })); + res.defineOwnMember(env, "invokeTypeInfer", new NativeFunction(args -> { + var frame = Frame.get(args.env, args.get(0).toNumber(args.env).getInt()); + if (frame.isNew) return StringValue.of("new"); + else return StringValue.of("call"); + })); + res.defineOwnMember(env, "target", new NativeFunction(args -> { + var frame = Frame.get(args.env, args.get(0).toNumber(args.env).getInt()); + if (frame.target == null) return Value.UNDEFINED; + else return frame.target; + })); res.defineOwnMember(env, "invoke", new NativeFunction(args -> { var func = (FunctionValue)args.get(0); @@ -330,9 +342,11 @@ public class SimpleRepl { })); res.defineOwnMember(env, "construct", new NativeFunction(args -> { var func = (FunctionValue)args.get(0); - var funcArgs = (ArrayValue)args.get(1); + var target = args.get(1); + var funcArgs = (ArrayValue)args.get(2); - return func.constructNoSelf(env, funcArgs.toArray()); + if (target == Value.UNDEFINED) return func.constructNoSelf(env, funcArgs.toArray()); + else return func.construct(env, target, funcArgs.toArray()); })); return res; @@ -388,9 +402,9 @@ public class SimpleRepl { setProto(args.env, env, Value.SYNTAX_ERR_PROTO, obj, "syntax"); setProto(args.env, env, Value.TYPE_ERR_PROTO, obj, "type"); setProto(args.env, env, Value.RANGE_ERR_PROTO, obj, "range"); - var val = obj.getMember(env, "regex"); + var val = obj.getMember(args.env, "regex"); if (val instanceof FunctionValue func) { - args.env.add(Value.REGEX_CONSTR, func); + env.add(Value.REGEX_CONSTR, func); } return Value.UNDEFINED; })); @@ -409,13 +423,29 @@ public class SimpleRepl { return res; } - private static void initEnv() { - environment.add(EventLoop.KEY, engine); - environment.add(DebugContext.KEY, new DebugContext()); - environment.add(Compiler.KEY, Compiler.DEFAULT); + private static Environment createESEnv() throws InterruptedException, ExecutionException { + var env = initEnv(); + var stubEnv = initEnv(); + Value.global(stubEnv).defineOwnMember(stubEnv, "target", Value.global(env)); + Value.global(stubEnv).defineOwnMember(stubEnv, "primordials", primordials(env)); + + EventLoop.get(stubEnv).pushMsg( + false, stubEnv, + Filename.parse("jscript://init.js"), Reading.resourceToString("lib/index.js"), + Value.UNDEFINED + ).get(); + + return env; + } + + private static Environment initEnv() { + var env = new Environment(); + env.add(EventLoop.KEY, engine); + env.add(DebugContext.KEY, new DebugContext()); + env.add(Compiler.KEY, Compiler.DEFAULT); // environment.add(CompileResult.DEBUG_LOG); - var glob = Value.global(environment); + var glob = Value.global(env); glob.defineOwnMember(null, "exit", new NativeFunction("exit", args -> { Thread.currentThread().interrupt(); @@ -439,20 +469,26 @@ public class SimpleRepl { return Value.UNDEFINED; })); + + return env; } private static void initEngine() { engineTask = engine.start(); } private static void initGlobals() throws InterruptedException, ExecutionException { - EventLoop.get(environment).pushMsg( - false, environment, - Filename.parse("jscript://init.js"), Reading.resourceToString("lib/index.js"), - Value.UNDEFINED, Value.global(environment), primordials(environment) - ).get(); - EventLoop.get(environment).pushMsg( - false, environment, - Filename.parse("jscript://ts.js"), Reading.resourceToString("lib/ts.js"), - Value.UNDEFINED + environment = createESEnv(); + var tsEnv = createESEnv(); + var res = new FunctionValue[1]; + var setter = new NativeFunction(args -> { + res[0] = (FunctionValue)args.get(0); + return Value.UNDEFINED; + }); + + var ts = Reading.resourceToString("lib/ts.js"); + if (ts != null) EventLoop.get(tsEnv).pushMsg( + false, tsEnv, + Filename.parse("jscript://ts.js"), ts, + Value.UNDEFINED, setter ).get(); } @@ -460,7 +496,7 @@ public class SimpleRepl { SimpleRepl.args = args; var reader = new Thread(SimpleRepl::reader); - initEnv(); + environment = initEnv(); initEngine(); reader.setDaemon(true);