Skip to content

Commit

Permalink
[Truffle] Very basic keyword arguments.
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisseaton committed Dec 11, 2014
1 parent c217f32 commit a560b2e
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 24 deletions.
Expand Up @@ -132,7 +132,7 @@ private static RubyRootNode makeGenericMethod(RubyContext context, MethodDetails
optional = methodDetails.getMethodAnnotation().optional();
}

final Arity arity = new Arity(required, optional, methodDetails.getMethodAnnotation().argumentsAsArray());
final Arity arity = new Arity(required, optional, methodDetails.getMethodAnnotation().argumentsAsArray(), false);

final List<RubyNode> argumentsNodes = new ArrayList<>();

Expand Down
Expand Up @@ -261,7 +261,7 @@ public RubyNilClass attrReader(RubyModule module, Object[] args) {
public static void attrReader(RubyNode currentNode, RubyContext context, SourceSection sourceSection, RubyModule module, String name) {
CompilerDirectives.transferToInterpreter();

final CheckArityNode checkArity = new CheckArityNode(context, sourceSection, new Arity(0, 0, false));
final CheckArityNode checkArity = new CheckArityNode(context, sourceSection, new Arity(0, 0, false, false));

final SelfNode self = new SelfNode(context, sourceSection);
final ReadInstanceVariableNode readInstanceVariable = new ReadInstanceVariableNode(context, sourceSection, "@" + name, self, false);
Expand Down Expand Up @@ -313,7 +313,7 @@ public RubyNilClass attrWriter(RubyModule module, Object[] args) {
public static void attrWriter(RubyNode currentNode, RubyContext context, SourceSection sourceSection, RubyModule module, String name) {
CompilerDirectives.transferToInterpreter();

final CheckArityNode checkArity = new CheckArityNode(context, sourceSection, new Arity(1, 0, false));
final CheckArityNode checkArity = new CheckArityNode(context, sourceSection, new Arity(1, 0, false, false));

final SelfNode self = new SelfNode(context, sourceSection);
final ReadPreArgumentNode readArgument = new ReadPreArgumentNode(context, sourceSection, 0, MissingArgumentBehaviour.RUNTIME_ERROR);
Expand Down
Expand Up @@ -40,15 +40,17 @@ public void executeVoid(VirtualFrame frame) {
}

private boolean checkArity(int given) {
if (arity.getRequired() != 0 && given < arity.getRequired()) {
return false;
if (arity.hasKeywords()) {
given -= 1;
}

if (!arity.allowsMore() && given > arity.getRequired() + arity.getOptional()) {
if (arity.getRequired() != 0 && given < arity.getRequired()) {
return false;
} else if (!arity.allowsMore() && given > arity.getRequired() + arity.getOptional()) {
return false;
} else {
return true;
}

return true;
}

@Override
Expand Down
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.nodes.methods.arguments;

import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.utilities.BranchProfile;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.RubyValueProfile;
import org.jruby.truffle.runtime.RubyArguments;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.UndefinedPlaceholder;
import org.jruby.truffle.runtime.core.RubyHash;
import org.jruby.truffle.runtime.core.RubyString;

import java.util.Map;

public class ReadKeywordArgumentNode extends RubyNode {

private final String name;
@Child protected RubyNode defaultValue;

public ReadKeywordArgumentNode(RubyContext context, SourceSection sourceSection, String name, RubyNode defaultValue) {
super(context, sourceSection);
this.name = name;
this.defaultValue = defaultValue;
}

@Override
public Object execute(VirtualFrame frame) {
notDesignedForCompilation();

final int last = RubyArguments.getUserArgumentsCount(frame.getArguments()) - 1;

if (last == -1) {
return defaultValue.execute(frame);
}

final Object hashValue = RubyArguments.getUserArgument(frame.getArguments(), last);

if (!(hashValue instanceof RubyHash)) {
return defaultValue.execute(frame);
}

final RubyHash hash = (RubyHash) hashValue;

Object value = null;

for (Map.Entry<Object, Object> entry : hash.slowToMap().entrySet()) {
if (entry.getKey().toString().equals(name)) {
value = entry.getValue();
break;
}
}

if (value == null) {
return defaultValue.execute(frame);
}

return value;
}

}
Expand Up @@ -14,11 +14,13 @@ public class Arity {
private final int required;
private final int optional;
private final boolean allowsMore;
private final boolean hasKeywords;

public Arity(int required, int optional, boolean allowsMore) {
public Arity(int required, int optional, boolean allowsMore, boolean hasKeywords) {
this.required = required;
this.optional = optional;
this.allowsMore = allowsMore;
this.hasKeywords = hasKeywords;
}

public int getRequired() {
Expand All @@ -33,4 +35,8 @@ public boolean allowsMore() {
return allowsMore;
}

public boolean hasKeywords() {
return hasKeywords;
}

}
Expand Up @@ -20,6 +20,7 @@
import org.jruby.truffle.nodes.control.SequenceNode;
import org.jruby.truffle.nodes.core.ArrayIndexNodeFactory;
import org.jruby.truffle.nodes.core.ArraySliceNodeFactory;
import org.jruby.truffle.nodes.literal.NilLiteralNode;
import org.jruby.truffle.nodes.methods.arguments.*;
import org.jruby.truffle.nodes.methods.locals.ReadLocalVariableNodeFactory;
import org.jruby.truffle.nodes.methods.locals.WriteLocalVariableNodeFactory;
Expand Down Expand Up @@ -91,13 +92,56 @@ public RubyNode visitArgsNode(org.jruby.ast.ArgsNode node) {
}
}

if (node.hasKwargs() && node.getKeywords() != null) {
for (org.jruby.ast.Node arg : node.getKeywords().childNodes()) {
sequence.add(arg.accept(this));
}
}

if (node.getBlock() != null) {
sequence.add(node.getBlock().accept(this));
}

return SequenceNode.sequence(context, sourceSection, sequence);
}

@Override
public RubyNode visitKeywordArgNode(org.jruby.ast.KeywordArgNode node) {
final SourceSection sourceSection = translate(node.getPosition());

final String name;
final RubyNode defaultValue;

final org.jruby.ast.Node firstChild = node.childNodes().get(0);

if (firstChild instanceof org.jruby.ast.LocalAsgnNode) {
final org.jruby.ast.LocalAsgnNode localAsgnNode = (org.jruby.ast.LocalAsgnNode) firstChild;
name = localAsgnNode.getName();

if (localAsgnNode.getValueNode() == null) {
defaultValue = new NilLiteralNode(context, sourceSection);
} else {
defaultValue = localAsgnNode.getValueNode().accept(this);
}
} else if (firstChild instanceof org.jruby.ast.DAsgnNode) {
final org.jruby.ast.DAsgnNode dAsgnNode = (org.jruby.ast.DAsgnNode) firstChild;
name = dAsgnNode.getName();

if (dAsgnNode.getValueNode() == null) {
defaultValue = new NilLiteralNode(context, sourceSection);
} else {
defaultValue = dAsgnNode.getValueNode().accept(this);
}
} else {
throw new UnsupportedOperationException();
}

final RubyNode readNode = new ReadKeywordArgumentNode(context, sourceSection, name, defaultValue);
final FrameSlot slot = methodBodyTranslator.getEnvironment().getFrameDescriptor().findFrameSlot(name);

return WriteLocalVariableNodeFactory.create(context, sourceSection, slot, readNode);
}

@Override
public RubyNode visitArgumentNode(org.jruby.ast.ArgumentNode node) {
final SourceSection sourceSection = translate(node.getPosition());
Expand Down
Expand Up @@ -61,7 +61,7 @@ public MethodDefinitionNode compileFunctionNode(SourceSection sourceSection, Str
*/

if (isBlock && argsNode.childNodes().size() == 2 && argsNode.getRestArgNode() instanceof org.jruby.ast.UnnamedRestArgNode) {
arityForCheck = new Arity(arity.getRequired(), 0, false);
arityForCheck = new Arity(arity.getRequired(), 0, false, false);
} else {
arityForCheck = arity;
}
Expand Down Expand Up @@ -201,7 +201,7 @@ private CallTarget withoutBlockDestructureSemantics(CallTarget callTarget) {
private static Arity getArity(org.jruby.ast.ArgsNode argsNode) {
final int minimum = argsNode.getRequiredArgsCount();
final int maximum = argsNode.getMaxArgumentsCount();
return new Arity(minimum, argsNode.getOptionalArgsCount(), maximum == -1);
return new Arity(minimum, argsNode.getOptionalArgsCount(), maximum == -1, argsNode.hasKwargs());
}

@Override
Expand Down
4 changes: 0 additions & 4 deletions spec/truffle/tags/language/lambda_tags.txt
@@ -1,6 +1,5 @@
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> ((*a, b)) { [a, b] }"
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> (a:) { a }"
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> (a: 1) { a }"
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> (**) { }"
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> (**k) { k }"
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> ((a, b, *c, d), (*e, f, g), (*h)) do\n [a, b, c, d, e, f, g, h]\n end"
Expand All @@ -10,12 +9,10 @@ fails:"A lambda literal -> () { } assigns variables from parameters for definiti
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> (a:, b: 1) { [a, b] }"
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> (a: 1, b:) { [a, b] }"
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> (a: @a = -> (a: 1) { a }, b:) do\n [a, b]\n end"
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> (a: 1, b: 2) { [a, b] }"
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> (a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l) do\n [a, b, c, d, e, f, g, h, k, l]\n end"
fails:"A lambda literal -> () { } assigns variables from parameters for definition \n @a = -> a, b=1, *c, d, e:, f: 2, g:, **k, &l do\n [a, b, c, d, e, f, g, k, l]\n end"
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda { |(*a, b)| [a, b] }"
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda { |a:| a }"
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda { |a: 1| a }"
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda { |**| }"
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda { |**k| k }"
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda do |(a, b, *c, d), (*e, f, g), (*h)|\n [a, b, c, d, e, f, g, h]\n end"
Expand All @@ -26,6 +23,5 @@ fails:"A lambda expression 'lambda { ... }' assigns variables from parameters fo
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda { |a:, b: 1| [a, b] }"
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda { |a: 1, b:| [a, b] }"
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda do |a: (@a = -> (a: 1) { a }), b:|\n [a, b]\n end"
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda { |a: 1, b: 2| [a, b] }"
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda do |a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l|\n [a, b, c, d, e, f, g, h, k, l]\n end"
fails:"A lambda expression 'lambda { ... }' assigns variables from parameters for definition \n @a = lambda do |a, b=1, *c, d, e:, f: 2, g:, **k, &l|\n [a, b, c, d, e, f, g, k, l]\n end"
7 changes: 0 additions & 7 deletions spec/truffle/tags/language/method_tags.txt
Expand Up @@ -24,7 +24,6 @@ fails:An attribute assignment method send with a middle splatted Object argument
fails:An attribute assignment method send with a trailing splatted Object argument raises a TypeError if #to_a does not return an Array
fails:"A method assigns local variables from method parameters for definition \n def m((*a, b)) [a, b] end"
fails:"A method assigns local variables from method parameters for definition \n def m(a:) a end"
fails:"A method assigns local variables from method parameters for definition \n def m(a: 1) a end"
fails:"A method assigns local variables from method parameters for definition \n def m(**) end"
fails:"A method assigns local variables from method parameters for definition \n def m(**k) k end"
fails:"A method assigns local variables from method parameters for definition \n def m((*a), (*b)) [a, b] end"
Expand All @@ -43,11 +42,9 @@ fails:"A method assigns local variables from method parameters for definition \n
fails:"A method assigns local variables from method parameters for definition \n def m(a=1, (b, *c), (d, (*e, f)))\n [a, b, c, d, e, f]\n end"
fails:"A method assigns local variables from method parameters for definition \n def m(a=1, b:) [a, b] end"
fails:"A method assigns local variables from method parameters for definition \n def m(a=1, b: 2) [a, b] end"
fails:"A method assigns local variables from method parameters for definition \n def m(a=1, **) a end"
fails:"A method assigns local variables from method parameters for definition \n def m(a=1, **k) [a, k] end"
fails:"A method assigns local variables from method parameters for definition \n def m(*, a:) a end"
fails:"A method assigns local variables from method parameters for definition \n def m(*a, b:) [a, b] end"
fails:"A method assigns local variables from method parameters for definition \n def m(*, a: 1) a end"
fails:"A method assigns local variables from method parameters for definition \n def m(*a, b: 1) [a, b] end"
fails:"A method assigns local variables from method parameters for definition \n def m(*, **) end"
fails:"A method assigns local variables from method parameters for definition \n def m(*a, **) a end"
Expand All @@ -60,11 +57,7 @@ fails:"A method assigns local variables from method parameters for definition \n
fails:"A method assigns local variables from method parameters for definition \n def m(a:, &b) [a, b] end"
fails:"A method assigns local variables from method parameters for definition \n def m(a: 1, b:) [a, b] end"
fails:"A method assigns local variables from method parameters for definition \n def m(a: def m(a: 1) a end, b:)\n [a, b]\n end"
fails:"A method assigns local variables from method parameters for definition \n def m(a: 1, b: 2) [a, b] end"
fails:"A method assigns local variables from method parameters for definition \n def m(a: 1, **) a end"
fails:"A method assigns local variables from method parameters for definition \n def m(a: 1, **k) [a, k] end"
fails:"A method assigns local variables from method parameters for definition \n def m(a: 1, &b) [a, b] end"
fails:"A method assigns local variables from method parameters for definition \n def m(**, &b) b end"
fails:"A method assigns local variables from method parameters for definition \n def m(**k, &b) [k, b] end"
fails:"A method assigns local variables from method parameters for definition \n def m(a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l)\n [a, b, c, d, e, f, g, h, k, l]\n end"
fails:"A method assigns local variables from method parameters for definition \n def m a, b=1, *c, d, e:, f: 2, g:, **k, &l\n [a, b, c, d, e, f, g, k, l]\n end"
4 changes: 2 additions & 2 deletions spec/truffle/tags/language/versions/def_2.0_tags.txt
Expand Up @@ -2,5 +2,5 @@ fails:An instance method with keyword arguments treats a sole hash argument corr
fails:An instance method with keyword arguments correctly distinguishes between optional and keyword arguments
fails:An instance method with keyword arguments correctly distinguishes between rest and keyword arguments
fails:An instance method with keyword arguments should allow keyword rest arguments
fails:An instance method with keyword arguments when there is a single keyword argument evaluates to the default when a value isn't provided
fails:An instance method with keyword arguments when there is a single keyword argument evaluates to the provided value
fails:An instance method with keyword arguments when there is a single keyword argument raises an argument error when a non-keyword argument is provided
fails:An instance method with keyword arguments when there is a single keyword argument raises an argument error when an unknown keyword argument is provided

0 comments on commit a560b2e

Please sign in to comment.