diff --git a/src/lib/libs/_entry.ts b/src/lib/libs/_entry.ts index 7932b68..b330028 100644 --- a/src/lib/libs/_entry.ts +++ b/src/lib/libs/_entry.ts @@ -1,21 +1,25 @@ import { object, setGlobalPrototypes, target } from "./primordials.ts"; -import { Error, RangeError, SyntaxError, TypeError } from "./errors.ts"; -import { Boolean } from "./boolean.ts"; -import { Function } from "./function.ts"; -import { Number } from "./number.ts"; -import { Object } from "./object.ts"; -import { String } from "./string.ts"; -import { Symbol } from "./symbol.ts"; -import { Array } from "./array.ts"; -import { Map, WeakMap } from "./map.ts"; -import { RegExp } from "./regex.ts"; -import { Date } from "./date.ts"; -import { Math as _Math } from "./math.ts"; -import { Set, WeakSet } from "./set.ts"; -import { JSON } from "./json.ts"; -import { console } from "./console.ts"; +import { Error, RangeError, SyntaxError, TypeError } from "./values/errors.ts"; +import { Boolean } from "./values/boolean.ts"; +import { Function } from "./values/function.ts"; +import { Number } from "./values/number.ts"; +import { Object } from "./values/object.ts"; +import { String } from "./values/string.ts"; +import { Symbol } from "./values/symbol.ts"; +import { Array } from "./values/array.ts"; +import { Map, WeakMap } from "./classes/map.ts"; +import { RegExp } from "./values/regex.ts"; +import { Date } from "./classes/date.ts"; +import { Math as _Math } from "./namespaces/math.ts"; +import { Set, WeakSet } from "./classes/set.ts"; +import { JSON } from "./namespaces/json.ts"; +import { console } from "./namespaces/console.ts"; import { encodeURI, encodeURIComponent } from "./url.ts"; -import { Promise } from "./promise.ts"; +import { Promise } from "./classes/promise.ts"; +import { ArrayBuffer } from "./arrays/ArrayBuffer.ts"; +import { Uint8Array } from "./arrays/Uint8Array.ts"; +import { Int32Array } from "./arrays/Int32Array.ts"; +import { TypedArray } from "./arrays/TypedArray.ts"; declare global { function print(...args: any[]): void; @@ -46,6 +50,11 @@ target.RangeError = RangeError; target.SyntaxError = SyntaxError; target.TypeError = TypeError; +fixup(TypedArray); +target.ArrayBuffer = fixup(ArrayBuffer); +target.Uint8Array = Uint8Array; +target.Int32Array = Int32Array; + target.Map = fixup(Map); target.WeakMap = fixup(WeakMap); target.Set = fixup(Set); @@ -56,7 +65,6 @@ target.Promise = fixup(Promise); target.Math = object.setPrototype(_Math, Object.prototype); target.JSON = object.setPrototype(JSON, Object.prototype); target.console = object.setPrototype(console, Object.prototype); -target.TYPED_ARRAY_SUPPORT = false; target.parseInt = Number.parseInt; target.parseFloat = Number.parseFloat; @@ -77,5 +85,7 @@ setGlobalPrototypes({ syntax: SyntaxError.prototype, range: RangeError.prototype, type: TypeError.prototype, + uint8: Uint8Array.prototype, + int32: Int32Array.prototype, regex: RegExp, }); diff --git a/src/lib/libs/arrays/ArrayBuffer.ts b/src/lib/libs/arrays/ArrayBuffer.ts new file mode 100644 index 0000000..0474256 --- /dev/null +++ b/src/lib/libs/arrays/ArrayBuffer.ts @@ -0,0 +1,30 @@ +import { buffer, type InternalBuffer, map, symbol } from "../primordials.ts"; + +export const abs = new map(true); +export const abKey: unique symbol = symbol.getSymbol("ArrayBuffer.impl") as any; + +export class ArrayBuffer { + public [abKey]!: InternalBuffer; + + public get byteLength() { + return this[abKey].length; + } + public get byteOffset() { + return 0; + } + + public constructor(val: unknown) { + if (buffer.isBuff(val)) this[abKey] = val; + else this[abKey] = buffer.buff(Number(val)); + } +} + +export function getAB(buff: InternalBuffer): ArrayBuffer { + let res = abs.get(buff); + if (res == null) { + res = new ArrayBuffer(buff); + abs.set(buff, res); + } + + return res; +} diff --git a/src/lib/libs/arrays/Int32Array.ts b/src/lib/libs/arrays/Int32Array.ts new file mode 100644 index 0000000..a99f165 --- /dev/null +++ b/src/lib/libs/arrays/Int32Array.ts @@ -0,0 +1,25 @@ +import { buffer } from "../primordials.ts"; +import { abstractIgnore, TypedArray, typedArrayFuncs } from "./TypedArray.ts"; + +const factory = buffer.int32; +const funcs = typedArrayFuncs(4, factory); + +export class Int32Array extends TypedArray { + public subarray(this: number[], start?: number, end?: number) { + return funcs.subarray(this, start, end); + } + public slice(this: any[], start?: number, end?: number) { + return funcs.slice(this, start, end); + } + public map(this: any[], cb: (val: number, i: number, self: any) => number, self?: any) { + return funcs.map(this, cb, self); + } + public filter(this: any[], cb: (val: number, i: number, self: any) => boolean, self?: any) { + return funcs.filter(this, cb, self); + } + + public constructor(obj: any, start?: number, end?: number) { + super(abstractIgnore); + return funcs.construct(obj, start, end) as any; + } +} \ No newline at end of file diff --git a/src/lib/libs/arrays/TypedArray.ts b/src/lib/libs/arrays/TypedArray.ts new file mode 100644 index 0000000..44cc853 --- /dev/null +++ b/src/lib/libs/arrays/TypedArray.ts @@ -0,0 +1,220 @@ +import { buffer, func, type InternalBuffer, object, string, symbol } from "../primordials.ts"; +import { symbols, wrapI } from "../utils.ts"; +import { Error, TypeError } from "../values/errors.ts"; +import { abKey, ArrayBuffer, getAB } from "./ArrayBuffer.ts"; + +export const abstractIgnore = symbol.getSymbol("TypedArray.abstractIgnore"); + +export function typedArrayFuncs(perEl: number, constructor: (buff: InternalBuffer, start: number, end: number) => number[]) { + return { + map(self: number[], cb: (val: number, i: number, self: number[]) => number, fnSelf: any) { + const res = constructor(buffer.buff(self.length * perEl), 0, self.length); + + for (let i = 0; i < self.length; i++) { + res[i] = func.invoke(cb, fnSelf, [self[i], i, self]); + } + + return res; + }, + filter(self: number[], cb: (val: number, i: number, self: number[]) => boolean, fnSelf: any) { + const bigger = constructor(buffer.buff(self.length * perEl), 0, self.length); + let j = 0; + + for (let i = 0; i < self.length; i++) { + if (func.invoke(cb, self, [self[i], i, fnSelf])) bigger[j++] = self[i]; + } + + const res = constructor(buffer.buff(j * perEl), 0, j); + for (let i = 0; i < j; i++) res[i] = bigger[i]; + return res; + }, + slice(self: number[], start = 0, end = self.length) { + start = wrapI(start, self.length); + end = wrapI(end, self.length); + + if (end <= start) return constructor(buffer.buff(0), 0, 0); + + const res = constructor(buffer.buff((end - start) * perEl), 0, end - start); + + for (let i = 0; i < end - start; i++) { + res[i] = self[start + i]; + } + + return res; + }, + subarray(self: number[], start = 0, end = self.length) { + start = wrapI(start, self.length); + end = wrapI(end, self.length); + + if (end <= start) return constructor(buffer.buff(0), 0, 0); + + const offset = buffer.start(self); + return constructor(buffer.backer(self), offset + start, offset + end); + }, + construct(self: ArrayBuffer | number[] | number | Iterable, start?: number, end?: number) { + if (typeof self === "number") { + return constructor(buffer.buff(self * perEl), 0, self); + } + if (self instanceof ArrayBuffer) { + const internal = self[abKey]; + if (start === undefined) start = 0; + if (end === undefined) end = (internal.length / perEl) | 0; + return constructor(internal, start, end); + } + if (symbols.iterator in self && typeof self[symbols.iterator] === "function") { + const arr: number[] = []; + let i = 0; + const gen: Iterator = self[symbols.iterator](); + + for (let it = gen.next(); !it.done; it = gen.next()) { + arr[i++] = Number(it.value); + } + + const res = constructor(buffer.buff(i * perEl), 0, i); + for (let j = 0; j < i; j++) res[j] = arr[j]; + return res; + } + + const res = constructor(buffer.buff((self as number[]).length * perEl), 0, (self as number[]).length); + for (let i = 0; i < (self as number[]).length; i++) res[i] = (self as number[])[i]; + return res; + }, + byteOffset(self: number[]) { + return buffer.start(self) * perEl; + }, + byteLength(self: number[]) { + return (buffer.end(self) - buffer.start(self)) * perEl; + }, + }; +} + +export class TypedArray { + public get buffer() { + return getAB(buffer.backer(this as any)); + } + public get byteOffset(): number { + throw new Error("abstract"); + } + public get byteLength(): number { + throw new Error("abstract"); + } + + public forEach(this: number[], cb: (val: number, 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 join(this: number[], delim = ",") { + delim = String(delim); + const parts = []; + if (delim) { + for (let i = 0; i < this.length; i++) { + if (i) parts[parts.length] = delim; + parts[parts.length] = (i in this) ? String(this[i]) : ""; + } + } + else { + for (let i = 0; i < this.length; i++) { + parts[i] = (i in this) ? String(this[i]) : ""; + } + } + + return string.stringBuild(parts); + } + + public subarray(this: number[], start = 0, end = this.length) { + throw new Error("'slice' is an abstract method"); + } + + public slice(this: number[], start = 0, end = this.length) { + throw new Error("'slice' is an abstract method"); + } + + public map(this: number[], cb: (val: number, i: number, self: this) => number, self?: any) { + throw new Error("'map' is an abstract method"); + } + public filter(this: number[], cb: (val: number, i: number, self: this) => boolean, self?: any) { + throw new Error("'filter' is an abstract method"); + } + public reduce(this: number[], cb: (a: number, b: number, i: number, self: number[]) => number, initial?: number) { + let i = 0; + if (arguments.length <= 1) initial = this[i++]; + + for (; i < this.length; i++) { + initial = cb(initial!, this[i], i, this); + } + + return initial; + } + public some(this: number[], cb: (val: number, i: number, self: number[]) => boolean, self?: any) { + for (let i = 0; i < this.length; i++) { + if (func.invoke(cb, self, [this[i], i, this])) return true; + } + + return false; + } + public every(this: number[], cb: (val: number, i: number, self: number[]) => boolean, self?: any) { + for (let i = 0; i < this.length; i++) { + if (!func.invoke(cb, self, [this[i], i, this])) return false; + } + + return true; + } + public find(this: number[], cb: (val: number, i: number, self: number[]) => boolean, self?: any) { + for (let i = 0; i < this.length; i++) { + if (func.invoke(cb, self, [this[i], i, this])) return this[i]; + } + + return undefined; + } + public indexOf(this: number[], val: number, start = 0) { + start |= 0; + if (start < 0) start = 0; + for (let i = start; i < this.length; i++) { + if (this[i] === val) return i; + } + + return -1; + } + public lastIndexOf(this: number[], val: number, start = 0) { + start |= 0; + if (start < 0) start = 0; + + for (let i = this.length - 1; i >= start; i--) { + if (this[i] === val) return i; + } + + return -1; + } + public includes(this: number[], val: number) { + for (let i = 0; i < this.length; i++) { + if (this[i] === val) return i; + } + + return false; + } + + public sort(this: number[], cb?: (a: number, b: number) => number) { + cb ||= (a, b) => a - b; + + return object.sort(this, cb); + } + public reverse(this: number[]) { + const mid = this.length >> 1; + const end = this.length - 1; + + for (let i = 0; i < mid; i++) { + const tmp = this[i]; + this[i] = this[end - i]; + this[end - i] = tmp; + } + + return this; + } + + public constructor(token?: typeof abstractIgnore) { + if (token !== abstractIgnore) { + throw new TypeError("TypedArray constructor can't be called"); + } + } +} \ No newline at end of file diff --git a/src/lib/libs/arrays/Uint8Array.ts b/src/lib/libs/arrays/Uint8Array.ts new file mode 100644 index 0000000..c40bf2e --- /dev/null +++ b/src/lib/libs/arrays/Uint8Array.ts @@ -0,0 +1,32 @@ +import { buffer } from "../primordials.ts"; +import { abstractIgnore, TypedArray, typedArrayFuncs } from "./TypedArray.ts"; + +const factory = buffer.uint8; +const funcs = typedArrayFuncs(1, factory); + +export class Uint8Array extends TypedArray { + public override get byteOffset() { + return funcs.byteOffset(this as any); + } + public override get byteLength() { + return funcs.byteLength(this as any); + } + + public override subarray(this: number[], start?: number, end?: number) { + return funcs.subarray(this, start, end); + } + public override slice(this: any[], start?: number, end?: number) { + return funcs.slice(this, start, end); + } + public override map(this: any[], cb: (val: number, i: number, self: any) => number, self?: any) { + return funcs.map(this, cb, self); + } + public override filter(this: any[], cb: (val: number, i: number, self: any) => boolean, self?: any) { + return funcs.filter(this, cb, self); + } + + public constructor(obj: any, start?: number, end?: number) { + super(abstractIgnore); + return funcs.construct(obj, start, end) as any; + } +} \ No newline at end of file diff --git a/src/lib/libs/primordials.ts b/src/lib/libs/primordials.ts index b042611..7699385 100644 --- a/src/lib/libs/primordials.ts +++ b/src/lib/libs/primordials.ts @@ -1,3 +1,10 @@ +const buffSymbol: unique symbol = undefined as any; + +export interface InternalBuffer { + length: number; + [buffSymbol]: "buffer"; +} + export interface SymbolPrimordials { makeSymbol(name: string): symbol; getSymbol(name: string): symbol; @@ -46,6 +53,22 @@ export interface ObjectPrimordials { memcpy(src: any[], dst: any[], srcI: number, dstI: number, n: number): void; sort(arr: any[], cb: Function): any[]; } +export interface BufferPrimordials { + buff(n: number): InternalBuffer; + backer(arr: number[]): InternalBuffer; + start(arr: number[]): number; + end(arr: number[]): number; + + uint8(buff: InternalBuffer, start: number, end: number): number[]; + int8(buff: InternalBuffer, start: number, end: number): number[]; + int32(buff: InternalBuffer, start: number, end: number): number[]; + + isUint8(val: any): val is number[]; + isInt8(val: any): val is number[]; + isInt32(val: any): val is number[]; + is(val: any): val is number[]; + isBuff(val: any): val is InternalBuffer; +} export interface FunctionPrimordials { invokeType(args: IArguments, self: any): "new" | "call"; invokeTypeInfer(): "new" | "call"; @@ -67,6 +90,7 @@ export interface Primordials { object: ObjectPrimordials; function: FunctionPrimordials; json: JSONPrimordials; + buffer: BufferPrimordials; map: new (weak?: boolean) => { get(key: any): any; has(key: any): boolean; @@ -97,6 +121,7 @@ export const { number, string, object, + buffer, function: func, json, map, diff --git a/src/main/java/me/topchetoeu/jscript/repl/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/repl/SimpleRepl.java index 3a03204..00ec699 100644 --- a/src/main/java/me/topchetoeu/jscript/repl/SimpleRepl.java +++ b/src/main/java/me/topchetoeu/jscript/repl/SimpleRepl.java @@ -36,8 +36,13 @@ import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; +import me.topchetoeu.jscript.runtime.values.objects.ArrayLikeValue; import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; +import me.topchetoeu.jscript.runtime.values.objects.buffers.Int32ArrayValue; +import me.topchetoeu.jscript.runtime.values.objects.buffers.Int8ArrayValue; +import me.topchetoeu.jscript.runtime.values.objects.buffers.TypedArrayValue; +import me.topchetoeu.jscript.runtime.values.objects.buffers.Uint8ArrayValue; import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; @@ -61,9 +66,12 @@ public class SimpleRepl { private static void reader() { try { - environment = createESEnv(); - tsEnvironment = createESEnv(); - + try { + environment = createESEnv(); + tsEnvironment = createESEnv(); + } + catch (ExecutionException e) { throw e.getCause(); } + server = new DebugServer(); debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true); server.targets.put("default", (socket, req) -> new SimpleDebugger(socket) @@ -74,16 +82,19 @@ public class SimpleRepl { try { try { initGlobals(); } catch (ExecutionException e) { throw e.getCause(); } } - catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); } + catch (EngineException | SyntaxException e) { + System.err.println("Failed to load stdlib. Falling back to barebones environment..."); + System.err.println(Value.errorToReadable(environment, e, null)); + } System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author())); for (var arg : args) { - try { - var file = new File(arg); - var raw = Reading.streamToString(new FileInputStream(file)); + var file = new File(arg); + var raw = Reading.streamToString(new FileInputStream(file)); + try { try { var res = engine.pushMsg( false, environment, @@ -95,14 +106,15 @@ public class SimpleRepl { catch (ExecutionException e) { throw e.getCause(); } } catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); } + } for (var i = 0; ; i++) { + var raw = Reading.readline(); + + if (raw == null) break; + try { - var raw = Reading.readline(); - - if (raw == null) break; - try { var res = engine.pushMsg( false, environment, @@ -114,8 +126,10 @@ public class SimpleRepl { catch (ExecutionException e) { throw e.getCause(); } } catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); } + } } + catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(environment, e, null)); } catch (IOException e) { System.out.println(e.toString()); engine.thread().interrupt(); @@ -183,34 +197,78 @@ public class SimpleRepl { var source = new StringBuilder(); - var inBrackets = false; + StringBuilder bracesSource = null; + StringBuilder bracketsSource = null; while (true) { if (n >= src.length()) break; var c = src.charAt(n++); - if (c == '\\') { + if (c == '\\' && n + 1 < src.length() && src.charAt(n) == 'b') { + c = '\b'; + n++; + } + + if (bracesSource != null) { + var failed = true; + + if (Character.isDigit(c)) { + bracesSource.append(c); + failed = false; + } + else if (c == ',' && bracesSource.indexOf(",") < 0) { + bracesSource.append(c); + failed = false; + } + else if (c == '}' && bracesSource.length() > 0) { + bracesSource.append(c); + source.append(bracesSource); + bracesSource = null; + continue; + } + + if (failed) { + source.append("\\"); + source.append(bracesSource); + bracesSource = null; + n--; + } + } + else if (bracketsSource != null) { + if (c == '[') bracketsSource.append("\\["); + else if (c == ']') { + var res = bracketsSource.append(']').toString(); + bracketsSource = null; + if (res.equals("[^]")) res = "[\\s\\S]"; + else if (res.equals("[]")) res = "[^\\s\\S]"; + source.append(res); + } + else if (c == '\\') { + if (n >= src.length()) break; + bracketsSource.append(c).append(src.charAt(n++)); + } + else bracketsSource.append(c); + } + else if (c == '\\') { if (n >= src.length()) throw new PatternSyntaxException("Unexpected end", src, n); c = src.charAt(n++); source.append('\\').append(c); } else if (c == '[') { - if (inBrackets) source.append("\\["); - else { - inBrackets = true; - source.append('['); - } + bracketsSource = new StringBuilder("["); } - else if (c == ']') { - if (inBrackets) { - inBrackets = false; - source.append(']'); - } - else throw new PatternSyntaxException("Unexpected ']'", src, n); + else if (c == '{' && bracketsSource == null) { + bracesSource = new StringBuilder("{"); } else source.append(c); } + if (bracesSource != null) { + source.append("\\"); + source.append(bracesSource); + } + if (bracketsSource != null) throw new PatternSyntaxException("Unmatched '['", src, n - bracketsSource.length()); + return source.toString(); } @@ -314,12 +372,18 @@ public class SimpleRepl { else return NumberValue.parseFloat(args.get(0).toString(), false); })); res.defineOwnField(env, "isNaN", new NativeFunction(args -> BoolValue.of(args.get(0).isNaN()))); - res.defineOwnField(env, "NaN", NumberValue.NAN); - res.defineOwnField(env, "Infinity", NumberValue.of(Double.POSITIVE_INFINITY)); res.defineOwnField(env, "pow", new NativeFunction(args -> { return NumberValue.of(Math.pow(args.get(0).toNumber(args.env).getDouble(), args.get(1).toNumber(args.env).getDouble())); })); + res.defineOwnField(env, "log", new NativeFunction(args -> { + return NumberValue.of(Math.log(args.get(0).toNumber(args.env).getDouble())); + })); + + res.defineOwnField(env, "NaN", NumberValue.NAN); + res.defineOwnField(env, "Infinity", NumberValue.of(Double.POSITIVE_INFINITY)); + res.defineOwnField(env, "PI", NumberValue.of(Math.PI)); + res.defineOwnField(env, "E", NumberValue.of(Math.E)); return res; } @@ -462,9 +526,6 @@ public class SimpleRepl { if (member == null) return Value.UNDEFINED; else return member.descriptor(args.env, obj); })); - res.defineOwnField(env, "isArray", new NativeFunction(args -> { - return BoolValue.of(args.get(0) instanceof ArrayValue); - })); res.defineOwnField(env, "preventExt", new NativeFunction(args -> { args.get(0).preventExtensions(); return VoidValue.UNDEFINED; @@ -499,6 +560,73 @@ public class SimpleRepl { return arr; })); + res.defineOwnField(env, "isArray", new NativeFunction(args -> { + return BoolValue.of(args.get(0) instanceof ArrayLikeValue); + })); + + return res; + } + + private static ObjectValue bufferPrimordials(Environment env) { + var buffProto = new ObjectValue(); + buffProto.defineOwnProperty(env, "length", Optional.of(new NativeFunction(args -> { + return NumberValue.of(args.self(byte[].class).length); + })), Optional.empty(), false, true); + + var res = new ObjectValue(); + res.setPrototype(null, null); + + res.defineOwnField(env, "buff", new NativeFunction(args -> { + var size = args.get(0).toNumber(env).getInt(); + return TypedArrayValue.buffer(new byte[size], buffProto); + })); + + res.defineOwnField(env, "uint8", new NativeFunction(args -> { + var buff = args.get(byte[].class, 0); + var start = args.get(1).toNumber(env).getInt(); + var end = args.get(2).toNumber(env).getInt(); + return new Uint8ArrayValue(buff, start, end); + })); + res.defineOwnField(env, "int8", new NativeFunction(args -> { + var buff = args.get(byte[].class, 0); + var start = args.get(1).toNumber(env).getInt(); + var end = args.get(2).toNumber(env).getInt(); + return new Int8ArrayValue(buff, start, end); + })); + res.defineOwnField(env, "int32", new NativeFunction(args -> { + var buff = args.get(byte[].class, 0); + var start = args.get(1).toNumber(env).getInt(); + var end = args.get(2).toNumber(env).getInt(); + return new Int32ArrayValue(buff, start, end); + })); + + res.defineOwnField(env, "isUint8", new NativeFunction(args -> { + return BoolValue.of(args.get(0) instanceof Uint8ArrayValue); + })); + res.defineOwnField(env, "isInt8", new NativeFunction(args -> { + return BoolValue.of(args.get(0) instanceof Int8ArrayValue); + })); + res.defineOwnField(env, "isInt32", new NativeFunction(args -> { + return BoolValue.of(args.get(0) instanceof Int32ArrayValue); + })); + + res.defineOwnField(env, "is", new NativeFunction(args -> { + return BoolValue.of(args.get(0) instanceof TypedArrayValue); + })); + res.defineOwnField(env, "isBuff", new NativeFunction(args -> { + return BoolValue.of(args.get(byte[].class, 0) != null); + })); + + res.defineOwnField(env, "backer", new NativeFunction(args -> { + return TypedArrayValue.buffer(((TypedArrayValue)args.get(0)).buffer, buffProto); + })); + res.defineOwnField(env, "start", new NativeFunction(args -> { + return NumberValue.of(((TypedArrayValue)args.get(0)).start); + })); + res.defineOwnField(env, "end", new NativeFunction(args -> { + return NumberValue.of(((TypedArrayValue)args.get(0)).end); + })); + return res; } @@ -584,6 +712,7 @@ public class SimpleRepl { res.defineOwnField(env, "number", numberPrimordials(env)); res.defineOwnField(env, "string", stringPrimordials(env)); res.defineOwnField(env, "object", objectPrimordials(env)); + res.defineOwnField(env, "buffer", bufferPrimordials(env)); res.defineOwnField(env, "function", functionPrimordials(env)); res.defineOwnField(env, "json", jsonPrimordials(env)); res.defineOwnField(env, "map", mapPrimordials(env)); @@ -605,6 +734,8 @@ 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"); + setProto(args.env, env, Value.UINT8_ARR_PROTO, obj, "uint8"); + setProto(args.env, env, Value.INT32_ARR_PROTO, obj, "int32"); var val = obj.getMember(args.env, "regex"); if (val instanceof FunctionValue func) { env.add(Value.REGEX_CONSTR, func); @@ -740,7 +871,7 @@ public class SimpleRepl { reader.setName("STD Reader"); reader.start(); - engine.thread().join(); + reader.join(); engineTask.interrupt(); debugTask.interrupt(); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java index a55c627..880527c 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -62,7 +62,10 @@ public abstract class Value { public static final Key FUNCTION_PROTO = new Key<>(); public static final Key ARRAY_PROTO = new Key<>(); - public static final Key BYTE_BUFF_PROTO = new Key<>(); + + public static final Key INT8_ARR_PROTO = new Key<>(); + public static final Key INT32_ARR_PROTO = new Key<>(); + public static final Key UINT8_ARR_PROTO = new Key<>(); public static final Key ERROR_PROTO = new Key<>(); public static final Key SYNTAX_ERR_PROTO = new Key<>(); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ByteBufferValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ByteBufferValue.java deleted file mode 100644 index 1be772a..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ByteBufferValue.java +++ /dev/null @@ -1,81 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.objects; - -import java.util.Arrays; -import java.util.Iterator; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; - -public class ByteBufferValue extends ArrayLikeValue implements Iterable { - public final byte[] values; - - public int size() { return values.length; } - public boolean setSize(int val) { return false; } - - @Override public Value get(int i) { - if (i < 0 || i >= values.length) return null; - return NumberValue.of(values[i]); - } - @Override public boolean set(Environment env, int i, Value val) { - if (i < 0 || i >= values.length) return false; - values[i] = (byte)val.toNumber(env).getInt(); - return true; - } - @Override public boolean has(int i) { - return i >= 0 && i < values.length; - } - @Override public boolean remove(int i) { - return false; - } - - public void copyTo(byte[] arr, int sourceStart, int destStart, int count) { - System.arraycopy(values, sourceStart, arr, destStart, count); - } - public void copyTo(ByteBufferValue arr, int sourceStart, int destStart, int count) { - arr.copyFrom(values, sourceStart, destStart, count); - } - public void copyFrom(byte[] arr, int sourceStart, int destStart, int count) { - System.arraycopy(arr, sourceStart, arr, destStart, count); - } - - public void move(int srcI, int dstI, int n) { - System.arraycopy(values, srcI, values, dstI, n); - } - - public void sort() { - var buckets = new int[256]; - - for (var i = 0; i < values.length; i++) { - buckets[values[i] + 128]++; - } - - var offset = 0; - - for (var i = 0; i < values.length; i++) { - Arrays.fill(values, offset, offset += buckets[i], (byte)(i - 128)); - } - } - - @Override public Iterator iterator() { - return new Iterator<>() { - private int i = 0; - - @Override public boolean hasNext() { - return i < size(); - } - @Override public Value next() { - if (!hasNext()) return null; - return get(i++); - } - }; - } - - public ByteBufferValue(int size) { - this(new byte[size]); - } - public ByteBufferValue(byte[] buffer) { - setPrototype(BYTE_BUFF_PROTO); - this.values = buffer; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/Int32ArrayValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/Int32ArrayValue.java new file mode 100644 index 0000000..01b7dbd --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/Int32ArrayValue.java @@ -0,0 +1,25 @@ +package me.topchetoeu.jscript.runtime.values.objects.buffers; + +public final class Int32ArrayValue extends TypedArrayValue { + @Override protected int onGet(int i) { + i = (i + start) << 2; + return ( + this.buffer[i] | + this.buffer[i + 1] << 8 | + this.buffer[i + 2] << 16 | + this.buffer[i + 3] << 24 + ); + } + @Override protected void onSet(int i, int val) { + i = (i + start) << 2; + this.buffer[i + start + 0] = (byte)(val & 0xFF); + this.buffer[i + start + 1] = (byte)(val >> 8 & 0xFF); + this.buffer[i + start + 2] = (byte)(val >> 16 & 0xFF); + this.buffer[i + start + 3] = (byte)(val >> 24 & 0xFF); + } + + public Int32ArrayValue(byte[] buff, int start, int end) { + super(buff, 4, start, end); + setPrototype(INT32_ARR_PROTO); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/Int8ArrayValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/Int8ArrayValue.java new file mode 100644 index 0000000..7d66576 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/Int8ArrayValue.java @@ -0,0 +1,15 @@ +package me.topchetoeu.jscript.runtime.values.objects.buffers; + +public final class Int8ArrayValue extends TypedArrayValue { + @Override protected int onGet(int i) { + return this.buffer[i + start]; + } + @Override protected void onSet(int i, int val) { + this.buffer[i + start] = (byte)val; + } + + public Int8ArrayValue(byte[] buff, int start, int end) { + super(buff, 1, start, end); + setPrototype(INT8_ARR_PROTO); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/TypedArrayValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/TypedArrayValue.java new file mode 100644 index 0000000..dd1e273 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/TypedArrayValue.java @@ -0,0 +1,60 @@ +package me.topchetoeu.jscript.runtime.values.objects.buffers; + +import java.util.WeakHashMap; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.values.Value; +import me.topchetoeu.jscript.runtime.values.objects.ArrayLikeValue; +import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; +import me.topchetoeu.jscript.runtime.values.primitives.UserValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; + +public abstract class TypedArrayValue extends ArrayLikeValue { + public static final WeakHashMap> userValues = new WeakHashMap<>(); + + public final byte[] buffer; + public final int elementSize; + public final int start; + public final int end; + + protected abstract int onGet(int i); + protected abstract void onSet(int i, int val); + + @Override public int size() { + return end - start; + } + + @Override public boolean setSize(int val) { + return false; + } + + @Override public Value get(int i) { + if (i < 0 || i >= size()) return null; + return NumberValue.of(onGet(i)); + } + @Override public boolean set(Environment env, int i, Value val) { + if (i < 0 || i >= size()) return false; + onSet(i, val.toNumber(env).getInt()); + return true; + } + @Override public boolean has(int i) { + return i >= 0 && i < size(); + } + @Override public boolean remove(int i) { + return false; + } + + public TypedArrayValue(byte[] buffer, int elementSize, int start, int end) { + this.buffer = buffer; + this.elementSize = elementSize; + this.start = start; + this.end = end; + } + + public static UserValue buffer(byte[] buff, ObjectValue proto) { + if (userValues.containsKey(buff)) return userValues.get(buff); + var res = UserValue.of(buff, proto); + userValues.put(buff, res); + return res; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/Uint8ArrayValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/Uint8ArrayValue.java new file mode 100644 index 0000000..63c2e2e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/buffers/Uint8ArrayValue.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.runtime.values.objects.buffers; + +public final class Uint8ArrayValue extends TypedArrayValue { + @Override protected int onGet(int i) { + var res = this.buffer[i + start]; + if (res < 0) res += 0x100; + + return res; + } + @Override protected void onSet(int i, int val) { + if (val > 0x7F) val -= 0x100; + this.buffer[i + start] = (byte)val; + } + + public Uint8ArrayValue(byte[] buff, int start, int end) { + super(buff, 1, start, end); + setPrototype(UINT8_ARR_PROTO); + } +}