diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index de09d95..fe3aeb5 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -14,8 +14,8 @@ import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; public class InstructionRunner { private static Value execReturn(Environment env, Instruction instr, Frame frame) { @@ -136,7 +136,7 @@ public class InstructionRunner { 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(new NumberValue(instr.get(0))); break; + case PUSH_NUMBER: frame.push(NumberValue.of((double)instr.get(0))); break; case PUSH_STRING: frame.push(new StringValue(instr.get(0))); break; default: } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java b/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java index de18785..714a2b5 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java @@ -12,15 +12,15 @@ import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; +import me.topchetoeu.jscript.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 new StringValue(val.string()); - if (val.isNumber()) return new NumberValue(val.number()); + 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(); @@ -43,7 +43,7 @@ public class JSONConverter { public static JSONElement fromJs(Environment env, Value val, HashSet prev) { if (val instanceof BoolValue) return JSONElement.bool(((BoolValue)val).value); - if (val instanceof NumberValue) return JSONElement.number(((NumberValue)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; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index f4b81a8..5740a3a 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -22,10 +22,10 @@ import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; public class SimpleRepl { static Thread engineTask; @@ -94,8 +94,8 @@ public class SimpleRepl { var res = new ObjectValue(); res.setPrototype(null, null); - res.defineOwnMember(env, "makeSymbol", new NativeFunction(args -> new SymbolValue(args.get(0).toString(args.env).value))); - res.defineOwnMember(env, "getSymbol", new NativeFunction(args -> SymbolValue.get(args.get(0).toString(args.env).value))); + res.defineOwnMember(env, "makeSymbol", new NativeFunction(args -> new SymbolValue(args.get(0).toString(args.env)))); + res.defineOwnMember(env, "getSymbol", new NativeFunction(args -> SymbolValue.get(args.get(0).toString(args.env)))); res.defineOwnMember(env, "getSymbolKey", new NativeFunction(args -> ((SymbolValue)args.get(0)).key())); res.defineOwnMember(env, "getSymbolDescriptor", new NativeFunction(args -> new StringValue(((SymbolValue)args.get(0)).value))); @@ -107,14 +107,14 @@ public class SimpleRepl { res.setPrototype(null, null); res.defineOwnMember(env, "parseInt", new NativeFunction(args -> { - var radix = args.get(1).toInt(env); + var nradix = args.get(1).toNumber(env); + var radix = nradix.isInt() ? nradix.getInt() : 10; - if (radix != 10 && args.get(0) instanceof NumberValue) { - return new NumberValue(args.get(0).toNumber(env).value - args.get(0).toNumber(env).value % 1); - } - else { - return NumberValue.parseInt(args.get(0).toString(), radix, false); + if (radix != 10 && args.get(0) instanceof NumberValue num) { + if (num.isInt()) return num; + else return NumberValue.of(num.getDouble() - num.getDouble() % 1); } + else return NumberValue.parseInt(args.get(0).toString(), radix, false); })); res.defineOwnMember(env, "parseFloat", new NativeFunction(args -> { if (args.get(0) instanceof NumberValue) { @@ -123,8 +123,8 @@ public class SimpleRepl { else return NumberValue.parseFloat(args.get(0).toString(), false); })); res.defineOwnMember(env, "isNaN", new NativeFunction(args -> BoolValue.of(args.get(0).isNaN()))); - res.defineOwnMember(env, "NaN", new NumberValue(Double.NaN)); - res.defineOwnMember(env, "Infinity", new NumberValue(Double.POSITIVE_INFINITY)); + res.defineOwnMember(env, "NaN", NumberValue.NAN); + res.defineOwnMember(env, "Infinity", NumberValue.of(Double.POSITIVE_INFINITY)); return res; } @@ -229,14 +229,14 @@ public class SimpleRepl { var func = (FunctionValue)args.get(0); var self = args.get(1); var funcArgs = (ArrayValue)args.get(2); - var name = args.get(3).toString(env).value; + var name = args.get(3).toString(env); return func.invoke(env, name, self, funcArgs.toArray()); })); res.defineOwnMember(env, "construct", new NativeFunction(args -> { var func = (FunctionValue)args.get(0); var funcArgs = (ArrayValue)args.get(1); - var name = args.get(2).toString(env).value; + var name = args.get(2).toString(env); return func.construct(env, name, funcArgs.toArray()); })); @@ -252,7 +252,7 @@ public class SimpleRepl { return new StringValue(JSON.stringify(JSONConverter.fromJs(env, args.get(0)))); })); res.defineOwnMember(env, "parse", new NativeFunction(args -> { - return JSONConverter.toJs(JSON.parse(null, args.get(0).toString(env).value)); + return JSONConverter.toJs(JSON.parse(null, args.get(0).toString(env))); })); res.defineOwnMember(env, "setConstructable", new NativeFunction(args -> { var func = (FunctionValue)args.get(0); @@ -288,7 +288,7 @@ public class SimpleRepl { int[] i = new int[1]; res.defineOwnMember(env, "setGlobalPrototype", new NativeFunction(args -> { - var type = args.get(0).toString(env).value; + var type = args.get(0).toString(env); var obj = (ObjectValue)args.get(1); switch (type) { @@ -330,7 +330,7 @@ public class SimpleRepl { return Value.UNDEFINED; })); res.defineOwnMember(env, "setIntrinsic", new NativeFunction(args -> { - var name = args.get(0).toString(env).value; + var name = args.get(0).toString(env); var val = args.get(1); Value.intrinsics(environment).put(name, val); @@ -338,7 +338,7 @@ public class SimpleRepl { return Value.UNDEFINED; })); res.defineOwnMember(env, "compile", new NativeFunction(args -> { - return Compiler.compileFunc(env, new Filename("jscript", "func" + i[0]++ + ".js"), args.get(0).toString(env).value); + return Compiler.compileFunc(env, new Filename("jscript", "func" + i[0]++ + ".js"), args.get(0).toString(env)); })); return res; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java index 20aed1a..ce26c0c 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java @@ -66,7 +66,7 @@ public class EngineException extends RuntimeException { public String toString(Environment env) { var ss = new StringBuilder(); try { - ss.append(value.toString(env).value).append('\n'); + ss.append(value.toString(env)).append('\n'); } catch (EngineException e) { var name = value.getMember(env, "name"); @@ -74,10 +74,10 @@ public class EngineException extends RuntimeException { if (name.isPrimitive() && desc.isPrimitive()) { if (name instanceof VoidValue) ss.append("Error: "); - else ss.append(name.toString(env).value + ": "); + else ss.append(name.toString(env) + ": "); if (desc instanceof VoidValue) ss.append("An error occurred"); - else ss.append(desc.toString(env).value); + else ss.append(desc.toString(env)); ss.append("\n"); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/KeyCache.java b/src/main/java/me/topchetoeu/jscript/runtime/values/KeyCache.java index f5660ed..5ab25e5 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/KeyCache.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/KeyCache.java @@ -1,28 +1,39 @@ package me.topchetoeu.jscript.runtime.values; import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; public final class KeyCache { public final Value value; - private Integer intCache; + 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).value; + else return stringCache = value.toString(env); } public double toNumber(Environment env) { - if (doubleCache != null) return doubleCache; - else return doubleCache = value.toNumber(env).value; + 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 (intCache != null) return intCache; - else return intCache = (int)toNumber(env); + if (doubleCache == null) toNumber(env); + return intCache; } public boolean toBoolean() { if (booleanCache != null) return booleanCache; @@ -45,13 +56,13 @@ public final class KeyCache { this.booleanCache = !value.equals(""); } public KeyCache(int value) { - this.value = new NumberValue(value); + this.value = NumberValue.of(value); this.intCache = value; this.doubleCache = (double)value; this.booleanCache = value != 0; } public KeyCache(double value) { - this.value = new NumberValue(value); + this.value = NumberValue.of(value); this.intCache = (int)value; this.doubleCache = value; this.booleanCache = value != 0; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java index 222857a..812bd58 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -26,10 +26,10 @@ import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; public abstract class Value { public static enum State { @@ -81,7 +81,7 @@ public abstract class Value { public abstract boolean isPrimitive(); public final boolean isNaN() { - return this instanceof NumberValue && Double.isNaN(((NumberValue)this).value); + return this == NumberValue.NAN || this instanceof NumberValue num && Double.isNaN(num.getDouble()); } public Value call(Environment env, boolean isNew, String name, Value self, Value ...args) { @@ -116,12 +116,9 @@ public abstract class Value { public abstract Value toPrimitive(Environment env); public abstract NumberValue toNumber(Environment env); - public abstract StringValue toString(Environment env); + public abstract String toString(Environment env); public abstract boolean toBoolean(); - public final int toInt(Environment env) { return (int)toNumber(env).value; } - public final long toLong(Environment env) { return (long)toNumber(env).value; } - public final boolean isInstanceOf(Environment env, Value proto) { for (var val = getPrototype(env); val != null; val = getPrototype(env)) { if (val.equals(proto)) return true; @@ -525,7 +522,7 @@ public abstract class Value { else if (this instanceof VoidValue) return ((VoidValue)this).name; else if (this instanceof StringValue) return JSON.stringify(JSONElement.string(((StringValue)this).value)); else if (this instanceof SymbolValue) return this.toString(); - else return this.toString(env).value; + else return this.toString(env); } public final String toReadable(Environment ext) { @@ -560,7 +557,11 @@ public abstract class Value { return aStr.value.compareTo(bStr.value) <= 0; } else { - return a.toNumber(env).value <= b.toNumber(env).value; + 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) { @@ -571,7 +572,11 @@ public abstract class Value { return aStr.value.compareTo(bStr.value) >= 0; } else { - return a.toNumber(env).value >= b.toNumber(env).value; + 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) { @@ -579,10 +584,14 @@ public abstract class Value { b = b.toPrimitive(env); if (a instanceof StringValue aStr && b instanceof StringValue bStr) { - return aStr.value.compareTo(bStr.value) >= 0; + return aStr.value.compareTo(bStr.value) < 0; } else { - return a.toNumber(env).value < b.toNumber(env).value; + 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) { @@ -590,10 +599,14 @@ public abstract class Value { b = b.toPrimitive(env); if (a instanceof StringValue aStr && b instanceof StringValue bStr) { - return aStr.value.compareTo(bStr.value) >= 0; + return aStr.value.compareTo(bStr.value) > 0; } else { - return a.toNumber(env).value > b.toNumber(env).value; + 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(); } } @@ -602,56 +615,96 @@ public abstract class Value { b = b.toPrimitive(env); if (a instanceof StringValue || b instanceof StringValue) { - return new StringValue(a.toString(env).value + b.toString(env).value); + return new StringValue(a.toString(env) + b.toString(env)); } else { - return new NumberValue(a.toNumber(env).value + b.toNumber(env).value); + 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) { - return new NumberValue(a.toNumber(env).value - b.toNumber(env).value); + 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) { - return new NumberValue(a.toNumber(env).value - b.toNumber(env).value); + 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) { - return new NumberValue(a.toNumber(env).value / b.toNumber(env).value); + 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) { - return new NumberValue(a.toNumber(env).value % b.toNumber(env).value); + 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) { - return new NumberValue(-a.toNumber(env).value); + 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 new NumberValue(a.toInt(env) & b.toInt(env)); + return NumberValue.of(a.toNumber(env).getInt() & b.toNumber(env).getInt()); } public static final NumberValue or(Environment env, Value a, Value b) { - return new NumberValue(a.toInt(env) | b.toInt(env)); + return NumberValue.of(a.toNumber(env).getInt() | b.toNumber(env).getInt()); } public static final NumberValue xor(Environment env, Value a, Value b) { - return new NumberValue(a.toInt(env) ^ b.toInt(env)); + return NumberValue.of(a.toNumber(env).getInt() ^ b.toNumber(env).getInt()); } public static final NumberValue bitwiseNot(Environment env, Value a) { - return new NumberValue(~a.toInt(env)); + return NumberValue.of(~a.toNumber(env).getInt()); } public static final NumberValue shiftLeft(Environment env, Value a, Value b) { - return new NumberValue(a.toInt(env) << b.toInt(env)); + return NumberValue.of(a.toNumber(env).getInt() << b.toNumber(env).getInt()); } public static final NumberValue shiftRight(Environment env, Value a, Value b) { - return new NumberValue(a.toInt(env) >> b.toInt(env)); + 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.toInt(env); - long _b = b.toInt(env); + 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 new NumberValue(_a >>> _b); + return NumberValue.of(_a >>> _b); } public static final boolean looseEqual(Environment env, Value a, Value b) { diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java index 20f7d80..fd1aa9d 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java @@ -6,8 +6,8 @@ import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; public abstract class FunctionValue extends ObjectValue { private static final StringValue typeString = new StringValue("function"); @@ -25,13 +25,13 @@ public abstract class FunctionValue extends ObjectValue { return new StringValue(name); } @Override public boolean set(Environment env, Value val, Value self) { - name = val.toString(env).value; + 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 new NumberValue(length); + return NumberValue.of(length); } @Override public boolean set(Environment env, Value val, Value self) { return false; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java index 8b0e94a..e7f70bd 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java @@ -4,11 +4,12 @@ import java.util.LinkedHashSet; import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; public abstract class ArrayLikeValue extends ObjectValue { private static class IndexField extends FieldMember { @@ -30,10 +31,16 @@ public abstract class ArrayLikeValue extends ObjectValue { private final FieldMember lengthField = new FieldMember(this, false, false, true) { @Override public Value get(Environment env, Value self) { - return new NumberValue(size()); + return NumberValue.of(size()); } @Override public boolean set(Environment env, Value val, Value self) { - return setSize(val.toInt(env)); + 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); } }; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ByteBufferValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ByteBufferValue.java index eb8c2f7..3ce3b90 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ByteBufferValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ByteBufferValue.java @@ -5,7 +5,7 @@ import java.util.Iterator; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; // TODO: Make methods generic public class ByteBufferValue extends ArrayLikeValue implements Iterable { @@ -26,11 +26,11 @@ public class ByteBufferValue extends ArrayLikeValue implements Iterable { @Override public Value get(int i) { if (i < 0 || i >= values.length) return null; - return new NumberValue(values[i]); + return NumberValue.of(values[i]); } @Override public boolean set(Environment env, int i, Value val) { if (i < 0 || i >= values.length) return false; - values[i] = (byte)val.toNumber(env).value; + values[i] = (byte)val.toNumber(env).getInt(); return true; } @Override public boolean has(int i) { diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java index ac70a26..7c62e1b 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java @@ -11,9 +11,9 @@ import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; public class ObjectValue extends Value { public static interface PrototypeProvider { @@ -56,7 +56,7 @@ public class ObjectValue extends Value { throw EngineException.ofType("Value couldn't be converted to a primitive."); } - @Override public StringValue toString(Environment env) { return toPrimitive(env).toString(env); } + @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 typeString; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java index 4b567db..9b09c22 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java @@ -2,6 +2,7 @@ package me.topchetoeu.jscript.runtime.values.primitives; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; public final class BoolValue extends PrimitiveValue { public static final BoolValue TRUE = new BoolValue(true); @@ -13,10 +14,8 @@ public final class BoolValue extends PrimitiveValue { @Override public StringValue type() { return typeString; } @Override public boolean toBoolean() { return value; } - @Override public NumberValue toNumber(Environment ext) { - return value ? new NumberValue(1) : new NumberValue(0); - } - @Override public StringValue toString(Environment ext) { return new StringValue(value ? "true" : "false"); } + @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); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java deleted file mode 100644 index 2fe9265..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java +++ /dev/null @@ -1,54 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.primitives; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.json.JSON; -import me.topchetoeu.jscript.common.json.JSONElement; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; - -public final class NumberValue extends PrimitiveValue { - public static final NumberValue NAN = new NumberValue(Double.NaN); - private static final StringValue typeString = new StringValue("number"); - - public final double value; - - @Override public StringValue type() { return typeString; } - - @Override public boolean toBoolean() { return value != 0; } - @Override public NumberValue toNumber(Environment ext) { return this; } - @Override public StringValue toString(Environment ext) { return new StringValue(toString()); } - @Override public String toString() { return JSON.stringify(JSONElement.number(value)); } - - @Override public ObjectValue getPrototype(Environment env) { - return env.get(NUMBER_PROTO); - } - - @Override public boolean equals(Object other) { - if (other instanceof NumberValue val) return value == val.value; - else return false; - } - - public NumberValue(double value) { - this.value = value; - } - - public static NumberValue parseInt(String str, int radix, boolean relaxed) { - if (radix < 2 || radix > 36) return new NumberValue(Double.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 new NumberValue(res.result); - } - return new NumberValue(Double.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 new NumberValue(res.result); - } - return new NumberValue(Double.NaN); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java index e2d29d3..fb32ca4 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java @@ -10,6 +10,7 @@ import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; public final class StringValue extends PrimitiveValue { public final String value; @@ -20,13 +21,13 @@ public final class StringValue extends PrimitiveValue { @Override public boolean toBoolean() { return !value.equals(""); } @Override public NumberValue toNumber(Environment ext) { var val = value.trim(); - if (val.equals("")) return new NumberValue(0); + if (val.equals("")) return NumberValue.of(0); var res = Parsing.parseNumber(new Source(val), 0, true); - if (res.isSuccess() && res.n == val.length()) return new NumberValue(res.result); - else return new NumberValue(Double.NaN); + if (res.isSuccess() && res.n == val.length()) return NumberValue.of(res.result); + else return NumberValue.NAN; } - @Override public StringValue toString(Environment ext) { return this; } + @Override public String toString(Environment ext) { return value; } @Override public boolean equals(Object other) { if (other instanceof StringValue val) return value.length() == val.value.length() && value.equals(val.value); @@ -43,7 +44,7 @@ public final class StringValue extends PrimitiveValue { return FieldMember.of(this, new StringValue(value.charAt(i) + ""), false, true, false); } else if (key.toString(env).equals("length")) { - return FieldMember.of(this, new NumberValue(value.length()), false, false, false); + return FieldMember.of(this, NumberValue.of(value.length()), false, false, false); } else return super.getOwnMember(env, key); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java index e5baeb4..5b7c17b 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java @@ -6,6 +6,7 @@ import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; public final class SymbolValue extends PrimitiveValue { private static final HashMap registry = new HashMap<>(); @@ -20,7 +21,7 @@ public final class SymbolValue extends PrimitiveValue { @Override public StringValue type() { return typeString; } @Override public boolean toBoolean() { return false; } - @Override public StringValue toString(Environment env) { + @Override public String toString(Environment env) { throw EngineException.ofType("Cannot convert a Symbol value to a string"); } @Override public NumberValue toNumber(Environment env) { diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java index 0a40322..e54c57a 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java @@ -5,17 +5,16 @@ import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; public final class VoidValue extends PrimitiveValue { - private final StringValue nameString; - public final String name; public final StringValue typeString; @Override public StringValue type() { return typeString; } @Override public boolean toBoolean() { return false; } @Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; } - @Override public StringValue toString(Environment ext) { return nameString; } + @Override public String toString(Environment ext) { return name; } @Override public ObjectValue getPrototype(Environment env) { return null; } @@ -26,6 +25,5 @@ public final class VoidValue extends PrimitiveValue { public VoidValue(String name, StringValue type) { this.name = name; this.typeString = type; - this.nameString = new StringValue(name); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/DoubleValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/DoubleValue.java new file mode 100644 index 0000000..488961c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/DoubleValue.java @@ -0,0 +1,38 @@ +package me.topchetoeu.jscript.runtime.values.primitives.numbers; + +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.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 boolean equals(Object other) { + 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; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/IntValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/IntValue.java new file mode 100644 index 0000000..9b3c74c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/IntValue.java @@ -0,0 +1,34 @@ +package me.topchetoeu.jscript.runtime.values.primitives.numbers; + +public final class IntValue extends NumberValue { + public final long value; + + @Override public boolean isInt() { + return (value & 0xFFFFFFFF00000000l) == 0; + } + @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 String toString() { return value + ""; } + @Override public boolean equals(Object other) { + if (other instanceof NumberValue val) return val.isLong() && value == val.getLong(); + else return false; + } + + public IntValue(long value) { + this.value = value; + } + public IntValue(int value) { + this.value = value; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/NumberValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/NumberValue.java new file mode 100644 index 0000000..dcab4d9 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/NumberValue.java @@ -0,0 +1,65 @@ +package me.topchetoeu.jscript.runtime.values.primitives.numbers; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.parsing.Parsing; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; +import me.topchetoeu.jscript.runtime.values.primitives.PrimitiveValue; +import me.topchetoeu.jscript.runtime.values.primitives.StringValue; + +public abstract class NumberValue extends PrimitiveValue { + public static final NumberValue NAN = new DoubleValue(Double.NaN); + private static final StringValue typeString = new StringValue("number"); + + @Override public final StringValue type() { return typeString; } + + 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); + } +}