package org.jruby.runtime;
import org.jruby.Ruby;
import org.jruby.ast.ArgsNode;
import org.jruby.ast.ArgumentNode;
import org.jruby.ast.ForNode;
import org.jruby.ast.IterNode;
import org.jruby.ast.MultipleAsgnNode;
import org.jruby.ast.Node;
import org.jruby.ast.PostExeNode;
import org.jruby.ast.PreExeNode;
import org.jruby.ast.StarNode;
import org.jruby.ast.UnnamedRestArgNode;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.TypeConverter;
import static org.jruby.runtime.Arity.UNLIMITED_ARGUMENTS;
public class Signature {
public enum Rest {
NONE, NORM, ANON, STAR;
private static final Rest[] VALUES = values();
public static Rest fromOrdinal(int ordinal) {
if (ordinal < 0 || ordinal >= VALUES.length) {
throw new RuntimeException("invalid Rest: " + ordinal);
}
return VALUES[ordinal];
}
}
public static final Signature NO_ARGUMENTS = new Signature(0, 0, 0, Rest.NONE, 0, 0, -1);
public static final Signature ONE_ARGUMENT = new Signature(1, 0, 0, Rest.NONE, 0, 0, -1);
public static final Signature TWO_ARGUMENTS = new Signature(2, 0, 0, Rest.NONE, 0, 0, -1);
public static final Signature THREE_ARGUMENTS = new Signature(3, 0, 0, Rest.NONE, 0, 0, -1);
public static final Signature OPTIONAL = new Signature(0, 0, 0, Rest.NORM, 0, 0, -1);
public static final Signature ONE_REQUIRED = new Signature(1, 0, 0, Rest.NORM, 0, 0, -1);
public static final Signature TWO_REQUIRED = new Signature(2, 0, 0, Rest.NORM, 0, 0, -1);
public static final Signature THREE_REQUIRED = new Signature(3, 0, 0, Rest.NORM, 0, 0, -1);
private final short pre;
private final short opt;
private final Rest rest;
private final short post;
private final short kwargs;
private final short requiredKwargs;
private final Arity arity;
private final int keyRest;
public Signature(int pre, int opt, int post, Rest rest, int kwargs, int requiredKwargs, int keyRest) {
this.pre = (short) pre;
this.opt = (short) opt;
this.post = (short) post;
this.rest = rest;
this.kwargs = (short) kwargs;
this.requiredKwargs = (short) requiredKwargs;
this.keyRest = keyRest;
if (rest != Rest.NONE || opt != 0) {
arity = Arity.createArity(-(required() + 1));
} else {
arity = Arity.fixed(required() + getRequiredKeywordForArityCount());
}
}
public int getRequiredKeywordForArityCount() {
return requiredKwargs > 0 ? 1 : 0;
}
public boolean restKwargs() {
return keyRest != -1;
}
public int pre() { return pre; }
public int opt() { return opt; }
public Rest rest() { return rest; }
public int post() { return post; }
public boolean hasKwargs() { return kwargs > 0 || restKwargs(); }
public boolean hasRest() { return rest != Rest.NONE; }
public int keyRest() { return keyRest; }
public boolean isFixed() {
return arityValue() >= 0;
}
public int required() { return pre + post; }
public Arity arity() { return arity; }
public int arityValue() {
int oneForKeywords = requiredKwargs > 0 ? 1 : 0;
int fixedValue = pre() + post() + oneForKeywords;
boolean hasOptionalKeywords = kwargs - requiredKwargs > 0;
if (opt() > 0 || rest() != Rest.NONE || (hasOptionalKeywords || restKwargs()) && oneForKeywords == 0) {
return -1 * (fixedValue + 1);
}
return fixedValue;
}
public static Signature from(Arity arity) {
switch(arity.required()) {
case 0:
return arity.isFixed() ? Signature.NO_ARGUMENTS : Signature.OPTIONAL;
case 1:
return arity.isFixed() ? Signature.ONE_ARGUMENT : Signature.ONE_REQUIRED;
case 2:
return arity.isFixed() ? Signature.TWO_ARGUMENTS : Signature.TWO_REQUIRED;
case 3:
return arity.isFixed() ? Signature.THREE_ARGUMENTS : Signature.THREE_REQUIRED;
}
throw new UnsupportedOperationException("We do not know enough about the arity to convert it to a signature");
}
public static Signature from(int pre, int opt, int post, int kwargs, int requiredKwargs, Rest rest, int keyRest) {
if (opt == 0 && post == 0 && kwargs == 0 && keyRest == -1) {
switch (pre) {
case 0:
switch (rest) {
case NONE:
return Signature.NO_ARGUMENTS;
case NORM:
return Signature.OPTIONAL;
}
break;
case 1:
switch (rest) {
case NONE:
return Signature.ONE_ARGUMENT;
case NORM:
return Signature.ONE_REQUIRED;
}
break;
case 2:
switch (rest) {
case NONE:
return Signature.TWO_ARGUMENTS;
case NORM:
return Signature.TWO_REQUIRED;
}
break;
case 3:
switch (rest) {
case NONE:
return Signature.THREE_ARGUMENTS;
case NORM:
return Signature.THREE_REQUIRED;
}
break;
}
}
return new Signature(pre, opt, post, rest, kwargs, requiredKwargs, keyRest);
}
public static Signature from(ArgsNode args) {
ArgumentNode restArg = args.getRestArgNode();
Rest rest = restArg != null ? restFromArg(restArg) : Rest.NONE;
return Signature.from(args.getPreCount(), args.getOptionalArgsCount(), args.getPostCount(),
args.getKeywordCount(), args.getRequiredKeywordCount(),rest,args.hasKeyRest() ? args.getKeyRest().getIndex() : -1);
}
public static Signature from(IterNode iter) {
if (iter instanceof ForNode) return from((ForNode)iter);
if (iter instanceof PreExeNode) return from((PreExeNode)iter);
if (iter instanceof PostExeNode) return from((PostExeNode)iter);
return from((ArgsNode) iter.getVarNode());
}
private static Rest restFromArg(Node restArg) {
Rest rest;
if (restArg instanceof UnnamedRestArgNode) {
UnnamedRestArgNode anonRest = (UnnamedRestArgNode) restArg;
if (anonRest.isStar()) {
rest = Rest.STAR;
} else {
rest = Rest.ANON;
}
} else if (restArg instanceof StarNode) {
rest = Rest.STAR;
} else {
rest = Rest.NORM;
}
return rest;
}
public static Signature from(ForNode iter) {
Node var = iter.getVarNode();
if (var instanceof MultipleAsgnNode) {
MultipleAsgnNode masgn = (MultipleAsgnNode)var;
Rest rest = masgn.getRest() == null ? Rest.NONE : restFromArg(masgn.getRest());
int argCount = masgn.getPreCount();
if (rest == Rest.NONE && argCount == 1) argCount = 2;
return Signature.from(argCount, 0, 0, 0, 0, rest, -1);
}
return Signature.ONE_ARGUMENT;
}
public static Signature from(PreExeNode iter) {
return Signature.NO_ARGUMENTS;
}
public static Signature from(PostExeNode iter) {
return Signature.NO_ARGUMENTS;
}
private static final int MAX_ENCODED_ARGS_EXPONENT = 8;
private static final int MAX_ENCODED_ARGS_MASK = 0xFF;
private static final int ENCODE_RESTKWARGS_SHIFT = 0;
private static final int ENCODE_REST_SHIFT = ENCODE_RESTKWARGS_SHIFT + MAX_ENCODED_ARGS_EXPONENT;
private static final int ENCODE_REQKWARGS_SHIFT = ENCODE_REST_SHIFT + MAX_ENCODED_ARGS_EXPONENT;
private static final int ENCODE_KWARGS_SHIFT = ENCODE_REQKWARGS_SHIFT + MAX_ENCODED_ARGS_EXPONENT;
private static final int ENCODE_POST_SHIFT = ENCODE_KWARGS_SHIFT + MAX_ENCODED_ARGS_EXPONENT;
private static final int ENCODE_OPT_SHIFT = ENCODE_POST_SHIFT + MAX_ENCODED_ARGS_EXPONENT;
private static final int ENCODE_PRE_SHIFT = ENCODE_OPT_SHIFT + MAX_ENCODED_ARGS_EXPONENT;
public long encode() {
return
((long)pre << ENCODE_PRE_SHIFT) |
((long)opt << ENCODE_OPT_SHIFT) |
((long)post << ENCODE_POST_SHIFT) |
((long)kwargs << ENCODE_KWARGS_SHIFT) |
((long)requiredKwargs << ENCODE_REQKWARGS_SHIFT) |
(rest.ordinal() << ENCODE_REST_SHIFT) |
(keyRest & 0xFF) << ENCODE_RESTKWARGS_SHIFT;
}
public static Signature decode(long l) {
return Signature.from(
(int)(l >>> ENCODE_PRE_SHIFT) & MAX_ENCODED_ARGS_MASK,
(int)(l >>> ENCODE_OPT_SHIFT) & MAX_ENCODED_ARGS_MASK,
(int)(l >>> ENCODE_POST_SHIFT) & MAX_ENCODED_ARGS_MASK,
(int)(l >>> ENCODE_KWARGS_SHIFT) & MAX_ENCODED_ARGS_MASK,
(int)(l >>> ENCODE_REQKWARGS_SHIFT) & MAX_ENCODED_ARGS_MASK,
Rest.fromOrdinal((int)((l >>> ENCODE_REST_SHIFT) & MAX_ENCODED_ARGS_MASK)),
(byte)((l >>> ENCODE_RESTKWARGS_SHIFT) & MAX_ENCODED_ARGS_MASK)
);
}
public String toString() {
return "signature(pre=" + pre + ",opt=" + opt + ",post=" + post + ",rest=" + rest + ",kwargs=" + kwargs + ",kwreq=" + requiredKwargs + ",kwrest=" + keyRest + ")";
}
public void checkArity(Ruby runtime, IRubyObject[] args) {
if (args.length < required()) {
throw runtime.newArgumentError(args.length, required(), hasRest() ? UNLIMITED_ARGUMENTS : (required() + opt));
}
if (rest == Rest.NONE || rest == Rest.ANON) {
if (args.length > required() + opt()) {
if (hasKwargs() && !TypeConverter.checkHashType(runtime, args[args.length - 1]).isNil()) {
if (args.length - 1 > required() + opt()) {
throw runtime.newArgumentError(args.length, required(), hasRest() ? UNLIMITED_ARGUMENTS : (required() + opt));
}
} else {
throw runtime.newArgumentError(args.length, required(), hasRest() ? UNLIMITED_ARGUMENTS : (required() + opt));
}
}
}
}
public boolean equals(Object other) {
if (!(other instanceof Signature)) return false;
Signature otherSig = (Signature) other;
return pre == otherSig.pre &&
opt == otherSig.opt &&
post == otherSig.post &&
rest == otherSig.rest &&
kwargs == otherSig.kwargs &&
requiredKwargs == otherSig.requiredKwargs &&
keyRest == otherSig.keyRest;
}
}