/** * Copyright (c) 2012-2016 André Bargull * Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms. * * <https://github.com/anba/es6draft> */ package com.github.anba.es6draft.runtime.types.builtins; import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.Realm; import com.github.anba.es6draft.runtime.types.Callable; import com.github.anba.es6draft.runtime.types.Intrinsics; import com.github.anba.es6draft.runtime.types.Property; import com.github.anba.es6draft.runtime.types.PropertyDescriptor; /** * <h1>9 Ordinary and Exotic Objects Behaviours</h1> * <ul> * <li>9.3 Built-in Function Objects * </ul> */ public abstract class BuiltinFunction extends OrdinaryObject implements Callable, Cloneable { /** * Function name for anonymous functions. */ protected static final String ANONYMOUS = ""; /** [[Realm]] */ private final Realm realm; private final String name; private final int arity; // Store default "name" and "length" inline to avoid allocating properties table space. private boolean hasDefaultName, hasDefaultLength; private MethodHandle callMethod; private Object methodInfo; /** * Creates a new built-in function. * * @param realm * the realm instance * @param name * the function name * @param arity * the function arity */ protected BuiltinFunction(Realm realm, String name, int arity) { super(realm); this.realm = realm; this.name = name; this.arity = arity; } /** * Returns the i-th argument or {@code undefined} if the argument index is out of bounds. * * @param arguments * the function arguments * @param index * the argument index * @return the requested argument or undefined if not present */ protected static final Object argument(Object[] arguments, int index) { return arguments.length > index ? arguments[index] : UNDEFINED; } /** * Returns the i-th argument or {@code defaultValue} if the argument index is out of bounds. * * @param arguments * the function arguments * @param index * the argument index * @param defaultValue * the default value for absent arguments * @return the requested argument or <var>defaultValue</var> if not present */ protected static final Object argument(Object[] arguments, int index, Object defaultValue) { return arguments.length > index ? arguments[index] : defaultValue; } /** * Returns the callee execution context. * * @return the callee execution context */ protected final ExecutionContext calleeContext() { return realm.defaultContext(); } /** * Creates the default function properties, i.e. 'name' and 'length', and initializes the [[Prototype]] slot to the * <code>%FunctionPrototype%</code> object. */ protected final void createDefaultFunctionProperties() { // Function.prototype is the [[Prototype]] for built-in functions, cf. 17 setPrototype(realm.getIntrinsic(Intrinsics.FunctionPrototype)); // "length" property of function objects, cf. 19.2.4.1 hasDefaultLength = true; // anonymous functions do not have an own "name" property, cf. 19.2.4.2 if (!name.isEmpty()) { hasDefaultName = true; } } @Override protected abstract BuiltinFunction clone(); @Override public final BuiltinFunction clone(ExecutionContext cx) { BuiltinFunction f = clone(); f.setPrototype(getPrototype()); return f; } @Override public final String toSource(ExecutionContext cx) { return FunctionSource.nativeCode(name); } /** * Returns the function's name. * * @return the function name */ public final String getName() { return name; } /** * Returns the function's arity. * * @return the function arity */ public final int getArity() { return arity; } /** * [[Realm]] * * @return the bound realm */ public final Realm getRealm() { return realm; } @Override public final Realm getRealm(ExecutionContext cx) { /* 7.3.22 GetFunctionRealm ( obj ) */ return realm; } protected MethodHandles.Lookup lookup() { return MethodHandles.publicLookup(); } /** * Returns `(? extends BuiltinFunction, ExecutionContext, Object, Object[]) {@literal ->} Object` method-handle. * * @return the call method handle */ public MethodHandle getCallMethod() { if (callMethod == null) { try { Method method = getClass().getDeclaredMethod("call", ExecutionContext.class, Object.class, Object[].class); callMethod = lookup().unreflect(method); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } return callMethod; } /** * Returns the method info object. * * @return the method info object */ public final Object getMethodInfo() { if (methodInfo == null) { methodInfo = new Object(); } return methodInfo; } /** * 9.3.1 [[Call]] (thisArgument, argumentsList) */ @Override public Object tailCall(ExecutionContext callerContext, Object thisValue, Object... args) throws Throwable { return call(callerContext, thisValue, args); } @Override public String toString() { return String.format("%s, name=%s, arity=%d", super.toString(), name, arity); } @Override public long getLength() { if (hasDefaultLength) { return arity; } return super.getLength(); } @Override protected boolean setPropertyValue(ExecutionContext cx, String propertyKey, Object value, Property current) { assert !(hasDefaultName && "name".equals(propertyKey)); assert !(hasDefaultLength && "length".equals(propertyKey)); return super.setPropertyValue(cx, propertyKey, value, current); } @Override protected boolean has(ExecutionContext cx, String propertyKey) { if (hasDefaultName && "name".equals(propertyKey)) { return true; } if (hasDefaultLength && "length".equals(propertyKey)) { return true; } return super.has(cx, propertyKey); } @Override protected boolean hasOwnProperty(ExecutionContext cx, String propertyKey) { if (hasDefaultName && "name".equals(propertyKey)) { return true; } if (hasDefaultLength && "length".equals(propertyKey)) { return true; } return super.hasOwnProperty(cx, propertyKey); } @Override protected Property getProperty(ExecutionContext cx, String propertyKey) { if (hasDefaultName && "name".equals(propertyKey)) { return new Property(name, false, false, true); } if (hasDefaultLength && "length".equals(propertyKey)) { return new Property(arity, false, false, true); } return super.getProperty(cx, propertyKey); } @Override protected boolean defineProperty(ExecutionContext cx, String propertyKey, PropertyDescriptor desc) { if (hasDefaultName && "name".equals(propertyKey)) { hasDefaultName = false; defineOwnPropertyUnchecked(propertyKey, new Property(name, false, false, true)); } if (hasDefaultLength && "length".equals(propertyKey)) { hasDefaultLength = false; defineOwnPropertyUnchecked(propertyKey, new Property(arity, false, false, true)); } return super.defineProperty(cx, propertyKey, desc); } @Override protected boolean deleteProperty(ExecutionContext cx, String propertyKey) { if (hasDefaultName && "name".equals(propertyKey)) { hasDefaultName = false; return true; } if (hasDefaultLength && "length".equals(propertyKey)) { hasDefaultLength = false; return true; } return super.deleteProperty(cx, propertyKey); } @Override protected List<String> getEnumerableKeys(ExecutionContext cx) { if (hasDefaultLength || hasDefaultName) { int totalSize = countProperties(false); if (hasDefaultLength) { totalSize += 1; } if (hasDefaultName) { totalSize += 1; } ArrayList<String> keys = new ArrayList<>(totalSize); appendIndexedProperties(keys); if (hasDefaultLength) { keys.add("length"); } if (hasDefaultName) { keys.add("name"); } appendProperties(keys); return keys; } return super.getEnumerableKeys(cx); } @Override protected Enumerability isEnumerableOwnProperty(String propertyKey) { if (hasDefaultName && "name".equals(propertyKey)) { return Enumerability.NonEnumerable; } if (hasDefaultLength && "length".equals(propertyKey)) { return Enumerability.NonEnumerable; } return super.isEnumerableOwnProperty(propertyKey); } @Override protected List<Object> getOwnPropertyKeys(ExecutionContext cx) { if (hasDefaultLength || hasDefaultName) { int totalSize = countProperties(true); if (hasDefaultLength) { totalSize += 1; } if (hasDefaultName) { totalSize += 1; } /* step 1 */ ArrayList<Object> ownKeys = new ArrayList<>(totalSize); /* step 2 */ appendIndexedProperties(ownKeys); /* step 3 */ if (hasDefaultLength) { ownKeys.add("length"); } if (hasDefaultName) { ownKeys.add("name"); } appendProperties(ownKeys); /* step 4 */ appendSymbolProperties(ownKeys); /* step 5 */ return ownKeys; } return super.getOwnPropertyKeys(cx); } }