From: Yehuda Katz Date: 2009-02-25T10:03:12+09:00 Subject: [ruby-core:22440] Re: YASNP (Yet Another Selector Namespace Proposal) --000e0cd241fc4de4a50463b3cfd7 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit Big +1 Charlie! One of the things that appealed to me about the original proposal was that it seemed likely to be implementable without massive changes to Ruby, while still supporting everything that is actually needed in selector namespaces without sacrificing performance. With this mock-up, Charlie is at least showing that it's not terrifically hard as a first approximation. :-D -- Yehuda 2009/2/24 Charles Oliver Nutter > 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 > > 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() > > -- Yehuda Katz Developer | Engine Yard (ph) 718.877.1325 --000e0cd241fc4de4a50463b3cfd7 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Big +1 Charlie!

One of the things that appealed to me about the orig= inal proposal was that it seemed likely to be implementable without massive= changes to Ruby, while still supporting everything that is actually needed= in selector namespaces without sacrificing performance. With this mock-up,= Charlie is at least showing that it's not terrifically hard as a first= approximation. :-D

-- Yehuda

2009/2/24 Charles Oliver Nu= tter <charle= s.nutter@sun.com>
Yehuda Katz wrote:
=A0 =A0* "use" is scoped

=A0 =A0* no special namespace keyword; any module can be a namespace
=A0 =A0* use Merb::String for String
=A0 =A0* use Merb::* (equivalent to Merb.constants.each {|k| use k for
=A0 =A0 =A0k.split("::", 2).last.constantize })

Here's a new mock-up of this behavior in JRuby (without making parser m= odifications that would certainly clean it up more):

{{
module Foo
=A0module Bar
=A0 =A0def to_s
=A0 =A0 =A0puts 'inside namespace: ' + to_s
=A0 =A0 =A0'blah'
=A0 =A0end
=A0end
end

using Foo::Bar =3D> 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 th= e file. So the "using" flips them on, and the end of the file fli= ps them off. Multiple are also possible:

{{
module Foo
=A0module Bar
=A0 =A0def to_s
=A0 =A0 =A0puts 'string namespace'
=A0 =A0end
=A0end
end
module Blah
=A0def to_s
=A0 =A0puts 'fixnum namespace'
=A0end
end

using Foo::Bar =3D> String
using Blah =3D> 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 w= ith 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

diff --git a/src/org/jruby/ast/CallNoArgNode.java b/src/org/jruby/ast/C= allNoArgNode.java
index 1b2cd40..081e1f0 100644
--- a/src/org/jruby/ast/CallNoArgNode.java
+++ b/src/org/jruby/ast/CallNoArgNode.java
@@ -32,8 +32,12 @@
=A0***** END LICENSE BLOCK *****/
=A0package org.jruby.ast;

+import java.util.List;
=A0import org.jruby.Ruby;
+import org.jruby.RubyArray;
=A0import org.jruby.RubyClass;
+import org.jruby.RubyHash;
+import org.jruby.RubyModule;
=A0import org.jruby.exceptions.JumpException;
=A0import org.jruby.internal.runtime.methods.DynamicMethod;
=A0import org.jruby.lexer.yacc.ISourcePosition;
@@ -46,19 +50,48 @@ import org.jruby.runtime.builtin.IRubyObject;
=A0* A method or operator call.
=A0*/
=A0public final class CallNoArgNode extends CallNode {
+ =A0 =A0List<Node> namespaces;
=A0 =A0 // For 'b.foo'
=A0 =A0 public CallNoArgNode(ISourcePosition position, Node receiverNode, = String name) {
=A0 =A0 =A0 =A0 super(position, receiverNode, name, null, null);
=A0 =A0 }
+
+ =A0 =A0// For 'b.foo()'. =A0Args are only significant in maintain= ing backwards compatible AST structure
+ =A0 =A0public CallNoArgNode(ISourcePosition position, Node receiverNode, = String name, List<Node> namespaces) {
+ =A0 =A0 =A0 =A0super(position, receiverNode, name, null, null);
+ =A0 =A0 =A0 =A0this.namespaces =3D namespaces;
+ =A0 =A0}

=A0 =A0 // For 'b.foo()'. =A0Args are only significant in maintain= ing backwards compatible AST structure
=A0 =A0 public CallNoArgNode(ISourcePosition position, Node receiverNode, = Node args, String name) {
=A0 =A0 =A0 =A0 super(position, receiverNode, name, args, null);
=A0 =A0 }
+
+ =A0 =A0// For 'b.foo()'. =A0Args are only significant in maintain= ing backwards compatible AST structure
+ =A0 =A0public CallNoArgNode(ISourcePosition position, Node receiverNode, = Node args, String name, List<Node> namespaces) {
+ =A0 =A0 =A0 =A0super(position, receiverNode, name, args, null);
+ =A0 =A0 =A0 =A0this.namespaces =3D namespaces;
+ =A0 =A0}

=A0 =A0 @Override
=A0 =A0 public IRubyObject interpret(Ruby runtime, ThreadContext context, = IRubyObject self, Block aBlock) {
- =A0 =A0 =A0 =A0return callAdapter.call(context, self, getReceiverNode().i= nterpret(runtime, context, self, aBlock));
+ =A0 =A0 =A0 =A0IRubyObject receiver =3D getReceiverNode().interpret(runti= me, context, self, aBlock);
+ =A0 =A0 =A0 =A0if (namespaces !=3D null) {
+ =A0 =A0 =A0 =A0 =A0 =A0DynamicMethod method =3D null;
+ =A0 =A0 =A0 =A0 =A0 =A0for (Node node : namespaces) {
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0IRubyObject namespace =3D ((RubyArray)node= .interpret(runtime, context, self, aBlock)).eltInternal(0);
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0RubyModule nsModule =3D ((RubyModule)((Rub= yHash)namespace).keys().eltInternal(0));
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0RubyClass targetClass =3D (RubyClass)((Rub= yHash)namespace).rb_values().eltInternal(0);
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if (targetClass =3D=3D receiver.getMetaCla= ss()) {
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0method =3D nsModule.searchMethod(c= allAdapter.methodName);
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if (method !=3D null && !m= ethod.isUndefined()) break;
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0}
+ =A0 =A0 =A0 =A0 =A0 =A0}
+ =A0 =A0 =A0 =A0 =A0 =A0if (method !=3D null && !method.isUndefine= d()) {
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0return method.call(context, receiver, self= .getMetaClass(), callAdapter.methodName);
+ =A0 =A0 =A0 =A0 =A0 =A0}
+ =A0 =A0 =A0 =A0}
+ =A0 =A0 =A0 =A0return callAdapter.call(context, self, receiver);
=A0 =A0 }

=A0 =A0 @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 @@
=A0***** END LICENSE BLOCK *****/
=A0package org.jruby.parser;

+import java.util.ArrayList;
+import java.util.List;
=A0import org.jruby.ast.AndNode;
=A0import org.jruby.ast.ArgsPreOneArgNode;
=A0import org.jruby.ast.ArgsPreTwoArgNode;
@@ -182,6 +184,8 @@ public class ParserSupport {
=A0 =A0 private ParserConfiguration configuration;
=A0 =A0 private RubyParserResult result;

+ =A0 =A0private List namespaces =3D new ArrayList();
+
=A0 =A0 public void reset() {
=A0 =A0 =A0 =A0 inSingleton =3D 0;
=A0 =A0 =A0 =A0 inDefinition =3D false;
@@ -192,6 +196,10 @@ public class ParserSupport {
=A0 =A0 =A0 =A0 =A0 =A0 throw new SyntaxException(PID.DUBY_EXTENSIONS_OFF,= position, "Duby extensions not configured");
=A0 =A0 =A0 =A0 }
=A0 =A0 }
+
+ =A0 =A0public List getNamespaces() {
+ =A0 =A0 =A0 =A0return namespaces;
+ =A0 =A0}

=A0 =A0 public StaticScope getCurrentScope() {
=A0 =A0 =A0 =A0 return currentScope;
@@ -922,8 +930,12 @@ public class ParserSupport {
=A0 =A0 =A0 =A0 if (receiver =3D=3D null) receiver =3D NilImplicitNode.NIL= ;

=A0 =A0 =A0 =A0 if (iter !=3D null) return new CallNoArgBlockNode(position= , receiver, (String) name.getValue(), iter);
-
- =A0 =A0 =A0 =A0return new CallNoArgNode(position, receiver, (String) name= .getValue());
+
+ =A0 =A0 =A0 =A0if (namespaces.size() > 0) {
+ =A0 =A0 =A0 =A0 =A0 =A0return new CallNoArgNode(position, receiver, (Stri= ng) name.getValue(), namespaces);
+ =A0 =A0 =A0 =A0} else {
+ =A0 =A0 =A0 =A0 =A0 =A0return new CallNoArgNode(position, receiver, (Stri= ng) name.getValue());
+ =A0 =A0 =A0 =A0}
=A0 =A0 }

=A0 =A0 private Node new_call_complexargs(Node receiver, Token name, Node = args, Node iter) {
@@ -972,8 +984,12 @@ public class ParserSupport {
=A0 =A0 =A0 =A0 switch (args.size()) {
=A0 =A0 =A0 =A0 =A0 =A0 case 0:
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (iter !=3D null) return new CallNoArgBl= ockNode(union(receiver, args), receiver, (String) name.getValue(), args, (I= terNode) iter);
-
- =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0return new CallNoArgNode(union(receiver, a= rgs), receiver, args, (String) name.getValue());
+
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0if (namespaces.size() > 0) {
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0return new CallNoArgNode(union(rec= eiver, args), receiver, args, (String) name.getValue(), namespaces);
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0} else {
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0return new CallNoArgNode(union(rec= eiver, args), receiver, args, (String) name.getValue());
+ =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0}
=A0 =A0 =A0 =A0 =A0 =A0 case 1:
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (iter !=3D null) return new CallOneArgB= lockNode(union(receiver, args), receiver, (String) name.getValue(), args, (= IterNode) iter);

@@ -1014,6 +1030,14 @@ public class ParserSupport {

=A0 =A0 private Node new_fcall_simpleargs(Token operation, ArrayNode args,= Node iter) {
=A0 =A0 =A0 =A0 String name =3D (String) operation.getValue();
+
+ =A0 =A0 =A0 =A0// it if's a "using" call add a namespace an= d return a nil node
+ =A0 =A0 =A0 =A0if (name.equals("using")) {
+ =A0 =A0 =A0 =A0 =A0 =A0namespaces.add(args);
+ =A0 =A0 =A0 =A0 =A0 =A0return new NilNode(union(operation, args));
+ =A0 =A0 =A0 =A0}
+
+ =A0 =A0 =A0 =A0// otherwise, if we have a namespace, pass it into the FCa= ll

=A0 =A0 =A0 =A0 switch (args.size()) {
=A0 =A0 =A0 =A0 =A0 =A0 case 0: =A0// foo()




--
Yehuda Katz
Deve= loper | Engine Yard
(ph) 718.877.1325
--000e0cd241fc4de4a50463b3cfd7--