Hi!

On Feb 6, 2010, at 10:32 PM, Terence Parr wrote:

> Hi. Can someone enlighten me on the relationship between interfaces and 
> generics? As it pertains to ANTLR, we have a Tree interface, but that is just 
> to define some common functionality. people can define their own adapters and 
> I assume only that a node is an object. Then, to get some work done I created 
> CommonTree which is a subclass of BaseTree it has a token payload. BaseTree 
> implements Tree.
> 
> Part of me thinks that I only need a single CommonTree class now but with a 
> generic parameter T that specifies the payload type. Unfortunately that 
> doesn't solve some of my use cases where I hate having to use typecasts. If I 
> create my own adapter that specifies how to create types, it might return 
> FooNode objects.  Every time I ask for CommonTree<Token>.getChild(3), I have 
> to cast it to FooNode.   Does that mean we need another parameter on the 
> tree?  CommonTree<PayloadType, NodeType>? seems weird.

Yes, it does seem weird. I wonder why CommonTree would need to be parameterized 
with the NodeType, isn't CommonTree already the concrete type?
To me it sounds like the TreeAdaptor would be TreeAdaptor<NodeType, 
PayloadType>, and then:
CommonTreeAdaptor implements TreeAdaptor<CommonTree, CommonToken> { … }

All the methods/fields in CommonTreeAdaptor then would have concrete types 
(likewise for any custom tree adaptor a user declares).

That, however, poses another problem in the "generic" recognizer classes (I 
really shouldn't use the word generic in this context, but can't of another 
word right now).

> Do we need the interface anymore given that we have generics? what about 
> backward compatibility? maybe I should leave the existing hierarchy alone and 
> create a new generic treenode that accepts the appropriate generic types?

I would probably not shoot for backwards compatibility with this. Maybe it's 
possible to support, but I wouldn't bet on it, and frankly, there are likely 
too many changes anyway to be compatible at this point. Also, the incompatible 
parts should be constrained to a small part of any users' code, so it should be 
ok.
As to whether we need the interface, I'm not sure. Part of me thinks that 
having the interface would at least make it more obvious what's going on, but 
it's probably not strictly necessary.

> For some reason it's not obvious to me how to use generics in this case. can 
> someone with more experience, gives me their thoughts?

I'm not entirely sure which use case is giving you a headache, could you paste 
(or point to) an example?

> Ter
> PS    seems like we want to generate a generic parser/lexer/treeparser 
> blackbox with two primary parameters Token and Tree generic types.

Yes, the main problem with generics in this context is the following ugly wart:
In the abstract parser superclass you will somehow have to refer to a 
TreeAdaptor<?, ?> (in which the ?, ? are <CommonTree, CommonToken > for 
CommonTreeAdaptor).
Since that's not feasible, because you would have to use typecasts all over 
again (you can't do much with wildcard types at all…) please see the following 
mock definitions:

CommonTree: always uses CommonToken

public class CommonTree extends BaseTree {
        CommonToken token;
        ...
}

CommonTreeAdaptor defined in terms of the generic TreeAdaptor interface.
Like the definition before, just that each reference to Object is now 
CommonTree and Token is CommonToken (although that's not a requirement, could 
be Token instead).

public class CommonTreeAdaptor implements TreeAdaptor<CommonTree, CommonToken> {
        @Override public CommonTree create(CommonToken payload) { ... }
}

TreeAdaptor is as such:

public interface TreeAdaptor<TreeType, PayloadType> {
        public TreeType create(PayloadType payload); 
        ...
}

The "generic parser" (in the real thing derived from BaseRecognizer, omitted 
for brevity here):

public abstract class Parser<NodeType, PayloadType> {

        private TreeAdaptor<NodeType, PayloadType> adaptor;

        public void setAdaptor(TreeAdaptor<NodeType, PayloadType> adaptor) {
                this.adaptor = adaptor;
        }

        public TreeAdaptor<NodeType, PayloadType> getAdaptor() {
                return adaptor;
        }
        
        /* Note: actual types! */
        public void example() {
                NodeType create = adaptor.create(-1, "EOF");
                NodeType dup = adaptor.dupNode(create);
        }
}

Ok, those are the things that work today. Now let's create a custom node type 
and an adaptor for it:

// lame, it's actually empty and just an Object subclass
public class CustomNodeType {

}

public class CustomNodeAdaptor implements TreeAdaptor<CustomNodeType, 
CommonToken> {
        @Override public CustomNodeType create(CommonToken payload) { ... }
}

That's the runtime part of it. Now let's look at how we generate recognizers 
that use actual types and no casts whatsoever. They derive from abstract 
Parser<NodeType, PayloadType>:

First the "common case":
public class CommonParser extends Parser<CommonTree, CommonToken> {

        public CommonTree a() {
                CommonTree a_result;
                CommonToken someToken = input.match(1); // also generic 
tokenstream, would return a CommonToken
                a_result = getAdaptor().create(someToken);
                return a_result;
        }
}

The "custom node" version:
public class TestParser extends Parser<CustomNodeType, CommonToken> {

        public CustomNodeType a() {
                CustomNodeType a_result;
                CommonToken someToken = input.match(1); // also generic 
tokenstream, would return a CommonToken
                a_result = getAdaptor().create(someToken);
                return a_result;
        }
}

Note how the ASTLabelType and TokenLabelType simply become the type parameters 
of the extends clause (and of course everywhere as variable types, method 
return types etc).
And, most importantly, type safe, no casts, easy to read.

Here's a mock Main.java:
public class Main {

        public static void main(String[] args) {
                TestParser parser = new TestParser();
                parser.setAdaptor(new CustomNodeAdaptor());
                CustomNodeType customNode = parser.a();
                
                CommonParser cp = new CommonParser();
                cp.setAdaptor(new CommonTreeAdaptor());
                CommonTree ct = cp.a();
        }
}

Again, no casts. Neat, if you ask me.
There's on problem with the two becomeRoot() methods in the TreeAdaptor 
interface.
One is
        public abstract TreeType becomeRoot(TreeType newRoot, TreeType oldRoot);
the other:
        public abstract TreeType becomeRoot(PayloadType newRoot, TreeType 
oldRoot);

Because of type erasure they end up having the same signature, so the 
convenience one should be renamed, IMHO. Not a big deal.

Let me know if the above wasn't clear enough, it got a bit out of hand 
length-wise :)

BTW, it won't even compile if you try to do:
        parser.setAdaptor(new CommonTreeAdaptor());
since it does not support the correct ASTLabelType. No more unpleasant runtime 
ClassCastExceptions here :)

cheers,
-k
_______________________________________________
antlr-dev mailing list
[email protected]
http://www.antlr.org/mailman/listinfo/antlr-dev

Reply via email to