build: split up into multiple projects, use kotlin DLS
All checks were successful
tagged-release / Tagged Release (push) Successful in 5m23s

This commit is contained in:
2025-01-10 04:05:17 +02:00
parent 9668bccef1
commit d563fc4919
209 changed files with 459 additions and 266 deletions

View File

@@ -0,0 +1,13 @@
package me.topchetoeu.j2s.runtime;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.objects.ArrayValue;
public class ArgumentsValue extends ArrayValue {
public final Frame frame;
public ArgumentsValue(Frame frame, Value... args) {
super(args);
this.frame = frame;
}
}

View File

@@ -0,0 +1,26 @@
package me.topchetoeu.j2s.runtime;
import java.util.function.Function;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.common.environment.Key;
import me.topchetoeu.j2s.common.parsing.Filename;
import me.topchetoeu.j2s.common.parsing.Location;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
public interface Compiler {
public Key<Compiler> KEY = new Key<>();
public FunctionValue compile(Environment env, Filename filename, String source, Function<Location, Location> map);
public static Compiler get(Environment ext) {
return ext.get(KEY, (env, filename, src, map) -> {
throw EngineException.ofError("No compiler attached to engine");
});
}
public static FunctionValue compileFunc(Environment env, Filename filename, String raw) {
return get(env).compile(env, filename, raw, v -> v);
}
}

View File

@@ -0,0 +1,74 @@
package me.topchetoeu.j2s.runtime;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.function.Supplier;
public final class Engine implements EventLoop {
private static class Task<T> implements Comparable<Task<?>> {
public final Supplier<?> runnable;
public final CompletableFuture<T> notifier = new CompletableFuture<T>();
public final boolean micro;
public Task(Supplier<T> runnable, boolean micro) {
this.runnable = runnable;
this.micro = micro;
}
@Override public int compareTo(Task<?> other) {
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
}
}
private PriorityBlockingQueue<Task<?>> tasks = new PriorityBlockingQueue<>();
private Thread thread;
@Override public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro) {
var msg = new Task<T>(runnable, micro);
tasks.add(msg);
return msg.notifier;
}
@SuppressWarnings("unchecked")
public void run(boolean untilEmpty) {
while (!untilEmpty || !tasks.isEmpty()) {
try {
var task = tasks.take();
try {
((Task<Object>)task).notifier.complete(task.runnable.get());
}
catch (CancellationException e) { task.notifier.cancel(false); throw e; }
catch (RuntimeException e) { task.notifier.completeExceptionally(e); }
}
catch (InterruptedException | CancellationException e) {
for (var msg : tasks) msg.notifier.cancel(false);
break;
}
}
}
public Thread thread() {
return thread;
}
public Thread start() {
if (thread == null) {
thread = new Thread(() -> run(false), "Event loop #" + hashCode());
thread.start();
}
return thread;
}
public void stop() {
if (thread != null) thread.interrupt();
thread = null;
}
public boolean inLoopThread() {
return Thread.currentThread() == thread;
}
public boolean isRunning() {
return this.thread != null;
}
}

View File

@@ -0,0 +1,36 @@
package me.topchetoeu.j2s.runtime;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.common.environment.Key;
import me.topchetoeu.j2s.common.parsing.Filename;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
public interface EventLoop {
public static final Key<EventLoop> KEY = new Key<>();
public static EventLoop get(Environment ext) {
if (ext.hasNotNull(KEY)) return ext.get(KEY);
else return new EventLoop() {
@Override public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro) {
throw EngineException.ofError("No event loop attached to environment.");
}
};
}
public <T> Future<T> pushMsg(Supplier<T> runnable, boolean micro);
public default Future<Void> pushMsg(Runnable runnable, boolean micro) {
return pushMsg(() -> { runnable.run(); return null; }, micro);
}
public default Future<Value> pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) {
return pushMsg(() -> func.apply(env, thisArg, args), micro);
}
public default Future<Value> pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) {
return pushMsg(() -> Compiler.compileFunc(env, filename, raw).apply(env, thisArg, args), micro);
}
}

View File

@@ -0,0 +1,411 @@
package me.topchetoeu.j2s.runtime;
import java.util.Arrays;
import java.util.Stack;
import java.util.concurrent.CancellationException;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.common.environment.Key;
import me.topchetoeu.j2s.runtime.debug.DebugContext;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.functions.CodeFunction;
import me.topchetoeu.j2s.runtime.values.objects.ArrayLikeValue;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.IntValue;
public final class Frame {
public static final Key<Stack<Frame>> KEY = new Key<>();
public static final EngineException STACK_OVERFLOW;
static {
STACK_OVERFLOW = EngineException.ofRange("Stack overflow!");
STACK_OVERFLOW.value.freeze();
}
public static enum TryState {
TRY,
CATCH,
FINALLY,
}
public static class TryCtx {
public final int start, end, catchStart, finallyStart;
public final int restoreStackPtr;
public final TryState state;
public final EngineException error;
public final PendingResult result;
public boolean hasCatch() { return catchStart >= 0; }
public boolean hasFinally() { return finallyStart >= 0; }
public boolean inBounds(int ptr) {
return ptr >= start && ptr < end;
}
public void setCause(EngineException target) {
if (error != null) target.setCause(error);
}
public TryCtx _catch(EngineException e) {
return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart);
}
public TryCtx _finally(PendingResult res) {
return new TryCtx(TryState.FINALLY, error, res, restoreStackPtr, start, end, -1, -1);
}
public TryCtx(TryState state, EngineException err, PendingResult res, int stackPtr, int start, int end, int catchStart, int finallyStart) {
this.catchStart = catchStart;
this.finallyStart = finallyStart;
this.restoreStackPtr = stackPtr;
this.result = res == null ? PendingResult.ofNone() : res;
this.state = state;
this.start = start;
this.end = end;
this.error = err;
}
}
private static class PendingResult {
public final boolean isReturn, isJump, isThrow;
public final Value value;
public final EngineException error;
public final int ptr;
public final Instruction instruction;
private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Value value, EngineException error, int ptr) {
this.instruction = instr;
this.isReturn = isReturn;
this.isJump = isJump;
this.isThrow = isThrow;
this.value = value;
this.error = error;
this.ptr = ptr;
}
public static PendingResult ofNone() {
return new PendingResult(null, false, false, false, null, null, 0);
}
public static PendingResult ofReturn(Value value, Instruction instr) {
return new PendingResult(instr, true, false, false, value, null, 0);
}
public static PendingResult ofThrow(EngineException error, Instruction instr) {
return new PendingResult(instr, false, false, true, null, error, 0);
}
public static PendingResult ofJump(int codePtr, Instruction instr) {
return new PendingResult(instr, false, true, false, null, null, codePtr);
}
}
/**
* An array of captures from the parent function
*/
public final Value[][] captures;
/**
* An array of non-capture variables
*/
public final Value[] locals;
/**
* An array of children-captured variables
*/
public final Value[][] capturables;
public final Value self;
public final Value target;
public final Value[] args;
public final Value argsVal;
public final Value argsLen;
public final boolean isNew;
public final Stack<TryCtx> tryStack = new Stack<>();
public final CodeFunction function;
public final Environment env;
private final DebugContext dbg;
public Value getVar(int i) {
if (i < 0) return captures[~i][0];
else if (i < locals.length) return locals[i];
else return capturables[i - locals.length][0];
}
public Value setVar(int i, Value val) {
if (i < 0) return captures[~i][0] = val;
else if (i < locals.length) return locals[i] = val;
else return capturables[i - locals.length][0] = val;
}
public Value[] captureVar(int i) {
if (i < 0) return captures[~i];
if (i >= locals.length) return capturables[i - locals.length];
else throw new RuntimeException("Illegal capture");
}
public Value[] stack = new Value[32];
public int stackPtr = 0;
public int codePtr = 0;
public boolean jumpFlag = false;
public boolean popTryFlag = false;
public void addTry(int start, int end, int catchStart, int finallyStart) {
var err = tryStack.empty() ? null : tryStack.peek().error;
var res = new TryCtx(TryState.TRY, err, null, stackPtr, start, end, catchStart, finallyStart);
tryStack.add(res);
}
public Value peek() {
return peek(0);
}
public Value peek(int offset) {
return stack[stackPtr - 1 - offset];
}
public Value pop() {
return stack[--stackPtr];
}
public Value[] take(int n) {
Value[] res = new Value[n];
System.arraycopy(stack, stackPtr - n, res, 0, n);
stackPtr -= n;
return res;
}
public void push(Value val) {
if (stack.length <= stackPtr) {
var newStack = new Value[stack.length * 2];
System.arraycopy(stack, 0, newStack, 0, stack.length);
stack = newStack;
}
stack[stackPtr++] = val;
}
public void replace(Value val) {
stack[stackPtr - 1] = val;
}
// for the love of christ don't touch this
/**
* This is provided only for optimization-sike. All parameters must be null except at most one, otherwise undefined behavior
*/
public final Value next(Value value, Value returnValue, EngineException error) {
if (value != null) push(value);
Instruction instr = null;
if (codePtr != function.body.instructions.length) instr = function.body.instructions[codePtr];
if (returnValue == null && error == null) {
try {
if (Thread.interrupted()) throw new CancellationException();
if (instr == null) {
if (stackPtr > 0) returnValue = stack[stackPtr - 1];
else returnValue = Value.UNDEFINED;
}
else {
dbg.onInstruction(env, this, instr);
try {
this.jumpFlag = this.popTryFlag = false;
returnValue = InstructionRunner.exec(env, instr, this);
}
catch (EngineException e) {
error = e.add(env, function.name, dbg.getMapOrEmpty(function).toLocation(codePtr, true));
}
}
}
catch (StackOverflowError e) { throw STACK_OVERFLOW; }
catch (EngineException e) { error = e; }
catch (RuntimeException e) {
if (!(e instanceof CancellationException)) {
System.out.println(dbg.getMapOrEmpty(function).toLocation(codePtr, true));
}
throw e;
}
}
while (!tryStack.empty()) {
var tryCtx = tryStack.peek();
TryCtx newCtx = null;
if (error != null) {
tryCtx.setCause(error);
if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
}
else if (returnValue != null) {
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
}
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofJump(codePtr, instr));
}
else if (!this.popTryFlag) newCtx = tryCtx;
if (newCtx != null) {
if (newCtx != tryCtx) {
switch (newCtx.state) {
case CATCH:
codePtr = tryCtx.catchStart;
stackPtr = tryCtx.restoreStackPtr;
break;
case FINALLY:
codePtr = tryCtx.finallyStart;
stackPtr = tryCtx.restoreStackPtr;
default:
}
tryStack.pop();
tryStack.push(newCtx);
}
error = null;
returnValue = null;
break;
}
else {
popTryFlag = false;
if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
codePtr = tryCtx.finallyStart;
stackPtr = tryCtx.restoreStackPtr;
tryStack.pop();
tryStack.push(tryCtx._finally(null));
break;
}
else {
tryStack.pop();
codePtr = tryCtx.end;
if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
if (!jumpFlag && returnValue == null && error == null) {
if (tryCtx.result.isJump) {
codePtr = tryCtx.result.ptr;
jumpFlag = true;
}
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
if (error == null && tryCtx.result.isThrow) {
error = tryCtx.result.error;
}
}
if (error != null) tryCtx.setCause(error);
continue;
}
}
}
if (returnValue != null) {
dbg.onInstruction(env, this, instr, returnValue, null, false);
return returnValue;
}
if (error != null) {
var caught = false;
loop: for (var frame : Frame.get(env)) {
for (var tryCtx : frame.tryStack) {
if (tryCtx.state == TryState.TRY) {
caught = true;
break loop;
}
}
}
dbg.onInstruction(env, this, instr, null, error, caught);
throw error;
}
return null;
}
/**
* Executes the next instruction in the frame
*/
public final Value next() {
return next(null, null, null);
}
/**
* Induces a value on the stack (as if it were returned by the last function call)
* and executes the next instruction in the frame.
*
* @param value The value to induce
*/
public final Value next(Value value) {
return next(value, null, null);
}
/**
* Induces a thrown error and executes the next instruction.
* Note that this is different than just throwing the error outside the
* function, as the function executed could have a try-catch which
* would otherwise handle the error
*
* @param error The error to induce
*/
public final Value induceError(EngineException error) {
return next(null, null, error);
}
/**
* Induces a return, as if there was a return statement before
* the currently executed instruction and executes the next instruction.
* Note that this is different than just returning the value outside the
* function, as the function executed could have a try-catch which
* would otherwise handle the error
*
* @param value The retunr value to induce
*/
public final Value induceReturn(Value value) {
return next(null, value, null);
}
public void onPush() {
get(env).push(this);
DebugContext.get(env).onFramePush(env, this);
}
public void onPop() {
DebugContext.get(env).onFramePop(env, this);
get(env).pop();
}
/**
* Gets an array proxy of the local locals
*/
public ObjectValue getValStackScope() {
return new ArrayLikeValue() {
@Override public Value get(int i) { return stack[i]; }
@Override public boolean set(Environment env, int i, Value val) { return false; }
@Override public boolean has(int i) { return i >= 0 && i < size(); }
@Override public boolean remove(int i) { return false; }
@Override public int size() { return stackPtr; }
@Override public boolean setSize(int val) { return false; }
};
}
public Frame(Environment env, boolean isNew, Value target, Value self, Value[] args, CodeFunction func) {
this.env = env;
this.dbg = DebugContext.get(env);
this.function = func;
this.isNew = isNew;
this.target = target;
this.self = self;
this.args = args;
this.argsVal = new ArgumentsValue(this, args);
this.argsLen = new IntValue(args.length);
this.captures = func.captures;
this.locals = new Value[func.body.localsN];
Arrays.fill(this.locals, Value.UNDEFINED);
this.capturables = new Value[func.body.capturablesN][1];
for (var i = 0; i < this.capturables.length; i++) this.capturables[i][0] = Value.UNDEFINED;
}
public static Stack<Frame> get(Environment env) {
if (env.has(KEY)) return env.get(KEY);
else {
var stack = new Stack<Frame>();
env.add(KEY, stack);
return stack;
}
}
public static Frame get(Environment env, int i) {
var stack = get(env);
i = stack.size() - i - 1;
if (i < 0 || i >= stack.size()) return null;
return stack.get(i);
}
}

View File

@@ -0,0 +1,568 @@
package me.topchetoeu.j2s.runtime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Optional;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.Operation;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.functions.CodeFunction;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
import me.topchetoeu.j2s.runtime.values.objects.ArrayValue;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.BoolValue;
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public class InstructionRunner {
private static Value execReturn(Environment env, Instruction instr, Frame frame) {
return frame.pop();
}
private static Value execThrow(Environment env, Instruction instr, Frame frame) {
throw new EngineException(frame.pop());
}
private static Value execThrowSyntax(Environment env, Instruction instr, Frame frame) {
throw EngineException.ofSyntax((String)instr.get(0));
}
private static Value execCall(Environment env, Instruction instr, Frame frame) {
var callArgs = frame.take(instr.get(0));
var func = frame.pop();
var self = (boolean)instr.get(1) ? frame.pop() : Value.UNDEFINED;
frame.push(func.apply(env, self, callArgs));
frame.codePtr++;
return null;
}
private static Value execCallNew(Environment env, Instruction instr, Frame frame) {
var callArgs = frame.take(instr.get(0));
var funcObj = frame.pop();
frame.push(funcObj.constructNoSelf(env, callArgs));
frame.codePtr++;
return null;
}
private static Value execDefProp(Environment env, Instruction instr, Frame frame) {
var val = frame.pop();
var key = frame.pop();
var obj = frame.pop();
FunctionValue accessor;
if (val == Value.UNDEFINED) accessor = null;
else if (val instanceof FunctionValue func) accessor = func;
else throw EngineException.ofType("Getter must be a function or undefined");
if ((boolean)instr.get(0)) obj.defineOwnProperty(env, key, null, Optional.of(accessor), true, true);
else obj.defineOwnProperty(env, key, Optional.of(accessor), null, true, true);
frame.codePtr++;
return null;
}
private static Value execDefField(Environment env, Instruction instr, Frame frame) {
var val = frame.pop();
var key = frame.pop();
var obj = frame.pop();
obj.defineOwnField(env, key, val, true, true, true);
frame.codePtr++;
return null;
}
private static Value execKeys(Environment env, Instruction instr, Frame frame) {
var val = frame.pop();
var members = new ArrayList<>(val.getMembers(env, instr.get(0), instr.get(1)));
Collections.reverse(members);
frame.push(Value.UNDEFINED);
for (var el : members) {
var obj = new ObjectValue();
obj.defineOwnField(env, "value", StringValue.of(el));
frame.push(obj);
}
frame.codePtr++;
return null;
}
private static Value execTryStart(Environment env, Instruction instr, Frame frame) {
int start = frame.codePtr + 1;
int catchStart = (int)instr.get(0);
int finallyStart = (int)instr.get(1);
if (finallyStart >= 0) finallyStart += start;
if (catchStart >= 0) catchStart += start;
int end = (int)instr.get(2) + start;
frame.addTry(start, end, catchStart, finallyStart);
frame.codePtr++;
return null;
}
private static Value execTryEnd(Environment env, Instruction instr, Frame frame) {
frame.popTryFlag = true;
return null;
}
private static Value execDup(Environment env, Instruction instr, Frame frame) {
int count = instr.get(0);
int offset = instr.get(1);
var el = frame.stack[frame.stackPtr - offset - 1];
for (var i = 0; i < count; i++) {
frame.push(el);
}
frame.codePtr++;
return null;
}
private static Value execLoadValue(Environment env, Instruction instr, Frame frame) {
switch (instr.type) {
case PUSH_UNDEFINED: frame.push(Value.UNDEFINED); break;
case PUSH_NULL: frame.push(Value.NULL); break;
case PUSH_BOOL: frame.push(BoolValue.of(instr.get(0))); break;
case PUSH_NUMBER: frame.push(NumberValue.of((double)instr.get(0))); break;
case PUSH_STRING: frame.push(StringValue.of(instr.get(0))); break;
default:
}
frame.codePtr++;
return null;
}
private static Value execLoadVar(Environment env, Instruction instr, Frame frame) {
int i = instr.get(0);
frame.push(frame.getVar(i));
frame.codePtr++;
return null;
}
private static Value execLoadObj(Environment env, Instruction instr, Frame frame) {
var obj = new ObjectValue();
obj.setPrototype(Value.OBJECT_PROTO);
frame.push(obj);
frame.codePtr++;
return null;
}
private static Value execLoadGlob(Environment env, Instruction instr, Frame frame) {
frame.push(Value.global(env));
frame.codePtr++;
return null;
}
private static Value execLoadIntrinsics(Environment env, Instruction instr, Frame frame) {
frame.push(Value.intrinsics(env).get((String)instr.get(0)));
frame.codePtr++;
return null;
}
private static Value execLoadArr(Environment env, Instruction instr, Frame frame) {
var res = new ArrayValue();
res.setSize(instr.get(0));
frame.push(res);
frame.codePtr++;
return null;
}
private static Value execLoadFunc(Environment env, Instruction instr, Frame frame) {
int id = instr.get(0);
String name = instr.get(1);
var captures = new Value[instr.params.length - 2][];
for (var i = 2; i < instr.params.length; i++) {
captures[i - 2] = frame.captureVar(instr.get(i));
}
var func = new CodeFunction(env, name, frame.function.body.children[id], captures);
frame.push(func);
frame.codePtr++;
return null;
}
private static Value execLoadMember(Environment env, Instruction instr, Frame frame) {
var key = frame.pop();
try {
var top = frame.stackPtr - 1;
frame.stack[top] = frame.stack[top].getMember(env, key);
}
catch (IllegalArgumentException e) {
throw EngineException.ofType(e.getMessage());
}
frame.codePtr++;
return null;
}
private static Value execLoadMemberInt(Environment env, Instruction instr, Frame frame) {
try {
var top = frame.stackPtr - 1;
frame.stack[top] = frame.stack[top].getMember(env, (int)instr.get(0));
}
catch (IllegalArgumentException e) {
throw EngineException.ofType(e.getMessage());
}
frame.codePtr++;
return null;
}
private static Value execLoadMemberStr(Environment env, Instruction instr, Frame frame) {
try {
var top = frame.stackPtr - 1;
frame.stack[top] = frame.stack[top].getMember(env, (String)instr.get(0));
}
catch (IllegalArgumentException e) {
throw EngineException.ofType(e.getMessage());
}
frame.codePtr++;
return null;
}
private static Value execLoadRegEx(Environment env, Instruction instr, Frame frame) {
if (env.hasNotNull(Value.REGEX_CONSTR)) {
frame.push(env.get(Value.REGEX_CONSTR).constructNoSelf(env,
StringValue.of(instr.get(0)),
StringValue.of(instr.get(1))
));
}
else {
throw EngineException.ofSyntax("Regex is not supported");
}
frame.codePtr++;
return null;
}
private static Value execDiscard(Environment env, Instruction instr, Frame frame) {
frame.pop();
frame.codePtr++;
return null;
}
private static Value execStoreMember(Environment env, Instruction instr, Frame frame) {
var val = frame.pop();
var key = frame.pop();
var obj = frame.pop();
if (!obj.setMember(env, key, val)) throw EngineException.ofSyntax("Can't set member '" + key.toReadable(env) + "'");
if ((boolean)instr.get(0)) frame.push(val);
frame.codePtr++;
return null;
}
private static Value execStoreMemberStr(Environment env, Instruction instr, Frame frame) {
var val = frame.pop();
var obj = frame.pop();
if (!obj.setMember(env, (String)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'");
if ((boolean)instr.get(1)) frame.push(val);
frame.codePtr++;
return null;
}
private static Value execStoreMemberInt(Environment env, Instruction instr, Frame frame) {
var val = frame.pop();
var obj = frame.pop();
if (!obj.setMember(env, (int)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'");
if ((boolean)instr.get(1)) frame.push(val);
frame.codePtr++;
return null;
}
private static Value execStoreVar(Environment env, Instruction instr, Frame frame) {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
int i = instr.get(0);
frame.setVar(i, val);
frame.codePtr++;
return null;
}
private static Value execJmp(Environment env, Instruction instr, Frame frame) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
return null;
}
private static Value execJmpIf(Environment env, Instruction instr, Frame frame) {
if (frame.pop().toBoolean()) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
}
else frame.codePtr ++;
return null;
}
private static Value execJmpIfNot(Environment env, Instruction instr, Frame frame) {
if (!frame.pop().toBoolean()) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
}
else frame.codePtr ++;
return null;
}
private static Value execTypeof(Environment env, Instruction instr, Frame frame) {
String name = instr.get(0);
Value obj;
if (name != null) obj = Value.global(env).getMember(env, name);
else obj = frame.pop();
frame.push(obj.type());
frame.codePtr++;
return null;
}
private static Value execNop(Environment env, Instruction instr, Frame frame) {
frame.codePtr++;
return null;
}
private static Value execDelete(Environment env, Instruction instr, Frame frame) {
var key = frame.pop();
var val = frame.pop();
if (!val.deleteMember(env, key)) throw EngineException.ofSyntax("Can't delete member '" + key.toReadable(env) + "'");
frame.codePtr++;
return null;
}
private static Value execOperation(Environment env, Instruction instr, Frame frame) {
Operation op = instr.get(0);
Value res;
var stack = frame.stack;
frame.stackPtr -= 1;
var ptr = frame.stackPtr;
switch (op) {
case ADD:
res = Value.add(env, stack[ptr - 1], stack[ptr]);
break;
case SUBTRACT:
res = Value.subtract(env, stack[ptr - 1], stack[ptr]);
break;
case DIVIDE:
res = Value.divide(env, stack[ptr - 1], stack[ptr]);
break;
case MULTIPLY:
res = Value.multiply(env, stack[ptr - 1], stack[ptr]);
break;
case MODULO:
res = Value.modulo(env, stack[ptr - 1], stack[ptr]);
break;
case AND:
res = Value.and(env, stack[ptr - 1], stack[ptr]);
break;
case OR:
res = Value.or(env, stack[ptr - 1], stack[ptr]);
break;
case XOR:
res = Value.xor(env, stack[ptr - 1], stack[ptr]);
break;
case EQUALS:
res = BoolValue.of(stack[ptr - 1].equals(stack[ptr]));
break;
case NOT_EQUALS:
res = BoolValue.of(!stack[ptr - 1].equals(stack[ptr]));
break;
case LOOSE_EQUALS:
res = BoolValue.of(Value.looseEqual(env, stack[ptr - 1], stack[ptr]));
break;
case LOOSE_NOT_EQUALS:
res = BoolValue.of(!Value.looseEqual(env, stack[ptr - 1], stack[ptr]));
break;
case GREATER:
res = BoolValue.of(Value.greater(env, stack[ptr - 1], stack[ptr]));
break;
case GREATER_EQUALS:
res = BoolValue.of(Value.greaterOrEqual(env, stack[ptr - 1], stack[ptr]));
break;
case LESS:
res = BoolValue.of(Value.less(env, stack[ptr - 1], stack[ptr]));
break;
case LESS_EQUALS:
res = BoolValue.of(Value.lessOrEqual(env, stack[ptr - 1], stack[ptr]));
break;
case INVERSE:
res = Value.bitwiseNot(env, stack[ptr++]);
frame.stackPtr++;
break;
case NOT:
res = BoolValue.of(!stack[ptr++].toBoolean());
frame.stackPtr++;
break;
case POS:
res = stack[ptr++].toNumber(env);
frame.stackPtr++;
break;
case NEG:
res = Value.negative(env, stack[ptr++]);
frame.stackPtr++;
break;
case SHIFT_LEFT:
res = Value.shiftLeft(env, stack[ptr - 1], stack[ptr]);
break;
case SHIFT_RIGHT:
res = Value.shiftRight(env, stack[ptr - 1], stack[ptr]);
break;
case USHIFT_RIGHT:
res = Value.unsignedShiftRight(env, stack[ptr - 1], stack[ptr]);
break;
case IN:
res = BoolValue.of(stack[ptr - 1].hasMember(env, stack[ptr], false));
break;
case INSTANCEOF:
res = BoolValue.of(stack[ptr - 1].isInstanceOf(env, stack[ptr].getMember(env, StringValue.of("prototype"))));
break;
default: return null;
}
stack[ptr - 1] = res;
frame.codePtr++;
return null;
}
private static Value execGlobDef(Environment env, Instruction instr, Frame frame) {
var name = (String)instr.get(0);
if (!Value.global(env).hasMember(env, name, false)) {
if (!Value.global(env).defineOwnField(env, name, Value.UNDEFINED)) throw EngineException.ofError("Couldn't define variable " + name);
}
frame.codePtr++;
return null;
}
private static Value execGlobGet(Environment env, Instruction instr, Frame frame) {
var name = (String)instr.get(0);
if ((boolean)instr.get(1)) {
frame.push(Value.global(env).getMember(env, name));
}
else {
var res = Value.global(env).getMemberOrNull(env, name);
if (res == null) throw EngineException.ofSyntax(name + " is not defined");
else frame.push(res);
}
frame.codePtr++;
return null;
}
private static Value execGlobSet(Environment env, Instruction instr, Frame frame) {
var name = (String)instr.get(0);
var keep = (boolean)instr.get(1);
var define = (boolean)instr.get(2);
var val = keep ? frame.peek() : frame.pop();
var res = false;
if (define) res = Value.global(env).setMember(env, name, val);
else res = Value.global(env).setMemberIfExists(env, name, val);
if (!res) throw EngineException.ofError("Couldn't set variable " + name);
frame.codePtr++;
return null;
}
private static Value execLoadArg(Environment env, Instruction instr, Frame frame) {
int i = instr.get(0);
if (i >= frame.args.length) frame.push(Value.UNDEFINED);
else frame.push(frame.args[i]);
frame.codePtr++;
return null;
}
private static Value execLoadArgsN(Environment env, Instruction instr, Frame frame) {
frame.push(frame.argsLen);
frame.codePtr++;
return null;
}
private static Value execLoadArgs(Environment env, Instruction instr, Frame frame) {
frame.push(frame.argsVal);
frame.codePtr++;
return null;
}
private static Value execLoadCallee(Environment env, Instruction instr, Frame frame) {
frame.push(frame.function);
frame.codePtr++;
return null;
}
private static Value execLoadThis(Environment env, Instruction instr, Frame frame) {
if (frame.self == null) throw EngineException.ofError("Super constructor must be called before 'this' is accessed");
frame.push(frame.self);
frame.codePtr++;
return null;
}
private static Value execLoadError(Environment env, Instruction instr, Frame frame) {
frame.push(frame.tryStack.peek().error.value);
frame.codePtr++;
return null;
}
public static Value exec(Environment env, Instruction instr, Frame frame) {
switch (instr.type) {
case NOP: return execNop(env, instr, frame);
case RETURN: return execReturn(env, instr, frame);
case THROW: return execThrow(env, instr, frame);
case THROW_SYNTAX: return execThrowSyntax(env, instr, frame);
case CALL: return execCall(env, instr, frame);
case CALL_NEW: return execCallNew(env, instr, frame);
case TRY_START: return execTryStart(env, instr, frame);
case TRY_END: return execTryEnd(env, instr, frame);
case DUP: return execDup(env, instr, frame);
case PUSH_UNDEFINED:
case PUSH_NULL:
case PUSH_STRING:
case PUSH_NUMBER:
case PUSH_BOOL:
return execLoadValue(env, instr, frame);
case LOAD_VAR: return execLoadVar(env, instr, frame);
case LOAD_OBJ: return execLoadObj(env, instr, frame);
case LOAD_ARR: return execLoadArr(env, instr, frame);
case LOAD_FUNC: return execLoadFunc(env, instr, frame);
case LOAD_MEMBER: return execLoadMember(env, instr, frame);
case LOAD_MEMBER_INT: return execLoadMemberInt(env, instr, frame);
case LOAD_MEMBER_STR: return execLoadMemberStr(env, instr, frame);
case LOAD_REGEX: return execLoadRegEx(env, instr, frame);
case LOAD_GLOB: return execLoadGlob(env, instr, frame);
case LOAD_INTRINSICS: return execLoadIntrinsics(env, instr, frame);
case LOAD_ERROR: return execLoadError(env, instr, frame);
case LOAD_THIS: return execLoadThis(env, instr, frame);
case LOAD_ARG: return execLoadArg(env, instr, frame);
case LOAD_ARGS: return execLoadArgs(env, instr, frame);
case LOAD_ARGS_N: return execLoadArgsN(env, instr, frame);
case LOAD_CALLED: return execLoadCallee(env, instr, frame);
case DISCARD: return execDiscard(env, instr, frame);
case STORE_MEMBER: return execStoreMember(env, instr, frame);
case STORE_MEMBER_STR: return execStoreMemberStr(env, instr, frame);
case STORE_MEMBER_INT: return execStoreMemberInt(env, instr, frame);
case STORE_VAR: return execStoreVar(env, instr, frame);
case KEYS: return execKeys(env, instr, frame);
case DEF_PROP: return execDefProp(env, instr, frame);
case DEF_FIELD: return execDefField(env, instr, frame);
case TYPEOF: return execTypeof(env, instr, frame);
case DELETE: return execDelete(env, instr, frame);
case JMP: return execJmp(env, instr, frame);
case JMP_IF: return execJmpIf(env, instr, frame);
case JMP_IFN: return execJmpIfNot(env, instr, frame);
case OPERATION: return execOperation(env, instr, frame);
case GLOB_DEF: return execGlobDef(env, instr, frame);
case GLOB_GET: return execGlobGet(env, instr, frame);
case GLOB_SET: return execGlobSet(env, instr, frame);
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "");
}
}
}

View File

@@ -0,0 +1,86 @@
package me.topchetoeu.j2s.runtime;
import java.util.HashSet;
import java.util.stream.Collectors;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.common.json.JSONElement;
import me.topchetoeu.j2s.common.json.JSONList;
import me.topchetoeu.j2s.common.json.JSONMap;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.objects.ArrayValue;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.BoolValue;
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
import me.topchetoeu.j2s.runtime.values.primitives.VoidValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public class JSONConverter {
public static Value toJs(JSONElement val) {
if (val.isBoolean()) return BoolValue.of(val.bool());
if (val.isString()) return StringValue.of(val.string());
if (val.isNumber()) return NumberValue.of(val.number());
if (val.isList()) return ArrayValue.of(val.list().stream().map(JSONConverter::toJs).collect(Collectors.toList()));
if (val.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineOwnField(null, el.getKey(), toJs(el.getValue()));
}
return res;
}
if (val.isNull()) return Value.NULL;
return Value.UNDEFINED;
}
public static JSONElement fromJs(Environment ext, Value val) {
var res = JSONConverter.fromJs(ext, val, new HashSet<>());
if (res == null) return JSONElement.NULL;
else return res;
}
public static JSONElement fromJs(Environment env, Value val, HashSet<Object> prev) {
if (val instanceof BoolValue) return JSONElement.bool(((BoolValue)val).value);
if (val instanceof NumberValue) return JSONElement.number(((NumberValue)val).getDouble());
if (val instanceof StringValue) return JSONElement.string(((StringValue)val).value);
if (val == Value.NULL) return JSONElement.NULL;
if (val instanceof VoidValue) return null;
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw EngineException.ofError("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = fromJs(env, el, prev);
if (jsonEl == null) continue;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw EngineException.ofError("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var key : val.getOwnMembers(env, true)) {
var el = fromJs(env, val.getMember(env, key), prev);
if (el == null) continue;
res.put(key, el);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
}

View File

@@ -0,0 +1,113 @@
package me.topchetoeu.j2s.runtime.debug;
import java.util.HashMap;
import java.util.WeakHashMap;
import me.topchetoeu.j2s.common.FunctionBody;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.common.environment.Key;
import me.topchetoeu.j2s.common.mapping.FunctionMap;
import me.topchetoeu.j2s.common.parsing.Filename;
import me.topchetoeu.j2s.runtime.Frame;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.functions.CodeFunction;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
public class DebugContext {
public static final Key<DebugContext> KEY = new Key<>();
public static final Key<Void> IGNORE = new Key<>();
private HashMap<Filename, String> sources;
private WeakHashMap<FunctionBody, FunctionMap> maps;
private DebugHandler debugger;
public synchronized boolean attachDebugger(DebugHandler debugger) {
if (this.debugger != null) return false;
if (sources != null) {
for (var source : sources.entrySet()) debugger.onSourceLoad(source.getKey(), source.getValue());
}
if (maps != null) {
for (var map : maps.entrySet()) debugger.onFunctionLoad(map.getKey(), map.getValue());
}
this.debugger = debugger;
return true;
}
public boolean detachDebugger(DebugHandler debugger) {
if (this.debugger != debugger) return false;
return detachDebugger();
}
public boolean detachDebugger() {
this.debugger = null;
return true;
}
public DebugHandler debugger() {
return debugger;
}
public FunctionMap getMap(FunctionBody func) {
if (maps == null) return null;
return maps.get(func);
}
public FunctionMap getMap(FunctionValue func) {
if (maps == null || !(func instanceof CodeFunction)) return null;
return getMap(((CodeFunction)func).body);
}
public FunctionMap getMapOrEmpty(FunctionBody func) {
if (maps == null) return FunctionMap.EMPTY;
var res = maps.get(func);
if (res == null) return FunctionMap.EMPTY;
else return res;
}
public FunctionMap getMapOrEmpty(FunctionValue func) {
if (maps == null || !(func instanceof CodeFunction)) return FunctionMap.EMPTY;
return getMapOrEmpty(((CodeFunction)func).body);
}
public void onFramePop(Environment env, Frame frame) {
if (debugger != null) debugger.onFramePop(env, frame);
}
public void onFramePush(Environment env, Frame frame) {
if (debugger != null) debugger.onFramePush(env, frame);
}
public boolean onInstruction(Environment env, Frame frame, Instruction instruction, Value returnVal, EngineException error, boolean caught) {
if (debugger != null) return debugger.onInstruction(env, frame, instruction, returnVal, error, caught);
else return false;
}
public boolean onInstruction(Environment env, Frame frame, Instruction instruction) {
if (debugger != null) return debugger.onInstruction(env, frame, instruction, null, null, false);
else return false;
}
public void onSource(Filename filename, String source) {
if (debugger != null) debugger.onSourceLoad(filename, source);
if (sources != null) sources.put(filename, source);
}
public void onFunctionLoad(FunctionBody func, FunctionMap map) {
if (maps != null) maps.put(func, map);
if (debugger != null) debugger.onFunctionLoad(func, map);
}
private DebugContext(boolean enabled) {
if (enabled) {
sources = new HashMap<>();
maps = new WeakHashMap<>();
}
}
public DebugContext() {
this(true);
}
public static boolean enabled(Environment exts) {
return exts != null && exts.hasNotNull(KEY) && !exts.has(IGNORE);
}
public static DebugContext get(Environment exts) {
if (enabled(exts)) return exts.get(KEY);
else return new DebugContext(false);
}
}

View File

@@ -0,0 +1,56 @@
package me.topchetoeu.j2s.runtime.debug;
import me.topchetoeu.j2s.common.FunctionBody;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.common.mapping.FunctionMap;
import me.topchetoeu.j2s.common.parsing.Filename;
import me.topchetoeu.j2s.runtime.Frame;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
public interface DebugHandler {
/**
* Called when a script has been loaded
* @param filename The name of the source
* @param source The name of the source
* @param breakpoints A set of all the breakpointable locations in this source
* @param map The source map associated with this file. null if this source map isn't mapped
*/
void onSourceLoad(Filename filename, String source);
/**
* Called when a function body has been loaded
* @param body The body loaded
* @param map The map of the function
*/
void onFunctionLoad(FunctionBody body, FunctionMap map);
/**
* Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
* This function might pause in order to await debugging commands.
* @param env The context of execution
* @param frame The frame in which execution is occuring
* @param instruction The instruction which was or will be executed
* @param returnVal The return value of the instruction, Values.NO_RETURN if none
* @param error The error that the instruction threw, null if none
* @param caught Whether or not the error has been caught
* @return Whether or not the frame should restart (currently does nothing)
*/
boolean onInstruction(Environment env, Frame frame, Instruction instruction, Value returnVal, EngineException error, boolean caught);
/**
* Called immediatly before a frame has been pushed on the frame stack.
* This function might pause in order to await debugging commands.
* @param env The context of execution
* @param frame The code frame which was pushed
*/
void onFramePush(Environment env, Frame frame);
/**
* Called immediatly after a frame has been popped out of the frame stack.
* This function might pause in order to await debugging commands.
* @param env The context of execution
* @param frame The code frame which was popped out
*/
void onFramePop(Environment env, Frame frame);
}

View File

@@ -0,0 +1,129 @@
package me.topchetoeu.j2s.runtime.exceptions;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.common.parsing.Location;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue.PrototypeProvider;
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
import me.topchetoeu.j2s.runtime.values.primitives.VoidValue;
public class EngineException extends RuntimeException {
public static class StackElement {
public final Location location;
public final String name;
public final Environment ext;
public boolean visible() {
return ext == null || !ext.get(Value.HIDE_STACK, false);
}
public String toString() {
if (name == null && location == null) return "(skipped)";
var res = "";
var loc = location;
if (loc != null) res += "at " + loc.toString() + " ";
if (name != null && !name.equals("")) res += "in " + name + " ";
return res.trim();
}
public StackElement(Environment ext, Location location, String name) {
if (name != null) name = name.trim();
if (name.equals("")) name = null;
if (ext == null) this.ext = null;
else this.ext = ext;
this.location = location;
this.name = name;
}
}
public final Value value;
public EngineException cause;
public Environment env = null;
public final List<StackElement> stackTrace = new ArrayList<>();
public EngineException add(Environment env, String name, Location location) {
var el = new StackElement(env, location, name);
// if (el.name == null && el.location == null) return this;
setEnvironment(env);
stackTrace.add(el);
return this;
}
public EngineException setCause(EngineException cause) {
this.cause = cause;
return this;
}
public EngineException setEnvironment(Environment env) {
if (this.env == null) this.env = env;
return this;
}
public String toString(Environment env) {
var ss = new StringBuilder();
try {
ss.append(value.toString(env)).append('\n');
}
catch (EngineException e) {
var name = value.getMember(env, "name");
var desc = value.getMember(env, "message");
if (name.isPrimitive() && desc.isPrimitive()) {
if (name instanceof VoidValue) ss.append("Error: ");
else ss.append(name.toString(env) + ": ");
if (desc instanceof VoidValue) ss.append("An error occurred");
else ss.append(desc.toString(env));
ss.append("\n");
}
else ss.append("[Error while stringifying]\n");
}
for (var line : stackTrace) {
if (line.visible()) ss.append(" ").append(line.toString()).append("\n");
}
if (cause != null) ss.append("Caused by ").append(cause.toString(env)).append('\n');
ss.deleteCharAt(ss.length() - 1);
return ss.toString();
}
private static ObjectValue err(String name, String msg, PrototypeProvider proto) {
var res = new ObjectValue();
res.setPrototype(proto);
if (msg == null) msg = "";
if (name != null) res.defineOwnField(Environment.empty(), "name", StringValue.of(name));
res.defineOwnField(Environment.empty(), "message", StringValue.of(msg));
return res;
}
public EngineException(Value error) {
super(error.toReadable(Environment.empty()));
this.value = error;
this.cause = null;
}
public static EngineException ofError(String name, String msg) {
return new EngineException(err(name, msg, env -> env.get(Value.ERROR_PROTO)));
}
public static EngineException ofError(String msg) {
return new EngineException(err(null, msg, env -> env.get(Value.ERROR_PROTO)));
}
public static EngineException ofSyntax(String msg) {
return new EngineException(err(null, msg, env -> env.get(Value.SYNTAX_ERR_PROTO)));
}
public static EngineException ofType(String msg) {
return new EngineException(err(null, msg, env -> env.get(Value.TYPE_ERR_PROTO)));
}
public static EngineException ofRange(String msg) {
return new EngineException(err(null, msg, env -> env.get(Value.RANGE_ERR_PROTO)));
}
}

View File

@@ -0,0 +1,72 @@
package me.topchetoeu.j2s.runtime.values;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
import me.topchetoeu.j2s.runtime.values.primitives.SymbolValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public final class KeyCache {
public final Value value;
private boolean isInt;
private int intCache;
private Double doubleCache;
private Boolean booleanCache;
private String stringCache;
public String toString(Environment env) {
if (stringCache != null) return stringCache;
else return stringCache = value.toString(env);
}
public double toNumber(Environment env) {
if (doubleCache == null) {
var res = value.toNumber(env);
isInt = res.isInt();
intCache = res.getInt();
doubleCache = res.getDouble();
}
return doubleCache;
}
public boolean isInt(Environment env) {
if (doubleCache == null) toNumber(env);
return isInt;
}
public int toInt(Environment env) {
if (doubleCache == null) toNumber(env);
return intCache;
}
public boolean toBoolean() {
if (booleanCache != null) return booleanCache;
else return booleanCache = value.toBoolean();
}
public SymbolValue toSymbol() {
if (value instanceof SymbolValue) return (SymbolValue)value;
else return null;
}
public boolean isSymbol() {
return value instanceof SymbolValue;
}
public KeyCache(Value value) {
this.value = value;
}
public KeyCache(String value) {
this.value = StringValue.of(value);
this.stringCache = value;
this.booleanCache = !value.equals("");
}
public KeyCache(int value) {
this.value = NumberValue.of(value);
this.isInt = true;
this.intCache = value;
this.doubleCache = (double)value;
this.booleanCache = value != 0;
}
public KeyCache(double value) {
this.value = NumberValue.of(value);
this.isInt = (int)value == value;
this.intCache = (int)value;
this.doubleCache = value;
this.booleanCache = value != 0;
}
}

View File

@@ -0,0 +1,191 @@
package me.topchetoeu.j2s.runtime.values;
import java.util.Optional;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.BoolValue;
public interface Member {
public static final class PropertyMember implements Member {
public final Value self;
public FunctionValue getter;
public FunctionValue setter;
public boolean configurable;
public boolean enumerable;
@Override public Value get(Environment env, Value self) {
if (getter != null) return getter.apply(env, self);
else return Value.UNDEFINED;
}
@Override public boolean set(Environment env, Value val, Value self) {
if (setter == null) return false;
setter.apply(env, self, val);
return true;
}
@Override public boolean configurable() { return configurable && self.getState().configurable; }
@Override public boolean enumerable() { return enumerable; }
public boolean reconfigure(
Environment env, Value self,
Optional<FunctionValue> get, Optional<FunctionValue> set,
Boolean enumerable, Boolean configurable
) {
if (this.configurable) {
// We will overlay the getters and setters of the new member
if (enumerable != null) this.enumerable = enumerable;
if (configurable != null) this.configurable = configurable;
if (get != null) this.getter = get.orElse(null);
if (set != null) this.setter = set.orElse(null);
return true;
}
else {
// We will pretend that a redefinition has occurred if the two members match exactly
if (configurable != null && this.configurable != configurable) return false;
if (enumerable != null && this.enumerable != enumerable) return false;
if (get != null && get.orElse(null) != getter) return false;
if (set != null && set.orElse(null) != setter) return false;
return true;
}
}
@Override public ObjectValue descriptor(Environment env, Value self) {
var res = new ObjectValue();
// Don't touch the ordering, as it's emulating V8
if (getter == null) res.defineOwnField(env, "getter", Value.UNDEFINED);
else res.defineOwnField(env, "getter", getter);
if (setter == null) res.defineOwnField(env, "setter", Value.UNDEFINED);
else res.defineOwnField(env, "setter", setter);
res.defineOwnField(env, "enumerable", BoolValue.of(enumerable));
res.defineOwnField(env, "configurable", BoolValue.of(configurable));
return res;
}
public PropertyMember(Value self, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
this.self = self;
this.getter = getter;
this.setter = setter;
this.configurable = configurable;
this.enumerable = enumerable;
}
public PropertyMember(Value self, Optional<FunctionValue> getter, Optional<FunctionValue> setter, Boolean configurable, Boolean enumerable) {
this.self = self;
this.getter = getter == null ? null : getter.orElse(null);
this.setter = setter == null ? null : setter.orElse(null);
this.configurable = configurable == null ? false : configurable;
this.enumerable = enumerable == null ? false : enumerable;
}
}
public static abstract class FieldMember implements Member {
private static class SimpleFieldMember extends FieldMember {
public Value value;
@Override public Value get(Environment env, Value self) { return value; }
@Override public boolean set(Environment env, Value val, Value self) {
if (!writable) return false;
value = val;
return true;
}
public SimpleFieldMember(Value self, Value value, Boolean configurable, Boolean enumerable, Boolean writable) {
super(self, configurable, enumerable, writable);
if (value == null) value = Value.UNDEFINED;
this.value = value;
}
}
public final Value self;
public boolean configurable;
public boolean enumerable;
public boolean writable;
@Override public final boolean configurable() { return configurable && self.getState().configurable; }
@Override public final boolean enumerable() { return enumerable; }
public final boolean writable() { return writable && self.getState().writable; }
public final boolean reconfigure(
Environment env, Value self, Value val,
Boolean writable, Boolean enumerable, Boolean configurable
) {
if (this.configurable) {
if (writable != null) this.writable = writable;
if (enumerable != null) this.enumerable = enumerable;
if (configurable != null) this.configurable = configurable;
if (val != null) {
// We will try to set a new value. However, the underlying field might be immutably readonly
// In such case, we will silently fail, since this is not covered by the specification
if (!set(env, val, self)) writable = false;
}
return true;
}
else {
// New field settings must be an exact match
if (configurable != null && this.configurable != configurable) return false;
if (enumerable != null && this.enumerable != enumerable) return false;
if (this.writable) {
// If the field isn't writable, the redefinition should be an exact match
if (writable != null && writable) return false;
if (val != null && val.equals(this.get(env, self))) return false;
return true;
}
else {
// Writable non-configurable fields may be made readonly or their values may be changed
if (writable != null) this.writable = writable;
if (!set(env, val, self)) writable = false;
return true;
}
}
}
@Override public ObjectValue descriptor(Environment env, Value self) {
var res = new ObjectValue();
res.defineOwnField(env, "value", get(env, self));
res.defineOwnField(env, "writable", BoolValue.of(writable));
res.defineOwnField(env, "enumerable", BoolValue.of(enumerable));
res.defineOwnField(env, "configurable", BoolValue.of(configurable));
return res;
}
public FieldMember(Value self, Boolean configurable, Boolean enumerable, Boolean writable) {
if (writable == null) writable = false;
if (enumerable == null) enumerable = false;
if (configurable == null) configurable = false;
this.self = self;
this.configurable = configurable;
this.enumerable = enumerable;
this.writable = writable;
}
public static FieldMember of(Value self, Value value) {
return new SimpleFieldMember(self, value, true, true, true);
}
public static FieldMember of(Value self, Value value, Boolean writable) {
return new SimpleFieldMember(self, value, true, true, writable);
}
public static FieldMember of(Value self, Value value, Boolean configurable, Boolean enumerable, Boolean writable) {
return new SimpleFieldMember(self, value, configurable, enumerable, writable);
}
}
public boolean configurable();
public boolean enumerable();
public ObjectValue descriptor(Environment env, Value self);
public Value get(Environment env, Value self);
public boolean set(Environment env, Value val, Value self);
}

View File

@@ -0,0 +1,671 @@
package me.topchetoeu.j2s.runtime.values;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import me.topchetoeu.j2s.common.SyntaxException;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.common.environment.Key;
import me.topchetoeu.j2s.runtime.EventLoop;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Member.PropertyMember;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
import me.topchetoeu.j2s.runtime.values.functions.NativeFunction;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.BoolValue;
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
import me.topchetoeu.j2s.runtime.values.primitives.SymbolValue;
import me.topchetoeu.j2s.runtime.values.primitives.VoidValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public abstract class Value {
public static enum State {
NORMAL(true, true, true),
NON_EXTENDABLE(false, true, true),
SEALED(false, false, true),
FROZEN(false, false, false);
public final boolean extendable;
public final boolean configurable;
public final boolean writable;
private State(boolean extendable, boolean configurable, boolean writable) {
this.extendable = extendable;
this.writable = writable;
this.configurable = configurable;
}
}
public static final Key<FunctionValue> REGEX_CONSTR = new Key<>();
public static final Key<Integer> MAX_STACK_COUNT = new Key<>();
public static final Key<Boolean> HIDE_STACK = new Key<>();
public static final Key<ObjectValue> BOOL_PROTO = new Key<>();
public static final Key<ObjectValue> NUMBER_PROTO = new Key<>();
public static final Key<ObjectValue> STRING_PROTO = new Key<>();
public static final Key<ObjectValue> SYMBOL_PROTO = new Key<>();
public static final Key<ObjectValue> OBJECT_PROTO = new Key<>();
public static final Key<ObjectValue> FUNCTION_PROTO = new Key<>();
public static final Key<ObjectValue> ARRAY_PROTO = new Key<>();
public static final Key<ObjectValue> INT8_ARR_PROTO = new Key<>();
public static final Key<ObjectValue> INT32_ARR_PROTO = new Key<>();
public static final Key<ObjectValue> UINT8_ARR_PROTO = new Key<>();
public static final Key<ObjectValue> ERROR_PROTO = new Key<>();
public static final Key<ObjectValue> SYNTAX_ERR_PROTO = new Key<>();
public static final Key<ObjectValue> TYPE_ERR_PROTO = new Key<>();
public static final Key<ObjectValue> RANGE_ERR_PROTO = new Key<>();
public static final Key<Value> GLOBAL = new Key<>();
public static final Key<Map<String, Value>> INTRINSICS = new Key<>();
public static final VoidValue UNDEFINED = new VoidValue("undefined", "undefined");
public static final VoidValue NULL = new VoidValue("null", "object");
public abstract StringValue type();
public abstract boolean isPrimitive();
public final boolean isNaN() {
return this == NumberValue.NAN || this instanceof NumberValue num && Double.isNaN(num.getDouble());
}
public Value apply(Environment env, Value self, Value ...args) {
throw EngineException.ofType("Value is not a function");
}
public Value construct(Environment env, Value target, Value ...args) {
throw EngineException.ofType("Value is not a constructor");
}
public final Value constructNoSelf(Environment env, Value ...args) {
return this.construct(env, this, args);
}
public abstract Value toPrimitive(Environment env);
public abstract NumberValue toNumber(Environment env);
public abstract String toString(Environment env);
public abstract boolean toBoolean();
public final boolean isInstanceOf(Environment env, Value proto) {
for (var val = getPrototype(env); val != null; val = val.getPrototype(env)) {
if (val.equals(proto)) return true;
}
return false;
}
public abstract Member getOwnMember(Environment env, KeyCache key);
public abstract Set<String> getOwnMembers(Environment env, boolean onlyEnumerable);
public abstract Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable);
public abstract boolean defineOwnField(Environment env, KeyCache key, Value val, Boolean writable, Boolean enumerable, Boolean configurable);
public abstract boolean defineOwnProperty(Environment env, KeyCache key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable);
public abstract boolean deleteOwnMember(Environment env, KeyCache key);
public abstract ObjectValue getPrototype(Environment env);
public abstract boolean setPrototype(Environment env, ObjectValue val);
public abstract State getState();
public abstract void preventExtensions();
public abstract void seal();
public abstract void freeze();
public final Member getOwnMember(Environment env, Value key) {
return getOwnMember(env, new KeyCache(key));
}
public final Member getOwnMember(Environment env, String key) {
return getOwnMember(env, new KeyCache(key));
}
public final Member getOwnMember(Environment env, int key) {
return getOwnMember(env, new KeyCache(key));
}
public final Member getOwnMember(Environment env, double key) {
return getOwnMember(env, new KeyCache(key));
}
public final boolean defineOwnProperty(Environment env, Value key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable) {
return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable);
}
public final boolean defineOwnProperty(Environment env, String key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable) {
return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable);
}
public final boolean defineOwnProperty(Environment env, int key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable) {
return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable);
}
public final boolean defineOwnProperty(Environment env, double key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable) {
return defineOwnProperty(env, new KeyCache(key), get, set, enumerable, configurable);
}
public final boolean defineOwnField(Environment env, Value key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) {
return defineOwnField(env, new KeyCache(key), val, writable, enumerable, configurable);
}
public final boolean defineOwnField(Environment env, String key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) {
return defineOwnField(env, new KeyCache(key), val, writable, enumerable, configurable);
}
public final boolean defineOwnField(Environment env, int key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) {
return defineOwnField(env, new KeyCache(key), val, writable, enumerable, configurable);
}
public final boolean defineOwnField(Environment env, double key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) {
return defineOwnField(env, new KeyCache(key), val, writable, enumerable, configurable);
}
public final boolean defineOwnField(Environment env, KeyCache key, Value val) {
return defineOwnField(env, key, val, true, true, true);
}
public final boolean defineOwnField(Environment env, Value key, Value val) {
return defineOwnField(env, new KeyCache(key), val);
}
public final boolean defineOwnField(Environment env, String key, Value val) {
return defineOwnField(env, new KeyCache(key), val);
}
public final boolean defineOwnField(Environment env, int key, Value val) {
return defineOwnField(env, new KeyCache(key), val);
}
public final boolean defineOwnField(Environment env, double key, Value val) {
return defineOwnField(env, new KeyCache(key), val);
}
public final boolean deleteOwnMember(Environment env, Value key) {
return deleteOwnMember(env, new KeyCache(key));
}
public final boolean deleteOwnMember(Environment env, String key) {
return deleteOwnMember(env, new KeyCache(key));
}
public final boolean deleteOwnMember(Environment env, int key) {
return deleteOwnMember(env, new KeyCache(key));
}
public final boolean deleteOwnMember(Environment env, double key) {
return deleteOwnMember(env, new KeyCache(key));
}
public final Value getMemberOrNull(Environment env, KeyCache key) {
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
var member = obj.getOwnMember(env, key);
if (member != null) return member.get(env, this);
}
return null;
}
public final Value getMemberOrNull(Environment env, Value key) {
return getMemberOrNull(env, new KeyCache(key));
}
public final Value getMemberOrNull(Environment env, String key) {
return getMemberOrNull(env, new KeyCache(key));
}
public final Value getMemberOrNull(Environment env, int key) {
return getMemberOrNull(env, new KeyCache(key));
}
public final Value getMemberOrNull(Environment env, double key) {
return getMemberOrNull(env, new KeyCache(key));
}
public final Value getMember(Environment env, KeyCache key) {
var res = getMemberOrNull(env, key);
if (res != null) return res;
else return Value.UNDEFINED;
}
public final Value getMember(Environment env, Value key) {
return getMember(env, new KeyCache(key));
}
public final Value getMember(Environment env, String key) {
return getMember(env, new KeyCache(key));
}
public final Value getMember(Environment env, int key) {
return getMember(env, new KeyCache(key));
}
public final Value getMember(Environment env, double key) {
return getMember(env, new KeyCache(key));
}
public final boolean setMember(Environment env, KeyCache key, Value val) {
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
var member = obj.getOwnMember(env, key);
if (member != null && (member instanceof PropertyMember || obj == this)) {
if (member.set(env, val, this)) {
if (val instanceof FunctionValue && !key.isSymbol()) ((FunctionValue)val).setName(key.toString(env));
return true;
}
else return false;
}
}
if (defineOwnField(env, key, val)) {
if (val instanceof FunctionValue func) {
if (key.isSymbol()) func.setName(key.toSymbol().toString());
else func.setName(key.toString(env));
}
return true;
}
else return false;
}
public final boolean setMember(Environment env, Value key, Value val) {
return setMember(env, new KeyCache(key), val);
}
public final boolean setMember(Environment env, String key, Value val) {
return setMember(env, new KeyCache(key), val);
}
public final boolean setMember(Environment env, int key, Value val) {
return setMember(env, new KeyCache(key), val);
}
public final boolean setMember(Environment env, double key, Value val) {
return setMember(env, new KeyCache(key), val);
}
public final boolean setMemberIfExists(Environment env, KeyCache key, Value val) {
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
var member = obj.getOwnMember(env, key);
if (member != null) {
if (member.set(env, val, obj)) {
if (!key.isSymbol() && val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env));
return true;
}
else return false;
}
}
return false;
}
public final boolean setMemberIfExists(Environment env, Value key, Value val) {
return setMemberIfExists(env, new KeyCache(key), val);
}
public final boolean setMemberIfExists(Environment env, String key, Value val) {
return setMemberIfExists(env, new KeyCache(key), val);
}
public final boolean setMemberIfExists(Environment env, int key, Value val) {
return setMemberIfExists(env, new KeyCache(key), val);
}
public final boolean setMemberIfExists(Environment env, double key, Value val) {
return setMemberIfExists(env, new KeyCache(key), val);
}
public final boolean hasMember(Environment env, KeyCache key, boolean own) {
for (Value obj = this; obj != null; obj = obj.getPrototype(env)) {
if (obj.getOwnMember(env, key) != null) return true;
if (own) return false;
}
return false;
}
public final boolean hasMember(Environment env, Value key, boolean own) {
return hasMember(env, new KeyCache(key), own);
}
public final boolean hasMember(Environment env, String key, boolean own) {
return hasMember(env, new KeyCache(key), own);
}
public final boolean hasMember(Environment env, int key, boolean own) {
return hasMember(env, new KeyCache(key), own);
}
public final boolean hasMember(Environment env, double key, boolean own) {
return hasMember(env, new KeyCache(key), own);
}
public final boolean deleteMember(Environment env, KeyCache key) {
if (!hasMember(env, key, true)) return true;
return deleteOwnMember(env, key);
}
public final boolean deleteMember(Environment env, Value key) {
return deleteMember(env, new KeyCache(key));
}
public final boolean deleteMember(Environment env, String key) {
return deleteMember(env, new KeyCache(key));
}
public final boolean deleteMember(Environment env, int key) {
return deleteMember(env, new KeyCache(key));
}
public final boolean deleteMember(Environment env, double key) {
return deleteMember(env, new KeyCache(key));
}
public final Set<String> getMembers(Environment env, boolean own, boolean onlyEnumerable) {
var res = new LinkedHashSet<String>();
var protos = new ArrayList<Value>();
for (var proto = this; proto != null; proto = proto.getPrototype(env)) {
protos.add(proto);
if (own) break;
}
Collections.reverse(protos);
for (var proto : protos) {
res.addAll(proto.getOwnMembers(env, onlyEnumerable));
}
return res;
}
public final Set<SymbolValue> getSymbolMembers(Environment env, boolean own, boolean onlyEnumerable) {
var res = new LinkedHashSet<SymbolValue>();
var protos = new ArrayList<Value>();
for (var proto = this; proto != null; proto = proto.getPrototype(env)) {
protos.add(proto);
if (own) break;
}
Collections.reverse(protos);
for (var proto : protos) {
res.addAll(proto.getOwnSymbolMembers(env, onlyEnumerable));
}
return res;
}
public final Value getMemberPath(Environment env, String ...path) {
var res = this;
for (var key : path) res = res.getMember(env, key);
return res;
}
public final Value getMemberPath(Environment env, Value ...path) {
var res = this;
for (var key : path) res = res.getMember(env, key);
return res;
}
public final ObjectValue getMemberDescriptor(Environment env, Value key) {
var member = getOwnMember(env, new KeyCache(key));
if (member != null) return member.descriptor(env, this);
else return null;
}
public Iterable<Object> toIterable(Environment env) {
return () -> {
if (!(this instanceof FunctionValue)) return Collections.emptyIterator();
var func = (FunctionValue)this;
return new Iterator<Object>() {
private Object value = null;
public boolean consumed = true;
private FunctionValue supplier = func;
private void loadNext() {
if (supplier == null) value = null;
else if (consumed) {
var curr = supplier.apply(env, Value.UNDEFINED);
if (curr == null) { supplier = null; value = null; }
if (curr.getMember(env, StringValue.of("done")).toBoolean()) { supplier = null; value = null; }
else {
this.value = curr.getMember(env, StringValue.of("value"));
consumed = false;
}
}
}
@Override public boolean hasNext() {
loadNext();
return supplier != null;
}
@Override public Object next() {
loadNext();
var res = value;
value = null;
consumed = true;
return res;
}
};
};
}
public void callWith(Environment env, Iterable<? extends Value> it) {
for (var el : it) {
this.apply(env, Value.UNDEFINED, el);
}
}
public void callWithAsync(Environment env, Iterable<? extends Value> it, boolean async) {
for (var el : it) {
env.get(EventLoop.KEY).pushMsg(() -> this.apply(env, Value.UNDEFINED, el), true);
}
}
/** @internal */
public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
return Arrays.asList(toString(env));
}
public final String toReadable(Environment ext) {
return String.join("\n", toReadableLines(ext, new HashSet<>()));
}
public static final Value global(Environment env) {
return env.initFrom(GLOBAL, () -> new ObjectValue());
}
public static final Map<String, Value> intrinsics(Environment env) {
return env.initFrom(INTRINSICS, () -> new HashMap<>());
}
public static FunctionValue fromIterator(Environment ext, Iterable<? extends Value> iterable) {
var it = iterable.iterator();
return new NativeFunction("", args -> {
var obj = new ObjectValue();
if (!it.hasNext()) obj.defineOwnField(args.env, "done", BoolValue.TRUE);
else obj.defineOwnField(args.env, "value", it.next());
return obj;
});
}
public static final boolean lessOrEqual(Environment env, Value a, Value b) {
a = a.toPrimitive(env);
b = b.toPrimitive(env);
if (a instanceof StringValue aStr && b instanceof StringValue bStr) {
return aStr.value.compareTo(bStr.value) <= 0;
}
else {
var na = a.toNumber(env);
var nb = b.toNumber(env);
if (na.isLong() && nb.isLong()) return na.getLong() <= nb.getLong();
else return na.getDouble() <= nb.getDouble();
}
}
public static final boolean greaterOrEqual(Environment env, Value a, Value b) {
a = a.toPrimitive(env);
b = b.toPrimitive(env);
if (a instanceof StringValue aStr && b instanceof StringValue bStr) {
return aStr.value.compareTo(bStr.value) >= 0;
}
else {
var na = a.toNumber(env);
var nb = b.toNumber(env);
if (na.isLong() && nb.isLong()) return na.getLong() >= nb.getLong();
else return na.getDouble() >= nb.getDouble();
}
}
public static final boolean less(Environment env, Value a, Value b) {
a = a.toPrimitive(env);
b = b.toPrimitive(env);
if (a instanceof StringValue aStr && b instanceof StringValue bStr) {
return aStr.value.compareTo(bStr.value) < 0;
}
else {
var na = a.toNumber(env);
var nb = b.toNumber(env);
if (na.isLong() && nb.isLong()) return na.getLong() < nb.getLong();
else return na.getDouble() < nb.getDouble();
}
}
public static final boolean greater(Environment env, Value a, Value b) {
a = a.toPrimitive(env);
b = b.toPrimitive(env);
if (a instanceof StringValue aStr && b instanceof StringValue bStr) {
return aStr.value.compareTo(bStr.value) > 0;
}
else {
var na = a.toNumber(env);
var nb = b.toNumber(env);
if (na.isLong() && nb.isLong()) return na.getLong() > nb.getLong();
else return na.getDouble() > nb.getDouble();
}
}
public static final Value add(Environment env, Value a, Value b) {
a = a.toPrimitive(env);
b = b.toPrimitive(env);
if (a instanceof StringValue || b instanceof StringValue) {
return StringValue.of(a.toString(env) + b.toString(env));
}
else {
var na = a.toNumber(env);
var nb = b.toNumber(env);
if (na.isInt() && nb.isInt()) return NumberValue.of(na.getInt() + nb.getInt());
else return NumberValue.of(na.getDouble() + nb.getDouble());
}
}
public static final NumberValue subtract(Environment env, Value a, Value b) {
var na = a.toNumber(env);
var nb = b.toNumber(env);
if (na.isInt() && nb.isInt()) return NumberValue.of(na.getInt() - nb.getInt());
else return NumberValue.of(na.getDouble() - nb.getDouble());
}
public static final NumberValue multiply(Environment env, Value a, Value b) {
var na = a.toNumber(env);
var nb = b.toNumber(env);
if (na.isInt() && nb.isInt()) return NumberValue.of(na.getInt() * nb.getInt());
else return NumberValue.of(na.getDouble() * nb.getDouble());
}
public static final NumberValue divide(Environment env, Value a, Value b) {
var na = a.toNumber(env);
var nb = b.toNumber(env);
if (na.isInt() && nb.isInt()) {
var ia = na.getInt();
var ib = nb.getInt();
if (ib == 0) {
if (ia == 0) return NumberValue.NAN;
else if (ia > 0) return NumberValue.of(Double.POSITIVE_INFINITY);
else return NumberValue.of(Double.NEGATIVE_INFINITY);
}
else if (ia % ib != 0) return NumberValue.of((double)ia / ib);
else return NumberValue.of(ia / ib);
}
else return NumberValue.of(na.getDouble() / nb.getDouble());
}
public static final NumberValue modulo(Environment env, Value a, Value b) {
var na = a.toNumber(env);
var nb = b.toNumber(env);
if (na.isInt() && nb.isInt()) {
var ia = na.getInt();
var ib = nb.getInt();
if (ib == 0) return NumberValue.NAN;
else return NumberValue.of(ia % ib);
}
else return NumberValue.of(na.getDouble() % nb.getDouble());
}
public static final NumberValue negative(Environment env, Value a) {
var na = a.toNumber(env);
if (na.isInt()) return NumberValue.of(-na.getInt());
else return NumberValue.of(-na.getDouble());
}
public static final NumberValue and(Environment env, Value a, Value b) {
return NumberValue.of(a.toNumber(env).getInt() & b.toNumber(env).getInt());
}
public static final NumberValue or(Environment env, Value a, Value b) {
return NumberValue.of(a.toNumber(env).getInt() | b.toNumber(env).getInt());
}
public static final NumberValue xor(Environment env, Value a, Value b) {
return NumberValue.of(a.toNumber(env).getInt() ^ b.toNumber(env).getInt());
}
public static final NumberValue bitwiseNot(Environment env, Value a) {
return NumberValue.of(~a.toNumber(env).getInt());
}
public static final NumberValue shiftLeft(Environment env, Value a, Value b) {
return NumberValue.of(a.toNumber(env).getInt() << b.toNumber(env).getInt());
}
public static final NumberValue shiftRight(Environment env, Value a, Value b) {
return NumberValue.of(a.toNumber(env).getInt() >> b.toNumber(env).getInt());
}
public static final NumberValue unsignedShiftRight(Environment env, Value a, Value b) {
long _a = a.toNumber(env).getLong() & 0xFFFFFFFF;
long _b = b.toNumber(env).getLong() & 0xFFFFFFFF;
if (_a < 0) _a += 0x100000000l;
if (_b < 0) _b += 0x100000000l;
return NumberValue.of(_a >>> _b);
}
public static final boolean looseEqual(Environment env, Value a, Value b) {
// In loose equality, null is equivalent to undefined
if (a instanceof VoidValue || b instanceof VoidValue) return a instanceof VoidValue && b instanceof VoidValue;
// If both are objects, just compare their references
if (!a.isPrimitive() && !b.isPrimitive()) return a.equals(b);
// Convert values to primitives
a = a.toPrimitive(env);
b = b.toPrimitive(env);
// Compare symbols by reference
if (a instanceof SymbolValue || b instanceof SymbolValue) return a.equals(b);
// Compare booleans as numbers
if (a instanceof BoolValue || b instanceof BoolValue) return a.toNumber(env).equals(b.toNumber(env));
// Comparse numbers as numbers
if (a instanceof NumberValue || b instanceof NumberValue) return a.toNumber(env).equals(b.toNumber(env));
// Default to strings
return a.toString(env).equals(b.toString(env));
}
public static final String errorToReadable(Environment env, RuntimeException err, String prefix) {
prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
if (err instanceof EngineException ee) {
if (env == null) env = ee.env;
try {
return prefix + " " + ee.toString(env);
}
catch (EngineException ex) {
return prefix + " " + ee.value.toReadable(env);
}
}
else if (err instanceof SyntaxException syntax) {
var newErr = EngineException.ofSyntax(syntax.msg);
newErr.add(null, syntax.loc.filename() + "", syntax.loc);
return errorToReadable(env, newErr, prefix);
}
else if (err.getCause() instanceof InterruptedException) return "";
else {
var str = new ByteArrayOutputStream();
err.printStackTrace(new PrintStream(str));
return prefix + " internal error " + str.toString();
}
}
}

View File

@@ -0,0 +1,57 @@
package me.topchetoeu.j2s.runtime.values.functions;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.UserValue;
public class Arguments {
public final Value self;
public final Value[] args;
public final Environment env;
public final boolean isNew;
public final <T extends Value> T setTargetProto(T obj) {
if (!self.isPrimitive()) {
var proto = self.getMember(env, "prototype");
if (proto instanceof ObjectValue objProto) self.setPrototype(env, objProto);
else if (proto == Value.NULL) self.setPrototype(env, null);
}
return obj;
}
public int n() {
return args.length;
}
public boolean has(int i) {
return i == -1 || i >= 0 && i < args.length;
}
public Value self() {
return get(-1);
}
public <T> T self(Class<T> clazz) {
return UserValue.unwrap(clazz, self);
}
public Value get(int i) {
if (i >= args.length || i < -1) return Value.UNDEFINED;
else if (i == -1) return self;
else return args[i];
}
public <T> T get(Class<T> clazz, int i) {
return UserValue.unwrap(clazz, get(i));
}
public Value getOrDefault(int i, Value def) {
if (i < -1 || i >= args.length) return def;
else return get(i);
}
public Arguments(Environment env, boolean isNew, Value thisArg, Value... args) {
this.env = env;
this.args = args;
this.self = thisArg;
this.isNew = isNew;
}
}

View File

@@ -0,0 +1,54 @@
package me.topchetoeu.j2s.runtime.values.functions;
import me.topchetoeu.j2s.common.FunctionBody;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.Frame;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
public final class CodeFunction extends FunctionValue {
public final FunctionBody body;
public final Value[][] captures;
public Environment env;
private Value onCall(Frame frame) {
frame.onPush();
try {
while (true) {
var res = frame.next(null, null, null);
if (res != null) return res;
}
}
finally {
frame.onPop();
}
}
@Override protected Value onApply(Environment ext, Value self, Value... args) {
var frame = new Frame(env, false, null, self, args, this);
var res = onCall(frame);
return res;
}
@Override protected Value onConstruct(Environment ext, Value target, Value... args) {
var self = new ObjectValue();
var proto = target.getMember(env, "prototype");
if (proto instanceof ObjectValue) self.setPrototype(env, (ObjectValue)proto);
else if (proto == Value.NULL) self.setPrototype(env, null);
var frame = new Frame(env, true, target, self, args, this);
var ret = onCall(frame);
if (ret == Value.UNDEFINED || ret.isPrimitive()) return self;
return ret;
}
public CodeFunction(Environment env, String name, FunctionBody body, Value[][] captures) {
super(name, body.length);
this.captures = captures;
this.env = env;
this.body = body;
}
}

View File

@@ -0,0 +1,121 @@
package me.topchetoeu.j2s.runtime.values.functions;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.debug.DebugContext;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.KeyCache;
import me.topchetoeu.j2s.runtime.values.Member;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.Member.FieldMember;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public abstract class FunctionValue extends ObjectValue {
public String name = "";
public int length;
public Value prototype = new ObjectValue();
public boolean enableApply = true;
public boolean enableConstruct = true;
private final FieldMember nameField = new FieldMember(this, true, false, false) {
@Override public Value get(Environment env, Value self) {
if (name == null) return StringValue.of("");
return StringValue.of(name);
}
@Override public boolean set(Environment env, Value val, Value self) {
name = val.toString(env);
return true;
}
};
private final FieldMember lengthField = new FieldMember(this, true, false, false) {
@Override public Value get(Environment env, Value self) {
return NumberValue.of(length);
}
@Override public boolean set(Environment env, Value val, Value self) {
return false;
}
};
private final FieldMember prototypeField = new FieldMember(this, false, false, true) {
@Override public Value get(Environment env, Value self) {
return prototype;
}
@Override public boolean set(Environment env, Value val, Value self) {
prototype = val;
return true;
}
};
protected abstract Value onApply(Environment ext, Value thisArg, Value ...args);
protected abstract Value onConstruct(Environment ext, Value target, Value ...args);
@Override public String toString() { return String.format("function %s(...)", name); }
@Override public Value apply(Environment env, Value self, Value... args) {
if (!enableApply) throw EngineException.ofType("Function cannot be applied");
return onApply(env, self, args);
}
@Override public Value construct(Environment env, Value target, Value... args) {
if (!enableConstruct) throw EngineException.ofType("Function cannot be constructed");
return onConstruct(env, target, args);
}
@Override public Member getOwnMember(Environment env, KeyCache key) {
if (!key.isSymbol()) switch (key.toString(env)) {
case "length": return lengthField;
case "name": return nameField;
case "prototype": return prototypeField;
}
return super.getOwnMember(env, key);
}
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
if (!key.isSymbol()) switch (key.toString(env)) {
case "length":
length = 0;
return true;
case "name":
name = "";
return true;
case "prototype":
return false;
}
return super.deleteOwnMember(env, key);
}
@Override public StringValue type() { return StringValue.of("function"); }
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
var dbg = DebugContext.get(env);
var res = new StringBuilder(this.toString());
var loc = dbg.getMapOrEmpty(this).start();
if (loc != null) res.append(" @ " + loc);
var lines = new LinkedList<String>(super.toReadableLines(env, passed));
if (lines.size() == 1 && lines.getFirst().equals("{}")) return Arrays.asList(res.toString());
lines.set(0, res.toString() + " " + lines.getFirst());
return lines;
}
public void setName(String val) {
if (this.name == null || this.name.equals("")) this.name = val;
}
public FunctionValue(String name, int length) {
setPrototype(FUNCTION_PROTO);
if (name == null) name = "";
this.length = length;
this.name = name;
prototype.defineOwnField(null, "constructor", this, true, false, true);
}
}

View File

@@ -0,0 +1,28 @@
package me.topchetoeu.j2s.runtime.values.functions;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.values.Value;
public final class NativeFunction extends FunctionValue {
public static interface NativeFunctionRunner {
Value run(Arguments args);
}
public final NativeFunctionRunner action;
@Override protected Value onApply(Environment env, Value self, Value... args) {
return action.run(new Arguments(env, false, self, args));
}
@Override protected Value onConstruct(Environment env, Value target, Value... args) {
return action.run(new Arguments(env, true, target, args));
}
public NativeFunction(String name, NativeFunctionRunner action) {
super(name, 0);
this.action = action;
}
public NativeFunction(NativeFunctionRunner action) {
super("", 0);
this.action = action;
}
}

View File

@@ -0,0 +1,205 @@
package me.topchetoeu.j2s.runtime.values.objects;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.KeyCache;
import me.topchetoeu.j2s.runtime.values.Member;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.Member.FieldMember;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public abstract class ArrayLikeValue extends ObjectValue {
private static class IndexField extends FieldMember {
private int i;
private ArrayLikeValue arr;
@Override public Value get(Environment env, Value self) {
return arr.get(i);
}
@Override public boolean set(Environment env, Value val, Value self) {
return arr.set(env, i, val);
}
public IndexField(int i, ArrayLikeValue arr) {
super(arr, true, true, true);
this.arr = arr;
this.i = i;
}
}
private final FieldMember lengthField = new FieldMember(this, false, false, true) {
@Override public Value get(Environment env, Value self) {
return NumberValue.of(size());
}
@Override public boolean set(Environment env, Value val, Value self) {
var num = val.toNumber(env);
if (!num.isInt()) throw EngineException.ofRange("Invalid array length");
var i = num.getInt();
if (i < 0) throw EngineException.ofRange("Invalid array length");
return setSize(i);
}
};
public abstract int size();
public abstract boolean setSize(int val);
public abstract Value get(int i);
public abstract boolean set(Environment env, int i, Value val);
public abstract boolean has(int i);
public abstract boolean remove(int i);
@Override public Member getOwnMember(Environment env, KeyCache key) {
var res = super.getOwnMember(env, key);
if (res != null) return res;
if (key.isSymbol()) return null;
var num = key.toNumber(env);
var i = key.toInt(env);
if (i == num && i >= 0 && i < size() && has(i)) return new IndexField(i, this);
else if (!key.isSymbol() && key.toString(env).equals("length")) return lengthField;
else return null;
}
@Override public boolean defineOwnField(
Environment env, KeyCache key, Value val,
Boolean writable, Boolean enumerable, Boolean configurable
) {
if (!getState().writable) return false;
if (!key.isSymbol()) {
var num = key.toNumber(env);
var i = key.toInt(env);
if (i == num) {
if (writable == null) writable = true;
if (configurable == null) configurable = true;
if (enumerable == null) enumerable = true;
if (writable && configurable && enumerable) {
if (!getState().extendable && !has(i)) return false;
if (set(env, i, val)) return true;
}
}
}
return super.defineOwnField(env, key, val, writable, enumerable, configurable);
}
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
if (!super.deleteOwnMember(env, key)) return false;
if (key.isSymbol()) return true;
var num = key.toNumber(env);
var i = key.toInt(env);
if (i == num && i >= 0 && i < size()) return remove(i);
else return true;
}
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
var res = new LinkedHashSet<String>();
res.addAll(super.getOwnMembers(env, onlyEnumerable));
for (var i = 0; i < size(); i++) {
if (has(i)) res.add(i + "");
}
if (!onlyEnumerable) res.add("length");
return res;
}
private LinkedList<String> toReadableBase(Environment env, HashSet<ObjectValue> passed, HashSet<String> ignoredKeys) {
var stringified = new LinkedList<LinkedList<String>>();
passed.add(this);
var emptyN = 0;
for (int i = 0; i < size(); i++) {
if (has(i)) {
String emptyStr = null;
if (emptyN == 1) emptyStr = "<empty>";
else if (emptyN > 1) emptyStr = "<empty x " + emptyN + ">";
if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr + ",")));
emptyN = 0;
stringified.add(new LinkedList<>(get(i).toReadableLines(env, passed)));
ignoredKeys.add(i + "");
var entry = stringified.getLast();
entry.set(entry.size() - 1, entry.getLast() + ",");
}
else {
emptyN++;
}
}
String emptyStr = null;
if (emptyN == 1) emptyStr = "<empty>";
else if (emptyN > 1) emptyStr = "<empty x " + emptyN + ">";
if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr)));
else if (stringified.size() > 0) {
var lastEntry = stringified.getLast();
lastEntry.set(lastEntry.size() - 1, lastEntry.getLast().substring(0, lastEntry.getLast().length() - 1));
}
passed.remove(this);
if (stringified.size() == 0) return new LinkedList<>(Arrays.asList("[]"));
var concat = new StringBuilder();
for (var entry : stringified) {
// We make a one-liner only when all members are one-liners
if (entry.size() != 1) {
concat = null;
break;
}
if (concat.length() != 0) concat.append(" ");
concat.append(entry.get(0));
}
// We don't want too long one-liners
if (concat != null && concat.length() < 160) return new LinkedList<>(Arrays.asList("[" + concat.toString() + "]"));
var res = new LinkedList<String>();
res.add("[");
for (var entry : stringified) {
for (var line : entry) {
res.add(" " + line);
}
}
res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1));
res.add("]");
return res;
}
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
var ignored = new HashSet<String>();
var lines = toReadableBase(env, passed, ignored);
var superLines = new LinkedList<String>(super.toReadableLines(env, passed, ignored));
if (superLines.size() == 1 && superLines.getFirst().equals("{}")) return lines;
lines.set(lines.size() - 1, lines.getLast() + " " + superLines.getFirst());
lines.addAll(superLines.subList(1, superLines.size()));
return lines;
}
}

View File

@@ -0,0 +1,154 @@
package me.topchetoeu.j2s.runtime.values.objects;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.stream.Stream;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.primitives.VoidValue;
public class ArrayValue extends ArrayLikeValue implements Iterable<Value> {
private Value[] values;
private int size;
private Value[] alloc(int index) {
index++;
if (index < values.length) return values;
if (index < values.length * 2) index = values.length * 2;
var arr = new Value[index];
System.arraycopy(values, 0, arr, 0, values.length);
return values = arr;
}
public int size() { return size; }
public boolean setSize(int val) {
if (val < 0) return false;
if (size > val) shrink(size - val);
else {
values = alloc(val);
size = val;
}
return true;
}
@Override public Value get(int i) {
if (i < 0 || i >= size) return null;
var res = values[i];
if (res == null) return Value.UNDEFINED;
else return res;
}
@Override public boolean set(Environment env, int i, Value val) {
if (i < 0) return false;
alloc(i)[i] = val;
if (i >= size) size = i + 1;
return true;
}
@Override public boolean has(int i) {
return i >= 0 && i < size && values[i] != null;
}
@Override public boolean remove(int i) {
if (i < 0 || i >= values.length) return true;
values[i] = null;
return true;
}
public void shrink(int n) {
if (n >= values.length) {
values = new Value[16];
size = 0;
}
else {
for (int i = 0; i < n; i++) values[--size] = null;
}
}
public Value[] toArray() {
var res = new Value[size];
copyTo(res, 0, 0, size);
return res;
}
public Stream<Value> stream() {
return Arrays.stream(toArray());
}
public void copyTo(Value[] arr, int sourceStart, int destStart, int count) {
var nullFill = Math.max(0, arr.length - size - destStart);
count -= nullFill;
System.arraycopy(values, sourceStart, arr, destStart, count);
Arrays.fill(arr, count, nullFill + count, null);
}
public void copyTo(ArrayValue arr, int sourceStart, int destStart, int count) {
if (arr == this) {
move(sourceStart, destStart, count);
return;
}
arr.copyFrom(values, sourceStart, destStart, count);
}
public void copyFrom(Value[] arr, int sourceStart, int destStart, int count) {
alloc(destStart + count);
System.arraycopy(arr, sourceStart, values, destStart, count);
if (size < destStart + count) size = destStart + count;
}
public void move(int srcI, int dstI, int n) {
values = alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n);
if (dstI + n >= size) size = dstI + n;
}
public void sort(Comparator<Value> comparator) {
Arrays.sort(values, 0, size, (a, b) -> {
var _a = 0;
var _b = 0;
if (a == null) _a = 2;
if (a instanceof VoidValue) _a = 1;
if (b == null) _b = 2;
if (b instanceof VoidValue) _b = 1;
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
return comparator.compare(a, b);
});
}
@Override public Iterator<Value> 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 ArrayValue() {
this(16);
}
public ArrayValue(int cap) {
setPrototype(ARRAY_PROTO);
values = new Value[Math.min(cap, 16)];
size = 0;
}
public ArrayValue(Value ...values) {
this();
copyFrom(values, 0, 0, values.length);
}
public static ArrayValue of(Collection<? extends Value> values) {
return new ArrayValue(values.toArray(new Value[0]));
}
}

View File

@@ -0,0 +1,333 @@
package me.topchetoeu.j2s.runtime.values.objects;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.common.environment.Key;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.KeyCache;
import me.topchetoeu.j2s.runtime.values.Member;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.Member.FieldMember;
import me.topchetoeu.j2s.runtime.values.Member.PropertyMember;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
import me.topchetoeu.j2s.runtime.values.primitives.SymbolValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public class ObjectValue extends Value {
public static interface PrototypeProvider {
public ObjectValue get(Environment env);
}
public static class Property {
public final FunctionValue getter;
public final FunctionValue setter;
public Property(FunctionValue getter, FunctionValue setter) {
this.getter = getter;
this.setter = setter;
}
}
protected PrototypeProvider prototype;
private HashMap<String, FieldMember> fields = new HashMap<>();
private HashMap<SymbolValue, FieldMember> symbolFields = new HashMap<>();
private HashMap<String, PropertyMember> properties = new HashMap<>();
private HashMap<SymbolValue, PropertyMember> symbolProperties = new HashMap<>();
private LinkedHashMap<String, Boolean> keys = new LinkedHashMap<>();
private LinkedHashMap<SymbolValue, Boolean> symbols = new LinkedHashMap<>();
@Override public boolean isPrimitive() { return false; }
@Override public Value toPrimitive(Environment env) {
if (env != null) {
var valueOf = getMember(env, "valueOf");
if (valueOf instanceof FunctionValue) {
var res = valueOf.apply(env, this);
if (res.isPrimitive()) return res;
}
var toString = getMember(env, "toString");
if (toString instanceof FunctionValue) {
var res = toString.apply(env, this);
if (res.isPrimitive()) return res;
}
}
throw EngineException.ofType("Value couldn't be converted to a primitive.");
}
@Override public String toString(Environment env) { return toPrimitive(env).toString(env); }
@Override public boolean toBoolean() { return true; }
@Override public NumberValue toNumber(Environment env) { return toPrimitive(env).toNumber(env); }
@Override public StringValue type() { return StringValue.of("object"); }
private State state = State.NORMAL;
@Override public State getState() { return state; }
public final void preventExtensions() {
if (state == State.NORMAL) state = State.NON_EXTENDABLE;
}
public final void seal() {
if (state == State.NORMAL || state == State.NON_EXTENDABLE) state = State.SEALED;
}
@Override public final void freeze() { state = State.FROZEN; }
@Override public Member getOwnMember(Environment env, KeyCache key) {
if (key.isSymbol()) {
if (!symbols.containsKey(key.toSymbol())) return null;
if (symbols.get(key.toSymbol())) return symbolProperties.get(key.toSymbol());
else return symbolFields.get(key.toSymbol());
}
else if (keys.containsKey(key.toString(env))) {
if (keys.get(key.toString(env))) return properties.get(key.toString(env));
else return fields.get(key.toString(env));
}
else return null;
}
@Override public boolean defineOwnField(
Environment env, KeyCache key, Value val,
Boolean writable, Boolean enumerable, Boolean configurable
) {
if (key.isSymbol()) {
if (symbols.containsKey(key.toSymbol())) {
if (symbols.get(key.toSymbol())) {
var prop = symbolProperties.get(key.toSymbol());
if (!prop.configurable) return false;
symbolProperties.remove(key.toSymbol());
}
else return symbolFields.get(key.toSymbol()).reconfigure(env, this, val, writable, enumerable, configurable);
}
symbols.put(key.toSymbol(), false);
symbolFields.put(key.toSymbol(), FieldMember.of(this, val, writable, enumerable, configurable));
return true;
}
else if (keys.containsKey(key.toString(env))) {
if (keys.get(key.toString(env))) {
var prop = properties.get(key.toString(env));
if (!prop.configurable) return false;
properties.remove(key.toString(env));
}
else return fields.get(key.toString(env)).reconfigure(env, this, val, writable, enumerable, configurable);
}
keys.put(key.toString(env), false);
fields.put(key.toString(env), FieldMember.of(this, val, writable, enumerable, configurable));
return true;
}
@Override public boolean defineOwnProperty(
Environment env, KeyCache key,
Optional<FunctionValue> get, Optional<FunctionValue> set,
Boolean enumerable, Boolean configurable
) {
if (key.isSymbol()) {
if (symbols.containsKey(key.toSymbol())) {
if (!symbols.get(key.toSymbol())) {
var field = symbolFields.get(key.toSymbol());
if (!field.configurable) return false;
symbolFields.remove(key.toSymbol());
}
else return symbolProperties.get(key.toSymbol()).reconfigure(env, this, get, set, enumerable, configurable);
}
symbols.put(key.toSymbol(), true);
symbolProperties.put(key.toSymbol(), new PropertyMember(this, get, set, enumerable, configurable));
return true;
}
else if (keys.containsKey(key.toString(env))) {
if (!keys.get(key.toString(env))) {
var field = fields.get(key.toString(env));
if (!field.configurable) return false;
fields.remove(key.toString(env));
}
else return properties.get(key.toString(env)).reconfigure(env, this, get, set, enumerable, configurable);
}
keys.put(key.toString(env), true);
properties.put(key.toString(env), new PropertyMember(this, get, set, enumerable, configurable));
return true;
}
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
if (!getState().extendable) return false;
if (key.isSymbol()) {
if (!symbols.containsKey(key.toSymbol())) return true;
if (symbols.get(key.toSymbol())) {
if (!symbolProperties.get(key.toSymbol()).configurable) return false;
symbolProperties.remove(key.toSymbol());
symbols.remove(key.toSymbol());
return true;
}
else {
if (!symbolFields.get(key.toSymbol()).configurable) return false;
symbolFields.remove(key.toSymbol());
keys.remove(key.toString(env));
return true;
}
}
else if (keys.containsKey(key.toString(env))) {
if (keys.get(key.toString(env))) {
if (!properties.get(key.toString(env)).configurable) return false;
properties.remove(key.toString(env));
symbols.remove(key.toSymbol());
return true;
}
else {
if (!fields.get(key.toString(env)).configurable) return false;
fields.remove(key.toString(env));
keys.remove(key.toString(env));
return true;
}
}
else return true;
}
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
if (onlyEnumerable) {
var res = new LinkedHashSet<String>();
for (var el : keys.entrySet()) {
if (el.getValue()) {
if (properties.get(el.getKey()).enumerable) res.add(el.getKey());
}
else {
if (fields.get(el.getKey()).enumerable) res.add(el.getKey());
}
}
return res;
}
else return keys.keySet();
}
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) {
if (onlyEnumerable) {
var res = new LinkedHashSet<SymbolValue>();
for (var el : symbols.entrySet()) {
if (el.getValue()) {
if (symbolProperties.get(el.getKey()).enumerable) res.add(el.getKey());
}
else {
if (symbolFields.get(el.getKey()).enumerable) res.add(el.getKey());
}
}
return res;
}
else return symbols.keySet();
}
@Override public ObjectValue getPrototype(Environment env) {
if (prototype == null || env == null) return null;
else return prototype.get(env);
}
@Override public final boolean setPrototype(Environment env, ObjectValue val) {
return setPrototype(_env -> val);
}
private final LinkedList<String> memberToReadable(Environment env, String key, Member member, HashSet<ObjectValue> passed) {
if (member instanceof PropertyMember prop) {
if (prop.getter == null && prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [No accessors]"));
else if (prop.getter == null) return new LinkedList<>(Arrays.asList(key + ": [Setter]"));
else if (prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [Getter]"));
else return new LinkedList<>(Arrays.asList(key + ": [Getter/Setter]"));
}
else {
var res = new LinkedList<String>();
var first = true;
for (var line : member.get(env, this).toReadableLines(env, passed)) {
if (first) res.add(key + ": " + line);
else res.add(line);
first = false;
}
return res;
}
}
public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed, HashSet<String> ignoredKeys) {
passed.add(this);
var stringified = new LinkedList<LinkedList<String>>();
for (var entry : getOwnSymbolMembers(env, true)) {
var member = getOwnMember(env, entry);
stringified.add(memberToReadable(env, "[" + entry.value + "]", member, passed));
}
for (var entry : getOwnMembers(env, true)) {
if (ignoredKeys.contains(entry)) continue;
var member = getOwnMember(env, entry);
stringified.add(memberToReadable(env, entry, member, passed));
}
passed.remove(this);
if (stringified.size() == 0) return Arrays.asList("{}");
var concat = new StringBuilder();
for (var entry : stringified) {
// We make a one-liner only when all members are one-liners
if (entry.size() != 1) {
concat = null;
break;
}
if (concat.length() != 0) concat.append(", ");
concat.append(entry.get(0));
}
// We don't want too long one-liners
if (concat != null && concat.length() < 80) return Arrays.asList("{ " + concat.toString() + " }");
var res = new LinkedList<String>();
res.add("{");
for (var entry : stringified) {
for (var line : entry) {
res.add(" " + line);
}
res.set(res.size() - 1, res.getLast() + ",");
}
res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1));
res.add("}");
return res;
}
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
if (passed.contains(this)) return Arrays.asList("[circular]");
return toReadableLines(env, passed, new HashSet<>());
}
public final boolean setPrototype(PrototypeProvider val) {
if (!getState().extendable) return false;
prototype = val;
return true;
}
public final boolean setPrototype(Key<ObjectValue> key) {
if (!getState().extendable) return false;
prototype = env -> env.get(key);
return true;
}
}

View File

@@ -0,0 +1,25 @@
package me.topchetoeu.j2s.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);
}
}

View File

@@ -0,0 +1,15 @@
package me.topchetoeu.j2s.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);
}
}

View File

@@ -0,0 +1,60 @@
package me.topchetoeu.j2s.runtime.values.objects.buffers;
import java.util.WeakHashMap;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.objects.ArrayLikeValue;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.UserValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public abstract class TypedArrayValue extends ArrayLikeValue {
public static final WeakHashMap<byte[], UserValue<byte[]>> 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<byte[]> 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;
}
}

View File

@@ -0,0 +1,19 @@
package me.topchetoeu.j2s.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);
}
}

View File

@@ -0,0 +1,39 @@
package me.topchetoeu.j2s.runtime.values.primitives;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public final class BoolValue extends PrimitiveValue {
public static final BoolValue TRUE = new BoolValue(true);
public static final BoolValue FALSE = new BoolValue(false);
public final boolean value;
@Override public StringValue type() { return StringValue.of("boolean"); }
@Override public boolean toBoolean() { return value; }
@Override public NumberValue toNumber(Environment ext) { return NumberValue.of(value ? 1 : 0); }
@Override public String toString(Environment ext) { return value ? "true" : "false"; }
@Override public ObjectValue getPrototype(Environment env) {
return env.get(BOOL_PROTO);
}
@Override public int hashCode() {
return Boolean.hashCode(value);
}
@Override public boolean equals(Object other) {
if (other == this) return true;
else if (other instanceof BoolValue bool) return value == bool.value;
else return false;
}
private BoolValue(boolean val) {
this.value = val;
}
public static BoolValue of(boolean val) {
return val ? TRUE : FALSE;
}
}

View File

@@ -0,0 +1,40 @@
package me.topchetoeu.j2s.runtime.values.primitives;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.values.KeyCache;
import me.topchetoeu.j2s.runtime.values.Member;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
public abstract class PrimitiveValue extends Value {
@Override public boolean defineOwnField(
Environment env, KeyCache key, Value val,
Boolean writable, Boolean enumerable, Boolean configurable
) { return false; }
@Override
public boolean defineOwnProperty(
Environment env, KeyCache key, Optional<FunctionValue> get, Optional<FunctionValue> set,
Boolean enumerable, Boolean configurable
) { return false; }
@Override public boolean deleteOwnMember(Environment env, KeyCache key) { return true; }
@Override public final boolean isPrimitive() { return true; }
@Override public final Value toPrimitive(Environment env) { return this; }
@Override public final boolean setPrototype(Environment env, ObjectValue val) { return false; }
@Override public Member getOwnMember(Environment env, KeyCache key) { return null; }
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) { return new HashSet<>(); }
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { return new HashSet<>(); }
@Override public State getState() { return State.FROZEN; }
@Override public void preventExtensions() {}
@Override public void seal() {}
@Override public void freeze() {}
}

View File

@@ -0,0 +1,97 @@
package me.topchetoeu.j2s.runtime.values.primitives;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.common.json.JSON;
import me.topchetoeu.j2s.common.json.JSONElement;
import me.topchetoeu.j2s.common.parsing.Parsing;
import me.topchetoeu.j2s.common.parsing.Source;
import me.topchetoeu.j2s.runtime.values.KeyCache;
import me.topchetoeu.j2s.runtime.values.Member;
import me.topchetoeu.j2s.runtime.values.Member.FieldMember;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public final class StringValue extends PrimitiveValue {
private static final WeakHashMap<String, StringValue> cache = new WeakHashMap<>();
public final String value;
@Override public StringValue type() { return of("string"); }
@Override public boolean toBoolean() { return !value.equals(""); }
@Override public NumberValue toNumber(Environment ext) {
var val = value.trim();
if (val.equals("")) return NumberValue.of(0);
var res = Parsing.parseNumber(new Source(val), 0, true);
if (res.isSuccess() && res.n == val.length()) return NumberValue.of(res.result);
else return NumberValue.NAN;
}
@Override public String toString(Environment ext) { return value; }
@Override public int hashCode() {
return value.hashCode();
}
@Override public boolean equals(Object other) {
if (this == other) return true;
else if (other instanceof StringValue val) return value.length() == val.value.length() && value.equals(val.value);
else return false;
}
@Override public String toString() {
return value;
}
@Override public ObjectValue getPrototype(Environment env) { return env.get(STRING_PROTO); }
@Override public Member getOwnMember(Environment env, KeyCache key) {
if (!key.isSymbol()) {
var num = key.toNumber(env);
var i = key.toInt(env);
if (i == num && i >= 0 && i < value.length()) {
return FieldMember.of(this, new StringValue(value.charAt(i) + ""), false, true, false);
}
else if (!key.isSymbol() && key.toString(env).equals("length")) {
return FieldMember.of(this, NumberValue.of(value.length()), false, false, false);
}
}
return super.getOwnMember(env, key);
}
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
var res = new LinkedHashSet<String>();
res.addAll(super.getOwnMembers(env, onlyEnumerable));
for (var i = 0; i < value.length(); i++) res.add(i + "");
if (!onlyEnumerable) res.add("length");
return res;
}
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
return Arrays.asList(JSON.stringify(JSONElement.string(value)));
}
private StringValue(String value) {
this.value = value;
}
public static StringValue of(String value) {
if (cache.containsKey(value)) return cache.get(value);
else {
StringValue res;
cache.put(value, res = new StringValue(value));
return res;
}
}
}

View File

@@ -0,0 +1,57 @@
package me.topchetoeu.j2s.runtime.values.primitives;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public final class SymbolValue extends PrimitiveValue {
private static final HashMap<String, SymbolValue> registry = new HashMap<>();
public final String value;
public Value key() {
return registry.containsKey(value) && registry.get(value) == this ? StringValue.of(value) : Value.UNDEFINED;
}
@Override public StringValue type() { return StringValue.of("symbol"); }
@Override public boolean toBoolean() { return true; }
@Override public String toString(Environment env) {
throw EngineException.ofType("Cannot convert a Symbol value to a string");
}
@Override public NumberValue toNumber(Environment env) {
throw EngineException.ofType("Cannot convert a Symbol value to a number");
}
@Override public ObjectValue getPrototype(Environment env) { return env.get(SYMBOL_PROTO); }
@Override public String toString() {
if (value == null) return "Symbol()";
else return "Symbol(" + value + ")";
}
@Override
public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
return Arrays.asList(toString());
}
public SymbolValue(String value) {
this.value = value;
}
public static SymbolValue get(String name) {
if (registry.containsKey(name)) return registry.get(name);
else {
var res = new SymbolValue(name);
registry.put(name, res);
return res;
}
}
}

View File

@@ -0,0 +1,86 @@
package me.topchetoeu.j2s.runtime.values.primitives;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.values.KeyCache;
import me.topchetoeu.j2s.runtime.values.Member;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public final class UserValue<T> extends Value {
public final T value;
public final ObjectValue prototype;
@Override public StringValue type() { return StringValue.of("object"); }
@Override public boolean toBoolean() { return true; }
@Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; }
@Override public String toString(Environment ext) { return "[user value]"; }
@Override public boolean defineOwnField(
Environment env, KeyCache key, Value val,
Boolean writable, Boolean enumerable, Boolean configurable
) { return false; }
@Override
public boolean defineOwnProperty(
Environment env, KeyCache key, Optional<FunctionValue> get, Optional<FunctionValue> set,
Boolean enumerable, Boolean configurable
) { return false; }
@Override public boolean deleteOwnMember(Environment env, KeyCache key) { return true; }
@Override public final boolean isPrimitive() { return false; }
@Override public final Value toPrimitive(Environment env) { return NumberValue.NAN; }
@Override public final boolean setPrototype(Environment env, ObjectValue val) { return false; }
@Override public Member getOwnMember(Environment env, KeyCache key) { return null; }
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) { return new HashSet<>(); }
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { return new HashSet<>(); }
@Override public State getState() { return State.FROZEN; }
@Override public void preventExtensions() {}
@Override public void seal() {}
@Override public void freeze() {}
@Override public int hashCode() {
return value.hashCode();
}
@Override public boolean equals(Object other) {
if (this == other) return true;
else if (other instanceof UserValue val) return Objects.equals(value, val.value);
else return false;
}
@Override public ObjectValue getPrototype(Environment env) { return prototype; }
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
return Arrays.asList(value.toString());
}
private UserValue(T value, ObjectValue prototype) {
this.value = value;
this.prototype = prototype;
}
public static <T> UserValue<T> of(T value) {
return new UserValue<T>(value, null);
}
public static <T> UserValue<T> of(T value, ObjectValue prototype) {
return new UserValue<T>(value, prototype);
}
@SuppressWarnings("unchecked")
public static <T> T unwrap(Class<T> clazz, Value val) {
if (val instanceof UserValue user && clazz.isInstance(user.value)) return (T)user.value;
else return null;
}
}

View File

@@ -0,0 +1,42 @@
package me.topchetoeu.j2s.runtime.values.primitives;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.KeyCache;
import me.topchetoeu.j2s.runtime.values.Member;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public final class VoidValue extends PrimitiveValue {
public final String name;
public final String type;
@Override public StringValue type() { return StringValue.of(type); }
@Override public boolean toBoolean() { return false; }
@Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; }
@Override public String toString(Environment ext) { return name; }
@Override public ObjectValue getPrototype(Environment env) { return null; }
@Override public Member getOwnMember(Environment env, KeyCache key) {
if (key.isSymbol()) throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toSymbol().toString()));
else throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env)));
}
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
return Arrays.asList(name);
}
@Override public String toString() {
return name;
}
public VoidValue(String name, String type) {
this.name = name;
this.type = type;
}
}

View File

@@ -0,0 +1,42 @@
package me.topchetoeu.j2s.runtime.values.primitives.numbers;
import me.topchetoeu.j2s.common.json.JSON;
import me.topchetoeu.j2s.common.json.JSONElement;
public final class DoubleValue extends NumberValue {
public final double value;
@Override public boolean isInt() {
return (int)value == value;
}
@Override public boolean isLong() {
return (long)value == value;
}
@Override public int getInt() {
return (int)value;
}
@Override public long getLong() {
return (long)value;
}
@Override public double getDouble() {
return value;
}
@Override public String toString() { return JSON.stringify(JSONElement.number(value)); }
@Override public int hashCode() {
return Double.hashCode(value);
}
@Override public boolean equals(Object other) {
if (this == other) return true;
else if (other instanceof NumberValue val) return value == val.getDouble();
else return false;
}
/**
* This constructs a double value directly. In almost all cases, you want to use NumberValue.of instead
*/
public DoubleValue(double value) {
this.value = value;
}
}

View File

@@ -0,0 +1,49 @@
package me.topchetoeu.j2s.runtime.values.primitives.numbers;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
public final class IntValue extends NumberValue {
public final long value;
@Override public boolean isInt() {
return (int)value == value;
}
@Override public boolean isLong() {
return true;
}
@Override public int getInt() {
return (int)value;
}
@Override public long getLong() {
return value;
}
@Override public double getDouble() {
return value;
}
@Override public int hashCode() {
return Long.hashCode(value);
}
@Override public String toString() { return value + ""; }
@Override public boolean equals(Object other) {
if (this == other) return true;
else if (other instanceof NumberValue val) return val.isLong() && value == val.getLong();
else return false;
}
@Override public List<String> toReadableLines(Environment env, HashSet<ObjectValue> passed) {
return Arrays.asList(value + "i");
}
public IntValue(long value) {
this.value = value;
}
public IntValue(int value) {
this.value = value;
}
}

View File

@@ -0,0 +1,64 @@
package me.topchetoeu.j2s.runtime.values.primitives.numbers;
import me.topchetoeu.j2s.common.environment.Environment;
import me.topchetoeu.j2s.common.parsing.Parsing;
import me.topchetoeu.j2s.common.parsing.Source;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.PrimitiveValue;
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
public abstract class NumberValue extends PrimitiveValue {
public static final NumberValue NAN = new DoubleValue(Double.NaN);
@Override public final StringValue type() { return StringValue.of("number"); }
public abstract double getDouble();
public abstract int getInt();
public abstract long getLong();
public abstract boolean isLong();
public abstract boolean isInt();
public abstract boolean equals(Object other);
public abstract String toString();
@Override public final boolean toBoolean() { return getDouble() != 0; }
@Override public final NumberValue toNumber(Environment ext) { return this; }
@Override public final String toString(Environment ext) { return toString(); }
@Override public final ObjectValue getPrototype(Environment env) {
return env.get(NUMBER_PROTO);
}
public static NumberValue parseInt(String str, int radix, boolean relaxed) {
if (radix < 2 || radix > 36) return NumberValue.NAN;
str = str.trim();
var res = Parsing.parseInt(new Source(str), 0, "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, radix), true);
if (res.isSuccess()) {
if (relaxed || res.n == str.length()) return of(res.result);
}
return NumberValue.NAN;
}
public static NumberValue parseFloat(String str, boolean relaxed) {
str = str.trim();
var res = Parsing.parseFloat(new Source(str), 0, true);
if (res.isSuccess()) {
if (relaxed || res.n == str.length()) return of(res.result);
}
return NumberValue.NAN;
}
public static NumberValue of(double value) {
if (Double.isNaN(value)) return NAN;
else if ((int)value == value) return new IntValue((int)value);
else return new DoubleValue(value);
}
public static NumberValue of(long value) {
return new IntValue(value);
}
public static NumberValue of(int value) {
return new IntValue(value);
}
}

View File

@@ -0,0 +1,14 @@
package me.topchetoeu.j2s;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestHelloWorld {
@Test
public void testHelloWorld() {
final String message = "Hello World!";
assertEquals("Hello World!", message);
}
}