Commits

Tim Vernum  committed 1027a1e

Start support for member references

  • Participants
  • Parent commits d9334dc

Comments (0)

Files changed (14)

File convert/build.xml

     <delete dir="${output}" />
   </target>
 
-<!--
-  <target name="fix">
-    <replace dir="source/java/parser/" 
-      token="public Object jjtAccept(final JavaParserVisitor visitor, final Object data)"
-      value="public &lt;R,T&gt; R jjtAccept(final JavaParserVisitor&lt;R,T&gt; visitor, final T data)" />
-  </target>
--->
-
-
 </project>
 

File convert/source/java/main/org/adjective/syntactic/convert/j8to7/AbstractASTConverter.java

+/* ------------------------------------------------------------------------
+ * Copyright 2013 Tim Vernum
+ * ------------------------------------------------------------------------
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ------------------------------------------------------------------------
+ */
+package org.adjective.syntactic.convert.j8to7;
+
+import org.adjective.syntactic.convert.j8to7.type.MethodInfo;
+import org.adjective.syntactic.convert.j8to7.type.TypeInfo;
+import org.adjective.syntactic.convert.j8to7.util.ClassFinder;
+import org.adjective.syntactic.convert.j8to7.util.Importer;
+import org.adjective.syntactic.parser.ast.ASTAllocationExpression;
+import org.adjective.syntactic.parser.ast.ASTArguments;
+import org.adjective.syntactic.parser.ast.ASTClassOrInterfaceBody;
+import org.adjective.syntactic.parser.ast.ASTClassOrInterfaceBodyElement;
+import org.adjective.syntactic.parser.ast.ASTFormalParameters;
+import org.adjective.syntactic.parser.ast.ASTMember;
+import org.adjective.syntactic.parser.ast.ASTMethodDeclaration;
+import org.adjective.syntactic.parser.ast.ASTModifiers;
+import org.adjective.syntactic.parser.name.ParameterizedName;
+import org.adjective.syntactic.parser.name.SimpleParameterizedName;
+import org.adjective.syntactic.parser.name.SimpleTypeParameter;
+import org.adjective.syntactic.parser.name.TypeParameter;
+import org.adjective.syntactic.parser.node.ExpressionNode;
+import org.adjective.syntactic.parser.type.JavaType;
+import org.adjective.syntactic.parser.type.SimpleJavaType;
+import org.adjective.syntactic.parser.util.ModifierSet;
+import org.adjective.syntactic.util.ArrayUtil;
+import org.adjective.syntactic.util.JavaTypeUtil;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+public class AbstractASTConverter
+{
+    private final Importer _importer;
+    private final ClassFinder _classFinder;
+
+    public AbstractASTConverter(final Importer importer, final ClassFinder classFinder)
+    {
+        _importer = importer;
+        _classFinder = classFinder;
+    }
+
+    protected TypeInfo getTypeInfo(final JavaType forType)
+    {
+        final String name = _importer.getQualifiedName(forType);
+        final TypeInfo typeInfo = _classFinder.getClass(name, getTypeParameters(forType));
+        if (typeInfo == null)
+        {
+            throw new ConversionException("Cannot find '" + name + "'");
+        }
+        return typeInfo;
+    }
+
+    private TypeParameter[] getTypeParameters(final JavaType forType)
+    {
+        return ArrayUtil.last(forType.getParameterizedTypeName()).getParameters();
+    }
+
+    protected ExpressionNode makeAnonymousInnerClass(final JavaType interfaceType, final MethodInfo functionalMethod,
+                                                     final ASTMethodDeclaration methodImpl)
+    {
+        final ModifierSet set = new ModifierSet(ModifierSet.Modifier.PUBLIC);
+        final ASTModifiers modifiers = new ASTModifiers(set);
+        final ASTMember member = new ASTMember(modifiers, methodImpl);
+        final JavaType astType = substituteType(methodImpl.getParameters(), functionalMethod, interfaceType);
+        ASTClassOrInterfaceBody body = new ASTClassOrInterfaceBody(new ASTClassOrInterfaceBodyElement(member));
+        return new ASTAllocationExpression(JavaTypeUtil.withoutWildcard(astType), new ASTArguments(), body);
+    }
+
+    private JavaType substituteType(final ASTFormalParameters parameters, final MethodInfo method, final JavaType type)
+    {
+        final Integer[] typeParameters = method.getClassTypeParameters();
+        if (ArrayUtil.isNull(typeParameters))
+        {
+            return type;
+        }
+        final ParameterizedName lastName = ArrayUtil.last(type.getParameterizedTypeName());
+        TypeParameter[] classParameters = Arrays.copyOf(lastName.getParameters(), lastName.getParameters().length);
+        for (int i = 0; i < typeParameters.length; i++)
+        {
+            final Integer classIndex = typeParameters[i];
+            if (classIndex != null)
+            {
+                classParameters[classIndex] = new SimpleTypeParameter(TypeParameter.Kind.EXACT,
+                                                                      parameters.getParameterTypes()[i]);
+            }
+        }
+
+        ParameterizedName[] name = new ParameterizedName[type.getParameterizedTypeName().length];
+        System.arraycopy(type.getParameterizedTypeName(), 0, name, 0, name.length - 1);
+        name[name.length - 1] = new SimpleParameterizedName(lastName.getName(), classParameters);
+        return new SimpleJavaType(name, type.getArrayDepth());
+    }
+
+    protected MethodInfo getFunctionalMethod(final JavaType forType)
+    {
+        final TypeInfo typeInfo = getTypeInfo(forType);
+        return getFunctionalMethod(typeInfo);
+    }
+
+    private MethodInfo getFunctionalMethod(final TypeInfo typeInfo)
+    {
+        final Collection<MethodInfo> methods = typeInfo.getAbstractMethods();
+        if (methods.size() != 1)
+        {
+            throw new ConversionException("Class '" + typeInfo.getName()
+                                                  + "' should have exactly 1 method but has " + methods.size());
+        }
+
+        return methods.iterator().next();
+    }
+}

File convert/source/java/main/org/adjective/syntactic/convert/j8to7/Convert8To7Visitor.java

 import org.adjective.syntactic.parser.ast.*;
 import org.adjective.syntactic.parser.node.BaseNode;
 import org.adjective.syntactic.parser.node.ExpressionNode;
+import org.adjective.syntactic.parser.node.ExpressionSuffix;
+import org.adjective.syntactic.parser.node.Nodes;
 import org.adjective.syntactic.parser.node.StatementNode;
 import org.adjective.syntactic.parser.node.VariableDeclarationNode;
 import org.adjective.syntactic.parser.type.JavaType;
     private final ClassFinder _classFinder;
     private final Importer _importer;
     private final LambdaConverter _lambdaConverter;
+    private final MemberReferenceConverter _referenceConverter;
     private final List<Pair<VariableDeclarationNode, String>> _toMarkFinal;
 
     public Convert8To7Visitor()
         _classFinder = new ClassFinder();
         _importer = new Importer(_classFinder);
         _lambdaConverter = new LambdaConverter(_importer, _classFinder);
+        _referenceConverter = new MemberReferenceConverter(_importer, _classFinder);
         _toMarkFinal = new ArrayList<Pair<VariableDeclarationNode, String>>();
     }
 
                 continue;
             }
 
-            expr = expr.as(ASTPrimaryExpression.class).getPrefix();
-
-            if (!expr.is(ASTLambdaExpression.class))
+            final ASTPrimaryExpression primary = expr.as(ASTPrimaryExpression.class);
+            ExpressionNode prefix = primary.getPrefix();
+            if (prefix.is(ASTLambdaExpression.class))
             {
+                final ASTLambdaExpression lambda = prefix.as(ASTLambdaExpression.class);
+                final ExpressionNode convert = _lambdaConverter.convertLambda(lambda, getType(node, var));
+                fixBody(lambda.getBody());
+                var.replaceInitializer(new ASTExpression(convert));
                 continue;
             }
 
-            final ASTLambdaExpression lambda = expr.as(ASTLambdaExpression.class);
-            final ExpressionNode convert = _lambdaConverter.convertLambda(lambda,
-                                                                          getType(node, var));
-            fixBody(lambda.getBody());
-            var.replaceInitializer(new ASTExpression(convert));
+            final Nodes<ExpressionSuffix> suffixes = primary.getSuffixes();
+            if (suffixes.has(ASTMemberReference.class))
+            {
+                final List<ExpressionSuffix> leading = new ArrayList<ExpressionSuffix>();
+                for (ExpressionSuffix suffix : suffixes)
+                {
+                    if (suffix.is(ASTMemberReference.class))
+                    {
+                        final ASTMemberReference reference = suffix.as(ASTMemberReference.class);
+                        final ExpressionNode target = makePrimaryExpression(prefix, leading);
+                        final ExpressionNode convert = _referenceConverter.convertReference(target,
+                                                                                            reference,
+                                                                                            getType(node, var));
+                        prefix = new ASTBracketExpression(convert);
+                        leading.clear();
+                    }
+                    else
+                    {
+                        leading.add(suffix);
+                    }
+                }
+                var.replaceInitializer(makePrimaryExpression(prefix, leading));
+            }
         }
 
         return super.visit(node, data);
     }
 
+    private ExpressionNode makePrimaryExpression(final ExpressionNode prefix, final List<ExpressionSuffix> suffix)
+    {
+        return suffix.isEmpty() ? prefix : new ASTPrimaryExpression(prefix, suffix);
+    }
+
     private void fixBody(final ASTLambdaBody body)
     {
         body.jjtAccept(new DefaultVisitor<Object, Object>()

File convert/source/java/main/org/adjective/syntactic/convert/j8to7/LambdaConverter.java

 package org.adjective.syntactic.convert.j8to7;
 
 import org.adjective.syntactic.convert.j8to7.type.MethodInfo;
-import org.adjective.syntactic.convert.j8to7.type.TypeInfo;
 import org.adjective.syntactic.convert.j8to7.util.ClassFinder;
 import org.adjective.syntactic.convert.j8to7.util.Importer;
 import org.adjective.syntactic.parser.ast.*;
-import org.adjective.syntactic.parser.name.ParameterizedName;
-import org.adjective.syntactic.parser.name.SimpleParameterizedName;
-import org.adjective.syntactic.parser.name.SimpleTypeParameter;
-import org.adjective.syntactic.parser.name.TypeParameter;
 import org.adjective.syntactic.parser.node.ExpressionNode;
 import org.adjective.syntactic.parser.type.JavaType;
-import org.adjective.syntactic.parser.type.SimpleJavaType;
-import org.adjective.syntactic.parser.util.ModifierSet;
-import org.adjective.syntactic.util.ArrayUtil;
-import org.adjective.syntactic.util.JavaTypeUtil;
 
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Iterator;
 
-public class LambdaConverter
+public class LambdaConverter extends AbstractASTConverter
 {
-    private final Importer _importer;
-    private final ClassFinder _classFinder;
-
     public LambdaConverter(final Importer importer, final ClassFinder classFinder)
     {
-        _classFinder = classFinder;
-        _importer = importer;
+        super(importer, classFinder);
     }
 
     public ExpressionNode convertLambda(final ASTLambdaExpression lambda, final JavaType forType)
     {
-        final ModifierSet set = new ModifierSet(ModifierSet.Modifier.PUBLIC);
-        final ASTModifiers modifiers = new ASTModifiers(set);
-        final TypeInfo typeInfo = getTypeInfo(forType);
-        final MethodInfo methodInfo = getFunctionalMethod(typeInfo);
+        final MethodInfo methodInfo = getFunctionalMethod(forType);
         final ASTMethodDeclaration astMethod = makeMethodDeclaration(methodInfo,
                                                                      lambda.getParameters(),
                                                                      lambda.getBody());
-        final ASTMember member = new ASTMember(modifiers, astMethod);
-        ASTClassOrInterfaceBody body = new ASTClassOrInterfaceBody(new ASTClassOrInterfaceBodyElement(member));
-        final JavaType astType = substituteType(astMethod.getParameters(), methodInfo, forType);
-        return new ASTAllocationExpression(JavaTypeUtil.withoutWildcard(astType), new ASTArguments(), body);
-    }
-
-    private TypeInfo getTypeInfo(final JavaType forType)
-    {
-        final String name = _importer.getQualifiedName(forType);
-        final TypeInfo typeInfo = _classFinder.getClass(name, getTypeParameters(forType));
-        if (typeInfo == null)
-        {
-            throw new ConversionException("Cannot find '" + name + "'");
-        }
-        return typeInfo;
-    }
-
-    private TypeParameter[] getTypeParameters(final JavaType forType)
-    {
-        return ArrayUtil.last(forType.getParameterizedTypeName()).getParameters();
-    }
-
-    private MethodInfo getFunctionalMethod(final TypeInfo typeInfo)
-    {
-        final Collection<MethodInfo> methods = typeInfo.getAbstractMethods();
-        if (methods.size() != 1)
-        {
-            throw new ConversionException("Class '" + typeInfo.getName()
-                                                  + "' should have exactly 1 method but has " + methods.size());
-        }
-
-        return methods.iterator().next();
+        return makeAnonymousInnerClass(forType, methodInfo, astMethod);
     }
 
     private ASTMethodDeclaration makeMethodDeclaration(final MethodInfo method, final ASTLambdaParameters parameters, final ASTLambdaBody lambdaBody)
                                         body);
     }
 
-    private JavaType substituteType(final ASTFormalParameters parameters, final MethodInfo method, final JavaType type)
-    {
-        final Integer[] typeParameters = method.getClassTypeParameters();
-        if (ArrayUtil.isNull(typeParameters))
-        {
-            return type;
-        }
-        final ParameterizedName lastName = ArrayUtil.last(type.getParameterizedTypeName());
-        TypeParameter[] classParameters = Arrays.copyOf(lastName.getParameters(), lastName.getParameters().length);
-        for (int i = 0; i < typeParameters.length; i++)
-        {
-            final Integer classIndex = typeParameters[i];
-            if (classIndex != null)
-            {
-                classParameters[classIndex] = new SimpleTypeParameter(TypeParameter.Kind.EXACT,
-                                                                      parameters.getParameterTypes()[i]);
-            }
-        }
-
-        ParameterizedName[] name = new ParameterizedName[type.getParameterizedTypeName().length];
-        System.arraycopy(type.getParameterizedTypeName(), 0, name, 0, name.length - 1);
-        name[name.length - 1] = new SimpleParameterizedName(lastName.getName(), classParameters);
-        return new SimpleJavaType(name, type.getArrayDepth());
-    }
-
     private ASTFormalParameters makeFormalParameters(final MethodInfo method, final ASTLambdaParameters parameters)
     {
         final JavaType[] parameterTypes;

File convert/source/java/main/org/adjective/syntactic/convert/j8to7/MemberReferenceConverter.java

+/* ------------------------------------------------------------------------
+ * Copyright 2013 Tim Vernum
+ * ------------------------------------------------------------------------
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ------------------------------------------------------------------------
+ */
+package org.adjective.syntactic.convert.j8to7;
+
+import org.adjective.syntactic.convert.j8to7.type.MethodInfo;
+import org.adjective.syntactic.convert.j8to7.util.ClassFinder;
+import org.adjective.syntactic.convert.j8to7.util.Importer;
+import org.adjective.syntactic.parser.ast.ASTArraySuffixList;
+import org.adjective.syntactic.parser.ast.ASTBlock;
+import org.adjective.syntactic.parser.ast.ASTFormalParameters;
+import org.adjective.syntactic.parser.ast.ASTIdentifier;
+import org.adjective.syntactic.parser.ast.ASTMemberReference;
+import org.adjective.syntactic.parser.ast.ASTMethodBody;
+import org.adjective.syntactic.parser.ast.ASTMethodDeclaration;
+import org.adjective.syntactic.parser.ast.ASTNameList;
+import org.adjective.syntactic.parser.node.ExpressionNode;
+import org.adjective.syntactic.parser.type.JavaType;
+
+public class MemberReferenceConverter extends AbstractASTConverter {
+
+    public MemberReferenceConverter(final Importer importer, final ClassFinder classFinder)
+    {
+        super(importer, classFinder);
+    }
+
+    public ExpressionNode convertReference(final ExpressionNode target, final ASTMemberReference reference, final JavaType forType)
+    {
+        final MethodInfo methodInfo = getFunctionalMethod(forType);
+        final ASTMethodDeclaration astMethod = makeMethodDeclaration(methodInfo, target, reference);
+        return makeAnonymousInnerClass(forType, methodInfo, astMethod);
+    }
+
+    private ASTMethodDeclaration makeMethodDeclaration(final MethodInfo method, final ExpressionNode target, final ASTMemberReference reference)
+    {
+        // TODO
+        final ASTFormalParameters parameters = new ASTFormalParameters();
+        final ASTNameList exceptions = new ASTNameList();
+        final ASTMethodBody body = new ASTMethodBody(new ASTBlock());
+
+        return new ASTMethodDeclaration(method.getReturnType(),
+                                        new ASTIdentifier(method.getName()),
+                                        parameters,
+                                        new ASTArraySuffixList(),
+                                        exceptions,
+                                        body);
+    }
+}

File parser/source/java/parser/org/adjective/syntactic/parser/ast/ASTBracketExpression.java

         super(i);
     }
 
+    public ASTBracketExpression(final ASTExpression expression)
+    {
+        this(JavaParserTreeConstants.JJTBRACKETEXPRESSION);
+        setChildren(expression);
+    }
+
+    public ASTBracketExpression(final ExpressionNode expression)
+    {
+        this(new ASTExpression(expression));
+    }
+
     @Override
     public <R, T> R jjtAccept(final JavaParserVisitor<R, T> visitor, final T data)
     {

File parser/source/java/parser/org/adjective/syntactic/parser/ast/ASTNameList.java

         super(id);
     }
 
-    public ASTNameList(final JavaType[] exceptionType)
+    public ASTNameList(final JavaType... exceptionType)
     {
         this(JavaParserTreeConstants.JJTNAMELIST);
         ASTQualifiedIdentifier[] identifiers = new ASTQualifiedIdentifier[exceptionType.length];

File parser/source/java/parser/org/adjective/syntactic/parser/ast/ASTPrimaryExpression.java

 import org.adjective.syntactic.parser.node.BaseNode;
 import org.adjective.syntactic.parser.node.ExpressionNode;
 import org.adjective.syntactic.parser.node.ExpressionSuffix;
+import org.adjective.syntactic.parser.node.Nodes;
 
 public class ASTPrimaryExpression extends BaseNode implements ExpressionNode
 {
         super(id);
     }
 
+    public ASTPrimaryExpression(ExpressionNode prefix, Iterable<ExpressionSuffix> suffixes)
+    {
+        this(JavaParserTreeConstants.JJTPRIMARYEXPRESSION);
+        appendChild(prefix);
+        appendChildren(suffixes);
+    }
+
     public <R, T> R jjtAccept(JavaParserVisitor<R, T> visitor, T data)
     {
         return visitor.visit(this, data);
         return getFirstChild().as(ExpressionNode.class);
     }
 
-    public Iterable<ExpressionSuffix> getSuffixes()
+    public Nodes<ExpressionSuffix> getSuffixes()
     {
         return super.children(ExpressionSuffix.class, 1, jjtGetNumChildren());
     }

File parser/source/java/parser/org/adjective/syntactic/parser/node/AbstractNodeList.java

+/* ------------------------------------------------------------------------
+ * Copyright 2013 Tim Vernum
+ * ------------------------------------------------------------------------
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ------------------------------------------------------------------------
+ */
+package org.adjective.syntactic.parser.node;
+
+import org.adjective.syntactic.parser.ast.Node;
+
+import java.util.Iterator;
+
+abstract class AbstractNodeList<T extends Node> implements Nodes<T>
+{
+    @Override
+    public abstract Iterator<T> iterator();
+
+    @Override
+    public int size()
+    {
+        int count = 0;
+        for (T ignored : this)
+        {
+            count++;
+        }
+        return count;
+    }
+
+    @Override
+    public boolean has(final Class<? extends Node> type)
+    {
+        return find(type) != null;
+    }
+
+    @Override
+    public <N extends Node> N find(final Class<? extends N> type)
+    {
+        for (T element : this)
+        {
+            if (type.isInstance(element))
+            {
+                return type.cast(element);
+            }
+        }
+        return null;
+    }
+}

File parser/source/java/parser/org/adjective/syntactic/parser/node/BaseNode.java

 
 package org.adjective.syntactic.parser.node;
 
-import org.adjective.syntactic.parser.ast.*;
+import org.adjective.syntactic.parser.ast.ASTArraySuffixList;
+import org.adjective.syntactic.parser.ast.ASTClassOrInterfaceType;
+import org.adjective.syntactic.parser.ast.ASTIdentifier;
+import org.adjective.syntactic.parser.ast.ASTPrimitiveType;
+import org.adjective.syntactic.parser.ast.ASTReferenceType;
+import org.adjective.syntactic.parser.ast.ASTVoidType;
+import org.adjective.syntactic.parser.ast.Node;
+import org.adjective.syntactic.parser.ast.SimpleNode;
 import org.adjective.syntactic.parser.jj.Token;
 import org.adjective.syntactic.parser.type.ArrayType;
 import org.adjective.syntactic.parser.type.JavaType;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
 
 /**
  * @author <a href="http://blog.adjective.org/">Tim Vernum</a>
         }
     }
 
-    protected Iterable<? extends BaseNode> children()
+    protected Nodes<? extends BaseNode> children()
     {
         return allChildren(BaseNode.class);
     }
 
-    protected <T extends Node> Iterable<T> allChildren(final Class<T> type)
+    protected <T extends Node> Nodes<T> allChildren(final Class<T> type)
     {
-        return new Iterable<T>()
-        {
-            @Override
-            public Iterator<T> iterator()
-            {
-                return new NodeIterator<T>(type, false);
-            }
-
-            @Override
-            public String toString()
-            {
-                return "allChildren(" + type + ") {" + jjtGetNumChildren() + "}";
-            }
-        };
+        return new TypeCheckNodeList<T>(this, type, false);
     }
 
-    protected <T extends Node> Iterable<T> findChildren(final Class<T> type)
+    protected <T extends Node> Nodes<T> findChildren(final Class<T> type)
     {
-        return new Iterable<T>()
-        {
-            @Override
-            public Iterator<T> iterator()
-            {
-                return new NodeIterator<T>(type, true);
-            }
-        };
+        return new TypeCheckNodeList<T>(this, type, true);
     }
 
     protected int countChildren(Class<? extends Node> nodeType)
         return count;
     }
 
-    public <T extends Node> Iterable<T> children(final Class<T> type, final int start)
+    public <T extends Node> Nodes<T> children(final Class<T> type, final int start)
     {
         return this.children(type, start, jjtGetNumChildren());
     }
 
-    public <T extends Node> Iterable<T> children(final Class<T> type, final int start, final int end)
+    public <T extends Node> Nodes<T> children(final Class<T> type, final int start, final int end)
     {
-        return new Iterable<T>()
-        {
-            @Override
-            public Iterator<T> iterator()
-            {
-                return new NodeIterator<T>(type, start, end, false);
-            }
-        };
+        return new TypeCheckNodeList<T>(this, type, start, end, false);
     }
 
     public boolean isEmpty()
         }
     }
 
+    protected void appendChildren(final Iterable<? extends Node> nodes)
+    {
+        for (Node node : nodes)
+        {
+            appendChild(node);
+        }
+    }
+
     @Override
     public boolean is(Class<? extends Node> nodeType)
     {
         return builder;
     }
 
-    private class NodeIterator<T extends Node> implements Iterator<T>
-    {
-        private final Class<T> _type;
-        private final boolean _skipNonMatching;
-        private final int _stop;
-        private int _index;
-
-        public NodeIterator(Class<T> type, boolean skipNonMatching)
-        {
-            this(type, 0, jjtGetNumChildren(), skipNonMatching);
-        }
-
-        public NodeIterator(Class<T> type, int start, final int end, boolean skipNonMatching)
-        {
-            _type = type;
-            _skipNonMatching = skipNonMatching;
-            _index = start - 1;
-            _stop = Math.min(end, jjtGetNumChildren());
-            advance();
-        }
-
-        private void advance()
-        {
-            for (_index++; hasNext(); _index++)
-            {
-                Node node = jjtGetChild(_index);
-                if (_type.isInstance(node) || !_skipNonMatching)
-                {
-                    return;
-                }
-            }
-        }
-
-        @Override
-        public boolean hasNext()
-        {
-            return _index < _stop;
-        }
-
-        @Override
-        public T next()
-        {
-            if (!hasNext())
-            {
-                throw new NoSuchElementException();
-            }
-            Node node = jjtGetChild(_index);
-            if (_type.isInstance(node))
-            {
-                advance();
-                return _type.cast(node);
-            }
-            else
-            {
-                throw new IllegalStateException("Node " + node + " is not of type " + _type.getSimpleName());
-            }
-        }
-
-        @Override
-        public void remove()
-        {
-            throw new UnsupportedOperationException("remove() - not implemented");
-        }
-    }
-
     private class TokenIterator implements Iterator<Token>
     {
-
         private Token _next;
         private final Token _last;
 

File parser/source/java/parser/org/adjective/syntactic/parser/node/ExpressionSuffix.java

 
 import org.adjective.syntactic.parser.ast.Node;
 
-public interface ExpressionSuffix extends Node {
+public interface ExpressionSuffix extends Node, NodeType {
 }

File parser/source/java/parser/org/adjective/syntactic/parser/node/Nodes.java

+/* ------------------------------------------------------------------------
+ * Copyright 2013 Tim Vernum
+ * ------------------------------------------------------------------------
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ------------------------------------------------------------------------
+ */
+package org.adjective.syntactic.parser.node;
+
+import org.adjective.syntactic.parser.ast.Node;
+
+public interface Nodes<T extends Node> extends Iterable<T> {
+    public int size();
+    public boolean has(Class<? extends Node> type);
+    public <N extends Node> N find(Class<? extends N> type);
+}

File parser/source/java/parser/org/adjective/syntactic/parser/node/TypeCheckNodeList.java

+/* ------------------------------------------------------------------------
+ * Copyright 2013 Tim Vernum
+ * ------------------------------------------------------------------------
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ------------------------------------------------------------------------
+ */
+package org.adjective.syntactic.parser.node;
+
+import org.adjective.syntactic.parser.ast.Node;
+
+import java.util.Iterator;
+
+class TypeCheckNodeList<T extends Node> extends AbstractNodeList<T>
+{
+    private final BaseNode _parent;
+    private final Class<T> _type;
+    private final int _start;
+    private final int _end;
+    private final boolean _skipNonMatching;
+
+    public TypeCheckNodeList(final BaseNode parent, final Class<T> type, final boolean skipNonMatching)
+    {
+        this(parent, type, 0, -1, skipNonMatching);
+    }
+
+    public TypeCheckNodeList(final BaseNode parent, final Class<T> type, final int start, final int end, final boolean skipNonMatching)
+    {
+        _parent = parent;
+        _type = type;
+        _start = start;
+        _end = end;
+        _skipNonMatching = skipNonMatching;
+    }
+
+    @Override
+    public Iterator<T> iterator()
+    {
+        return new TypeFilterNodeIterator<T>(_parent, _type, _start, getEnd(), _skipNonMatching);
+    }
+
+    private int getEnd()
+    {
+        if (_end == -1)
+        {
+            return _parent.jjtGetNumChildren();
+        }
+        else
+        {
+            return Math.min(_end, _parent.jjtGetNumChildren());
+        }
+    }
+
+    @Override
+    public int size()
+    {
+        if (_skipNonMatching)
+        {
+            return super.size();
+        }
+        else
+        {
+            // Shortcut : avoid iterating if we don't need to filter
+            return getEnd() - _start;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        if (_start == 0 && _end == -1)
+        {
+            if (_skipNonMatching)
+            {
+                return "(" + _parent + ").findChildren(" + _type + ")";
+            }
+            return "(" + _parent + ").allChildren(" + _type + ")";
+        }
+        return "(" + _parent + ").children(" + _type + "," + _start + "," + _end + "," + _skipNonMatching + ")";
+    }
+}

File parser/source/java/parser/org/adjective/syntactic/parser/node/TypeFilterNodeIterator.java

+/* ------------------------------------------------------------------------
+ * Copyright 2013 Tim Vernum
+ * ------------------------------------------------------------------------
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ------------------------------------------------------------------------
+ */
+package org.adjective.syntactic.parser.node;
+
+import org.adjective.syntactic.parser.ast.Node;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+class TypeFilterNodeIterator<T extends Node> implements Iterator<T>
+{
+    private final Class<T> _type;
+    private final boolean _skipNonMatching;
+    private final int _stop;
+    private int _index;
+    private BaseNode _parent;
+
+    public TypeFilterNodeIterator(final BaseNode parent, Class<T> type, boolean skipNonMatching)
+    {
+        this(parent, type, 0, parent.jjtGetNumChildren(), skipNonMatching);
+    }
+
+    public TypeFilterNodeIterator(final BaseNode parent, Class<T> type, int start, final int end, boolean skipNonMatching)
+    {
+        _parent = parent;
+        _type = type;
+        _skipNonMatching = skipNonMatching;
+        _index = start - 1;
+        _stop = Math.min(end, _parent.jjtGetNumChildren());
+        advance();
+    }
+
+    private void advance()
+    {
+        for (_index++; hasNext(); _index++)
+        {
+            Node node = _parent.jjtGetChild(_index);
+            if (_type.isInstance(node) || !_skipNonMatching)
+            {
+                return;
+            }
+        }
+    }
+
+    @Override
+    public boolean hasNext()
+    {
+        return _index < _stop;
+    }
+
+    @Override
+    public T next()
+    {
+        if (!hasNext())
+        {
+            throw new NoSuchElementException();
+        }
+        Node node = _parent.jjtGetChild(_index);
+        if (_type.isInstance(node))
+        {
+            advance();
+            return _type.cast(node);
+        }
+        else
+        {
+            throw new IllegalStateException("Node " + node + " is not of type " + _type.getSimpleName());
+        }
+    }
+
+    @Override
+    public void remove()
+    {
+        throw new UnsupportedOperationException("remove() - not implemented");
+    }
+}