Skip to content

Commit

Permalink
Merge branch 'master' into scratch/satish/support-default-arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
0xe authored Sep 5, 2024
2 parents a3ddc54 + 3577b83 commit b892d86
Show file tree
Hide file tree
Showing 17 changed files with 937 additions and 113 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
server-id: github
settings-path: ${{ github.workspace }}
- name: Set up Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 # v4.0.1
- name: Publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,14 +244,14 @@ static void put(Context cx, Scriptable o, int p, Object v, boolean isThrow) {
}

/**
* Implement the ECMAScript abstract operation "GroupBy" defined in section 7.3.35 of ECMA262.
* Implement the ECMAScript abstract operation "GroupBy"
*
* @param cx
* @param scope
* @param items
* @param callback
* @param keyCoercion
* @see <a href="https://tc39.es/ecma262/#sec-groupby"></a>
* @see <a href="https://262.ecma-international.org/15.0/#sec-groupby"></a>
*/
static Map<Object, List<Object>> groupBy(
Context cx,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class CodeGenerator extends Icode {
private ScriptNode scriptOrFn;
private int iCodeTop;
private int stackDepth;
private int lineNumber;
private int lineNumber = -1;
private int doubleTableTop;

private final HashMap<String, Integer> strings = new HashMap<>();
Expand Down
6 changes: 6 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/IRFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -2095,12 +2095,18 @@ private Node createAssignment(int assignType, Node left, Node right) {
case Token.ASSIGN_BITOR:
assignOp = Token.BITOR;
break;
case Token.ASSIGN_LOGICAL_OR:
assignOp = Token.OR;
break;
case Token.ASSIGN_BITXOR:
assignOp = Token.BITXOR;
break;
case Token.ASSIGN_BITAND:
assignOp = Token.BITAND;
break;
case Token.ASSIGN_LOGICAL_AND:
assignOp = Token.AND;
break;
case Token.ASSIGN_LSH:
assignOp = Token.LSH;
break;
Expand Down
129 changes: 129 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/LambdaAccessorSlot.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package org.mozilla.javascript;

import java.util.function.BiConsumer;
import java.util.function.Function;

/**
* A specialized property accessor using lambda functions, similar to {@link LambdaSlot}, but allows
* defining properties with getter and setter lambdas that require access to the owner object
* ('this'). This enables the implementation of properties that can access instance fields of the
* owner.
*
* <p>Unlike {@link LambdaSlot}, Lambda functions used to define getter and setter logic require the
* owner's `Scriptable` object as one of the parameters. This is particularly useful for
* implementing properties that behave like standard JavaScript properties, but are implemented with
* native functionality without the need for reflection.
*/
public class LambdaAccessorSlot extends Slot {
private transient Function<Scriptable, Object> getter;
private transient BiConsumer<Scriptable, Object> setter;
private LambdaFunction getterFunction;
private LambdaFunction setterFunction;

LambdaAccessorSlot(Object name, int index) {
super(name, index, 0);
}

LambdaAccessorSlot(Slot oldSlot) {
super(oldSlot);
}

@Override
boolean isValueSlot() {
return false;
}

@Override
boolean isSetterSlot() {
return true;
}

@Override
ScriptableObject getPropertyDescriptor(Context cx, Scriptable scope) {
ScriptableObject desc = (ScriptableObject) cx.newObject(scope);

int attr = getAttributes();
boolean es6 = cx.getLanguageVersion() >= Context.VERSION_ES6;
if (es6) {
if (getterFunction == null && setterFunction == null) {
desc.defineProperty(
"writable",
(attr & ScriptableObject.READONLY) == 0,
ScriptableObject.EMPTY);
}
} else {
desc.setCommonDescriptorProperties(
attr, getterFunction == null && setterFunction == null);
}

if (getterFunction != null) {
desc.defineProperty("get", this.getterFunction, ScriptableObject.EMPTY);
}

if (setterFunction != null) {
desc.defineProperty("set", this.setterFunction, ScriptableObject.EMPTY);
} else if (es6) {
desc.defineProperty("set", Undefined.instance, ScriptableObject.EMPTY);
}

if (es6) {
desc.defineProperty(
"enumerable", (attr & ScriptableObject.DONTENUM) == 0, ScriptableObject.EMPTY);
desc.defineProperty(
"configurable",
(attr & ScriptableObject.PERMANENT) == 0,
ScriptableObject.EMPTY);
}
return desc;
}

@Override
public boolean setValue(Object value, Scriptable scope, Scriptable start, boolean isThrow) {
if (setter == null) {
if (getter != null) {
throwNoSetterException(start, value);
return true;
}
} else {
setter.accept(start, value);
return true;
}

return super.setValue(value, start, start, isThrow);
}

@Override
public Object getValue(Scriptable owner) {
if (getter != null) {
return getter.apply(owner);
}
return super.getValue(owner);
}

public void setGetter(Scriptable scope, Function<Scriptable, Object> getter) {
this.getter = getter;
if (getter != null) {
this.getterFunction =
new LambdaFunction(
scope,
"get " + super.name,
0,
(cx1, scope1, thisObj, args) -> getter.apply(thisObj));
}
}

public void setSetter(Scriptable scope, BiConsumer<Scriptable, Object> setter) {
this.setter = setter;
if (setter != null) {
this.setterFunction =
new LambdaFunction(
scope,
"set " + super.name,
1,
(cx1, scope1, thisObj, args) -> {
setter.accept(thisObj, args[0]);
return Undefined.instance;
});
}
}
}
22 changes: 22 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

package org.mozilla.javascript;

import java.util.function.BiConsumer;
import java.util.function.Function;

/**
* This class implements a JavaScript function that may be used as a constructor by delegating to an
* interface that can be easily implemented as a lambda. The LambdaFunction class may be used to add
Expand Down Expand Up @@ -120,6 +123,25 @@ public void definePrototypeProperty(Symbol key, Object value, int attributes) {
proto.defineProperty(key, value, attributes);
}

public void definePrototypeProperty(
Context cx,
String name,
java.util.function.Function<Scriptable, Object> getter,
int attributes) {
ScriptableObject proto = getPrototypeScriptable();
proto.defineProperty(cx, name, getter, null, attributes);
}

public void definePrototypeProperty(
Context cx,
String name,
Function<Scriptable, Object> getter,
BiConsumer<Scriptable, Object> setter,
int attributes) {
ScriptableObject proto = getPrototypeScriptable();
proto.defineProperty(cx, name, getter, setter, attributes);
}

/**
* Define a function property directly on the constructor that is implemented under the covers
* by a LambdaFunction.
Expand Down
2 changes: 2 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -933,8 +933,10 @@ public boolean hasSideEffects() {
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_LOGICAL_OR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LOGICAL_AND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
Expand Down
85 changes: 85 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.mozilla.javascript.ScriptRuntime.StringIdOrIndex;
Expand Down Expand Up @@ -1690,6 +1691,63 @@ public void defineProperty(
slot.setter = setter;
}

/**
* Define a property on this object that is implemented using lambda functions accepting
* Scriptable `this` object as first parameter. Unlike with `defineProperty(String name,
* Supplier<Object> getter, Consumer<Object> setter, int attributes)` where getter and setter
* need to have access to target object instance, this allows for defining properties on
* LambdaConstructor prototype providing getter and setter logic with java instance methods. If
* a property with the same name already exists, then it will be replaced. This property will
* appear to the JavaScript user exactly like descriptor with a getter and setter, just as if
* they had been defined in JavaScript using Object.defineOwnProperty.
*
* @param name the name of the property
* @param getter a function that given Scriptable `this` returns the value of the property. If
* null, throws typeError
* @param setter a function that Scriptable `this` and a value sets the value of the property,
* by calling appropriate method on `this`. If null, then the value will be set directly and
* may not be retrieved by the getter.
* @param attributes the attributes to set on the property
*/
public void defineProperty(
Context cx,
String name,
java.util.function.Function<Scriptable, Object> getter,
BiConsumer<Scriptable, Object> setter,
int attributes) {
if (getter == null && setter == null)
throw ScriptRuntime.typeError("at least one of {getter, setter} is required");

slotMap.compute(
name,
0,
(id, index, existing) ->
ensureLambdaAccessorSlot(
cx, id, index, existing, getter, setter, attributes));
}

private LambdaAccessorSlot createLambdaAccessorSlot(
Object name,
int index,
Slot existing,
java.util.function.Function<Scriptable, Object> getter,
BiConsumer<Scriptable, Object> setter,
int attributes) {
LambdaAccessorSlot slot;
if (existing == null) {
slot = new LambdaAccessorSlot(name, index);
} else if (existing instanceof LambdaAccessorSlot) {
slot = (LambdaAccessorSlot) existing;
} else {
slot = new LambdaAccessorSlot(existing);
}

slot.setGetter(this, getter);
slot.setSetter(this, setter);
slot.setAttributes(attributes);
return slot;
}

protected void checkPropertyDefinition(ScriptableObject desc) {
Object getter = getProperty(desc, "get");
if (getter != NOT_FOUND && getter != Undefined.instance && !(getter instanceof Callable)) {
Expand Down Expand Up @@ -2695,6 +2753,33 @@ private static LambdaSlot ensureLambdaSlot(Object name, int index, Slot existing
}
}

private LambdaAccessorSlot ensureLambdaAccessorSlot(
Context cx,
Object name,
int index,
Slot existing,
java.util.function.Function<Scriptable, Object> getter,
BiConsumer<Scriptable, Object> setter,
int attributes) {
var newSlot = createLambdaAccessorSlot(name, index, existing, getter, setter, attributes);
var newDesc = newSlot.getPropertyDescriptor(cx, this);
checkPropertyDefinition(newDesc);

if (existing == null) {
checkPropertyChange(name, null, newDesc);
return newSlot;
} else if (existing instanceof LambdaAccessorSlot) {
var slot = (LambdaAccessorSlot) existing;
var existingDesc = slot.getPropertyDescriptor(cx, this);
checkPropertyChange(name, existingDesc, newDesc);
return newSlot;
} else {
var existingDesc = existing.getPropertyDescriptor(cx, this);
checkPropertyChange(name, existingDesc, newDesc);
return newSlot;
}
}

private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
final long stamp = slotMap.readLock();
Expand Down
Loading

0 comments on commit b892d86

Please sign in to comment.