Skip to content

Commit

Permalink
Implement number classes using lambdas (#1808)
Browse files Browse the repository at this point in the history
The next step in the push to move away from IdScriptableObject -- switch the implementations of Number, BigInteger, and Boolean to be based on lambdas.
  • Loading branch information
gbrail authored Feb 5, 2025
1 parent 177b047 commit 030acb5
Show file tree
Hide file tree
Showing 6 changed files with 370 additions and 577 deletions.
16 changes: 16 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,22 @@ public void defineConstructorMethod(
defineProperty(name, f, attributes);
}

/**
* Replace the default "Object" prototype with a prototype of a specific implementation. This is
* only necessary for a few built-in constructors, like Boolean, that must have their prototype
* be an object with a specific "internal data slot."
*/
public void setPrototypeScriptable(ScriptableObject proto) {
proto.setParentScope(getParentScope());
setPrototypeProperty(proto);
Scriptable objectProto = getObjectPrototype(this);
if (proto != objectProto) {
// not the one we just made, it must remain grounded
proto.setPrototype(objectProto);
}
proto.defineProperty("constructor", this, DONTENUM);
}

/**
* A convenience method to convert JavaScript's "this" object into a target class and throw a
* TypeError if it does not match. This is useful for implementing lambda functions, as "this"
Expand Down
280 changes: 102 additions & 178 deletions rhino/src/main/java/org/mozilla/javascript/NativeBigInt.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,65 @@
import java.util.Arrays;

/** This class implements the BigInt native object. */
final class NativeBigInt extends IdScriptableObject {
final class NativeBigInt extends ScriptableObject {
private static final long serialVersionUID = 1335609231306775449L;

private static final Object BIG_INT_TAG = "BigInt";
private static final String CLASS_NAME = "BigInt";

private final BigInteger bigIntValue;

static void init(Scriptable scope, boolean sealed) {
NativeBigInt obj = new NativeBigInt(BigInteger.ZERO);
obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
LambdaConstructor constructor =
new LambdaConstructor(
scope,
CLASS_NAME,
1,
NativeBigInt::js_constructorFunc,
NativeBigInt::js_constructor);
constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT);
constructor.defineConstructorMethod(
scope,
"asIntN",
2,
(Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) ->
js_asIntOrUintN(true, args),
DONTENUM,
DONTENUM | READONLY);
constructor.defineConstructorMethod(
scope,
"asUintN",
2,
(Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) ->
js_asIntOrUintN(false, args),
DONTENUM,
DONTENUM | READONLY);
constructor.definePrototypeMethod(
scope, "toString", 0, NativeBigInt::js_toString, DONTENUM, DONTENUM | READONLY);
// Alias toLocaleString to toString
constructor.definePrototypeMethod(
scope,
"toLocaleString",
0,
NativeBigInt::js_toString,
DONTENUM,
DONTENUM | READONLY);
constructor.definePrototypeMethod(
scope, "toSource", 0, NativeBigInt::js_toSource, DONTENUM, DONTENUM | READONLY);
constructor.definePrototypeMethod(
scope,
"valueOf",
0,
(Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) ->
toSelf(thisObj).bigIntValue,
DONTENUM,
DONTENUM | READONLY);
constructor.definePrototypeProperty(
SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY);

ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, DONTENUM);
if (sealed) {
constructor.sealObject();
}
}

NativeBigInt(BigInteger bigInt) {
Expand All @@ -26,198 +77,71 @@ static void init(Scriptable scope, boolean sealed) {

@Override
public String getClassName() {
return "BigInt";
return CLASS_NAME;
}

@Override
protected void fillConstructorProperties(IdFunctionObject ctor) {
addIdFunctionProperty(ctor, BIG_INT_TAG, ConstructorId_asIntN, "asIntN", 2);
addIdFunctionProperty(ctor, BIG_INT_TAG, ConstructorId_asUintN, "asUintN", 2);
private static NativeBigInt toSelf(Scriptable thisObj) {
return LambdaConstructor.convertThisObject(thisObj, NativeBigInt.class);
}

super.fillConstructorProperties(ctor);
private static Object js_constructorFunc(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
return (args.length >= 1) ? ScriptRuntime.toBigInt(args[0]) : BigInteger.ZERO;
}

@Override
protected void initPrototypeId(int id) {
if (id == SymbolId_toStringTag) {
initPrototypeValue(
SymbolId_toStringTag,
SymbolKey.TO_STRING_TAG,
getClassName(),
DONTENUM | READONLY);
return;
}
private static Scriptable js_constructor(Context cx, Scriptable scope, Object[] args) {
throw ScriptRuntime.typeErrorById("msg.no.new", CLASS_NAME);
}

String s;
int arity;
switch (id) {
case Id_constructor:
arity = 1;
s = "constructor";
break;
case Id_toString:
arity = 0;
s = "toString";
break;
case Id_toLocaleString:
arity = 0;
s = "toLocaleString";
break;
case Id_toSource:
arity = 0;
s = "toSource";
break;
case Id_valueOf:
arity = 0;
s = "valueOf";
break;
default:
throw new IllegalArgumentException(String.valueOf(id));
}
initPrototypeMethod(BIG_INT_TAG, id, s, arity);
private static Object js_toString(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
int base =
(args.length == 0 || args[0] == Undefined.instance)
? 10
: ScriptRuntime.toInt32(args[0]);
BigInteger value = toSelf(thisObj).bigIntValue;
return ScriptRuntime.bigIntToString(value, base);
}

@Override
public Object execIdCall(
IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (!f.hasTag(BIG_INT_TAG)) {
return super.execIdCall(f, cx, scope, thisObj, args);
}
int id = f.methodId();
if (id == Id_constructor) {
if (thisObj == null) {
// new BigInt(val) throws TypeError.
throw ScriptRuntime.typeErrorById("msg.not.ctor", BIG_INT_TAG);
}
BigInteger val = (args.length >= 1) ? ScriptRuntime.toBigInt(args[0]) : BigInteger.ZERO;
// BigInt(val) converts val to a BigInteger value.
return val;
private static Object js_toSource(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
return "(new BigInt(" + ScriptRuntime.toString(toSelf(thisObj).bigIntValue) + "))";
}

} else if (id < Id_constructor) {
return execConstructorCall(id, args);
}
private static Object js_asIntOrUintN(boolean isSigned, Object[] args) {
int bits = ScriptRuntime.toIndex(args.length < 1 ? Undefined.instance : args[0]);
BigInteger bigInt = ScriptRuntime.toBigInt(args.length < 2 ? Undefined.instance : args[1]);

// The rest of BigInt.prototype methods require thisObj to be BigInt
BigInteger value = ensureType(thisObj, NativeBigInt.class, f).bigIntValue;

switch (id) {
case Id_toString:
case Id_toLocaleString:
{
// toLocaleString is just an alias for toString for now
int base =
(args.length == 0 || args[0] == Undefined.instance)
? 10
: ScriptRuntime.toInt32(args[0]);
return ScriptRuntime.bigIntToString(value, base);
}

case Id_toSource:
return "(new BigInt(" + ScriptRuntime.toString(value) + "))";

case Id_valueOf:
return value;

default:
throw new IllegalArgumentException(String.valueOf(id));
if (bits == 0) {
return BigInteger.ZERO;
}
}

private static Object execConstructorCall(int id, Object[] args) {
switch (id) {
case ConstructorId_asIntN:
case ConstructorId_asUintN:
{
int bits =
ScriptRuntime.toIndex(args.length < 1 ? Undefined.instance : args[0]);
BigInteger bigInt =
ScriptRuntime.toBigInt(args.length < 2 ? Undefined.instance : args[1]);

if (bits == 0) {
return BigInteger.ZERO;
}

byte[] bytes = bigInt.toByteArray();

int newBytesLen = (bits / Byte.SIZE) + 1;
if (newBytesLen > bytes.length) {
return bigInt;
}

byte[] newBytes =
Arrays.copyOfRange(bytes, bytes.length - newBytesLen, bytes.length);

int mod = bits % Byte.SIZE;
switch (id) {
case ConstructorId_asIntN:
if (mod == 0) {
newBytes[0] = newBytes[1] < 0 ? (byte) -1 : 0;
} else if ((newBytes[0] & (1 << (mod - 1))) != 0) {
newBytes[0] = (byte) (newBytes[0] | (-1 << mod));
} else {
newBytes[0] = (byte) (newBytes[0] & ((1 << mod) - 1));
}
break;
case ConstructorId_asUintN:
newBytes[0] = (byte) (newBytes[0] & ((1 << mod) - 1));
break;
}
return new BigInteger(newBytes);
}

default:
throw new IllegalArgumentException(String.valueOf(id));
byte[] bytes = bigInt.toByteArray();

int newBytesLen = (bits / Byte.SIZE) + 1;
if (newBytesLen > bytes.length) {
return bigInt;
}
}

@Override
public String toString() {
return ScriptRuntime.bigIntToString(bigIntValue, 10);
}
byte[] newBytes = Arrays.copyOfRange(bytes, bytes.length - newBytesLen, bytes.length);

@Override
protected int findPrototypeId(Symbol k) {
if (SymbolKey.TO_STRING_TAG.equals(k)) {
return SymbolId_toStringTag;
int mod = bits % Byte.SIZE;
if (isSigned) {
if (mod == 0) {
newBytes[0] = newBytes[1] < 0 ? (byte) -1 : 0;
} else if ((newBytes[0] & (1 << (mod - 1))) != 0) {
newBytes[0] = (byte) (newBytes[0] | (-1 << mod));
} else {
newBytes[0] = (byte) (newBytes[0] & ((1 << mod) - 1));
}
} else {
newBytes[0] = (byte) (newBytes[0] & ((1 << mod) - 1));
}
return 0;
return new BigInteger(newBytes);
}

@Override
protected int findPrototypeId(String s) {
int id;
switch (s) {
case "constructor":
id = Id_constructor;
break;
case "toString":
id = Id_toString;
break;
case "toLocaleString":
id = Id_toLocaleString;
break;
case "toSource":
id = Id_toSource;
break;
case "valueOf":
id = Id_valueOf;
break;
default:
id = 0;
break;
}
return id;
public String toString() {
return ScriptRuntime.bigIntToString(bigIntValue, 10);
}

private static final int ConstructorId_asIntN = -1,
ConstructorId_asUintN = -2,
Id_constructor = 1,
Id_toString = 2,
Id_toLocaleString = 3,
Id_toSource = 4,
Id_valueOf = 5,
SymbolId_toStringTag = 6,
MAX_PROTOTYPE_ID = SymbolId_toStringTag;

private BigInteger bigIntValue;
}
Loading

0 comments on commit 030acb5

Please sign in to comment.