From: Charles Oliver Nutter Date: 2009-02-25T09:12:03+09:00 Subject: [ruby-core:22438] Re: YASNP (Yet Another Selector Namespace Proposal) This is a multi-part message in MIME format. --Boundary_(ID_QJA7GZ7RyCtSVuKVBc4/4A) Content-type: text/plain; format=flowed; charset=UTF-8 Content-transfer-encoding: 7BIT Yehuda Katz wrote: > * "use" is scoped > * no special namespace keyword; any module can be a namespace > * use Merb::String for String > * use Merb::* (equivalent to Merb.constants.each {|k| use k for > k.split("::", 2).last.constantize }) Here's a new mock-up of this behavior in JRuby (without making parser modifications that would certainly clean it up more): {{ module Foo module Bar def to_s puts 'inside namespace: ' + to_s 'blah' end end end using Foo::Bar => String puts 'hello'.to_s puts 1.to_s #output: inside namespace: hello blah 1 }} The improvements here are the use of a hash to associate a namespace with a specific class, only applying the namespaces that map to the target object's class, and (behind the scenes) resetting namespaces at the end of the file. So the "using" flips them on, and the end of the file flips them off. Multiple are also possible: {{ module Foo module Bar def to_s puts 'string namespace' end end end module Blah def to_s puts 'fixnum namespace' end end using Foo::Bar => String using Blah => Fixnum 'hello'.to_s 1.to_s #output: string namespace fixnum namespace }} Again, this is only hacked for these specific call types, namely an fcall with no block (for "using" to capture namespaces) and a call with no args and no block (for the to_s calls). Patch against JRuby trunk is attached as an example. - Charlie --Boundary_(ID_QJA7GZ7RyCtSVuKVBc4/4A) Content-type: text/plain; name=more_selector_namespaces.patch; x-mac-creator=0; x-mac-type=0 Content-transfer-encoding: 7BIT Content-disposition: inline; filename=more_selector_namespaces.patch diff --git a/src/org/jruby/ast/CallNoArgNode.java b/src/org/jruby/ast/CallNoArgNode.java index 1b2cd40..081e1f0 100644 --- a/src/org/jruby/ast/CallNoArgNode.java +++ b/src/org/jruby/ast/CallNoArgNode.java @@ -32,8 +32,12 @@ ***** END LICENSE BLOCK *****/ package org.jruby.ast; +import java.util.List; import org.jruby.Ruby; +import org.jruby.RubyArray; import org.jruby.RubyClass; +import org.jruby.RubyHash; +import org.jruby.RubyModule; import org.jruby.exceptions.JumpException; import org.jruby.internal.runtime.methods.DynamicMethod; import org.jruby.lexer.yacc.ISourcePosition; @@ -46,19 +50,48 @@ import org.jruby.runtime.builtin.IRubyObject; * A method or operator call. */ public final class CallNoArgNode extends CallNode { + List namespaces; // For 'b.foo' public CallNoArgNode(ISourcePosition position, Node receiverNode, String name) { super(position, receiverNode, name, null, null); } + + // For 'b.foo()'. Args are only significant in maintaining backwards compatible AST structure + public CallNoArgNode(ISourcePosition position, Node receiverNode, String name, List namespaces) { + super(position, receiverNode, name, null, null); + this.namespaces = namespaces; + } // For 'b.foo()'. Args are only significant in maintaining backwards compatible AST structure public CallNoArgNode(ISourcePosition position, Node receiverNode, Node args, String name) { super(position, receiverNode, name, args, null); } + + // For 'b.foo()'. Args are only significant in maintaining backwards compatible AST structure + public CallNoArgNode(ISourcePosition position, Node receiverNode, Node args, String name, List namespaces) { + super(position, receiverNode, name, args, null); + this.namespaces = namespaces; + } @Override public IRubyObject interpret(Ruby runtime, ThreadContext context, IRubyObject self, Block aBlock) { - return callAdapter.call(context, self, getReceiverNode().interpret(runtime, context, self, aBlock)); + IRubyObject receiver = getReceiverNode().interpret(runtime, context, self, aBlock); + if (namespaces != null) { + DynamicMethod method = null; + for (Node node : namespaces) { + IRubyObject namespace = ((RubyArray)node.interpret(runtime, context, self, aBlock)).eltInternal(0); + RubyModule nsModule = ((RubyModule)((RubyHash)namespace).keys().eltInternal(0)); + RubyClass targetClass = (RubyClass)((RubyHash)namespace).rb_values().eltInternal(0); + if (targetClass == receiver.getMetaClass()) { + method = nsModule.searchMethod(callAdapter.methodName); + if (method != null && !method.isUndefined()) break; + } + } + if (method != null && !method.isUndefined()) { + return method.call(context, receiver, self.getMetaClass(), callAdapter.methodName); + } + } + return callAdapter.call(context, self, receiver); } @Override diff --git a/src/org/jruby/parser/ParserSupport.java b/src/org/jruby/parser/ParserSupport.java index f3d9b97..267b54b 100644 --- a/src/org/jruby/parser/ParserSupport.java +++ b/src/org/jruby/parser/ParserSupport.java @@ -35,6 +35,8 @@ ***** END LICENSE BLOCK *****/ package org.jruby.parser; +import java.util.ArrayList; +import java.util.List; import org.jruby.ast.AndNode; import org.jruby.ast.ArgsPreOneArgNode; import org.jruby.ast.ArgsPreTwoArgNode; @@ -182,6 +184,8 @@ public class ParserSupport { private ParserConfiguration configuration; private RubyParserResult result; + private List namespaces = new ArrayList(); + public void reset() { inSingleton = 0; inDefinition = false; @@ -192,6 +196,10 @@ public class ParserSupport { throw new SyntaxException(PID.DUBY_EXTENSIONS_OFF, position, "Duby extensions not configured"); } } + + public List getNamespaces() { + return namespaces; + } public StaticScope getCurrentScope() { return currentScope; @@ -922,8 +930,12 @@ public class ParserSupport { if (receiver == null) receiver = NilImplicitNode.NIL; if (iter != null) return new CallNoArgBlockNode(position, receiver, (String) name.getValue(), iter); - - return new CallNoArgNode(position, receiver, (String) name.getValue()); + + if (namespaces.size() > 0) { + return new CallNoArgNode(position, receiver, (String) name.getValue(), namespaces); + } else { + return new CallNoArgNode(position, receiver, (String) name.getValue()); + } } private Node new_call_complexargs(Node receiver, Token name, Node args, Node iter) { @@ -972,8 +984,12 @@ public class ParserSupport { switch (args.size()) { case 0: if (iter != null) return new CallNoArgBlockNode(union(receiver, args), receiver, (String) name.getValue(), args, (IterNode) iter); - - return new CallNoArgNode(union(receiver, args), receiver, args, (String) name.getValue()); + + if (namespaces.size() > 0) { + return new CallNoArgNode(union(receiver, args), receiver, args, (String) name.getValue(), namespaces); + } else { + return new CallNoArgNode(union(receiver, args), receiver, args, (String) name.getValue()); + } case 1: if (iter != null) return new CallOneArgBlockNode(union(receiver, args), receiver, (String) name.getValue(), args, (IterNode) iter); @@ -1014,6 +1030,14 @@ public class ParserSupport { private Node new_fcall_simpleargs(Token operation, ArrayNode args, Node iter) { String name = (String) operation.getValue(); + + // it if's a "using" call add a namespace and return a nil node + if (name.equals("using")) { + namespaces.add(args); + return new NilNode(union(operation, args)); + } + + // otherwise, if we have a namespace, pass it into the FCall switch (args.size()) { case 0: // foo() --Boundary_(ID_QJA7GZ7RyCtSVuKVBc4/4A)--