diff --git a/src/main/java/org/sablecc/sablecc/ConstructNFA.java b/src/main/java/org/sablecc/sablecc/ConstructNFA.java
index 0ba83abd258cd96e7aaadcd37b8151219d594d93..761ab6a6ab882e8dd20d0065c7a23a74f564411a 100644
--- a/src/main/java/org/sablecc/sablecc/ConstructNFA.java
+++ b/src/main/java/org/sablecc/sablecc/ConstructNFA.java
@@ -16,81 +16,65 @@ public class ConstructNFA extends DepthFirstAdapter
   private ResolveIds ids;
   private String stateName;
 
+  private Map<Node, Object> nodeValues; // values are of type CharSet or NFA
+  private Map<PTokenDef, NFA> nfasByToken;
+  private NFA fullNFA;
+
   ConstructNFA(ResolveIds ids, String stateName)
   {
     this.ids = ids;
     this.stateName = stateName;
-  }
-
-  @Override
-  public void outStart(Start node)
-  {
-    setOut(node, getOut(node.getPGrammar()));
 
-    // free memory
-    if(getOut(node.getPGrammar()) != null)
-      setOut(node.getPGrammar(), null);
+    this.nodeValues = new HashMap<>();
+    this.nfasByToken = new HashMap<>();
+    this.fullNFA = null;
   }
 
-  @Override
-  public void outAGrammar(AGrammar node)
-  {
-    setOut(node, getOut(node.getTokens()));
-
-    // free memory
-    if(getOut(node.getTokens()) != null)
-      setOut(node.getTokens(), null);
+  public NFA getNFA() {
+    return this.fullNFA;
   }
 
   @Override
   public void outAHelperDef(AHelperDef node)
   {
-    setOut(node, getOut(node.getRegExp()));
+    this.nodeValues.put(node, this.nodeValues.get(node.getRegExp()));
 
     // free memory
-    if(getOut(node.getRegExp()) != null)
-      setOut(node.getRegExp(), null);
+    this.nodeValues.remove(node.getRegExp());
   }
 
   @Override
   public void outATokens(ATokens node)
   {
     ATokenDef[] tokenDefs = (ATokenDef[]) node.getTokenDefs().toArray(new ATokenDef[0]);
-    NFA result = null;
 
     for(int i = tokenDefs.length - 1; i >= 0 ; i--)
     {
-      NFA nfa = (NFA) getOut(tokenDefs[i]);
+      NFA nfa = this.nfasByToken.get(tokenDefs[i]);
       if(nfa != null)
       {
-        if(result == null)
+        if(fullNFA == null)
         {
-          result = nfa;
+          fullNFA = nfa;
         }
         else
         {
-          result = nfa.merge(result);
+          fullNFA = nfa.merge(fullNFA);
         }
 
         // free memory
-        if(getOut(tokenDefs[i]) != null)
-          setOut(tokenDefs[i], null);
+        this.nfasByToken.remove(tokenDefs[i]);
       }
     }
-
-    if(result != null)
-      setOut(node, result);
   }
 
   @Override
   public void outATokenDef(ATokenDef node)
   {
-    Set set
-      = (Set) getOut(node.getStateList());
-    Object o1 = getOut(node.getRegExp());
+    Set<String> set = stateNamesFromStateList(node.getStateList());
+    Object o1 = this.nodeValues.get(node.getRegExp());
 
-    if((set
-        == null) || (set.size() == 0) || set.contains(stateName))
+    if(set.isEmpty() || set.contains(stateName))
     {
       //System.out.print("*");
 
@@ -98,7 +82,7 @@ public class ConstructNFA extends DepthFirstAdapter
       String name = ids.names.get(node);
 
       n1.states[n1.states.length - 1].accept = name;
-      setOut(node, n1);
+      this.nfasByToken.put(node, n1);
     }
     else
     {
@@ -106,17 +90,17 @@ public class ConstructNFA extends DepthFirstAdapter
     }
 
     // free memory
-    if(getOut(node.getStateList()) != null)
-      setOut(node.getStateList(), null);
-    if(getOut(node.getRegExp()) != null)
-      setOut(node.getRegExp(), null);
+    this.nodeValues.remove(node.getRegExp());
   }
 
-  @Override
-  public void outAStateList(AStateList node)
+  private static Set<String> stateNamesFromStateList(PStateList stateList)
   {
-    Set set
-      = new TreeSet();
+    if (stateList == null) {
+      return Collections.emptySet();
+    }
+
+    final AStateList node = (AStateList)stateList;
+    Set<String> set = new TreeSet<>();
     AStateListTail[] stateListTails = (AStateListTail[]) node.getStateLists().toArray(new AStateListTail[0]);
 
     for(int i = stateListTails.length - 1; i >= 0 ; i--)
@@ -126,8 +110,7 @@ public class ConstructNFA extends DepthFirstAdapter
     }
 
     set.add(node.getId().getText().toUpperCase());
-    setOut(node, set
-            );
+    return set;
   }
 
   @Override
@@ -140,7 +123,7 @@ public class ConstructNFA extends DepthFirstAdapter
     {
       for(int i = concats.length - 1; i >= 0 ; i--)
       {
-        Object o = getOut(concats[i]);
+        Object o = this.nodeValues.get(concats[i]);
         NFA nfa = (o instanceof NFA) ? (NFA) o : new NFA((CharSet) o);
 
         if(result == null)
@@ -153,18 +136,16 @@ public class ConstructNFA extends DepthFirstAdapter
         }
 
         // free memory
-        if(getOut(concats[i]) != null)
-          setOut(concats[i], null);
+        this.nodeValues.remove(concats[i]);
       }
-      setOut(node, result);
+      this.nodeValues.put(node, result);
     }
     else if(concats.length == 1)
     {
-      setOut(node, getOut(concats[0]));
+      this.nodeValues.put(node, this.nodeValues.get(concats[0]));
 
       // free memory
-      if(getOut(concats[0]) != null)
-        setOut(concats[0], null);
+      this.nodeValues.remove(concats[0]);
     }
   }
 
@@ -175,15 +156,14 @@ public class ConstructNFA extends DepthFirstAdapter
 
     if(unExps.length == 0)
     {
-      setOut(node, new NFA());
+      this.nodeValues.put(node, new NFA());
     }
     else if(unExps.length == 1)
     {
-      setOut(node, getOut(unExps[0]));
+      this.nodeValues.put(node, this.nodeValues.get(unExps[0]));
 
       // free memory
-      if(getOut(unExps[0]) != null)
-        setOut(unExps[0], null);
+      this.nodeValues.remove(unExps[0]);
     }
     else
     {
@@ -191,7 +171,7 @@ public class ConstructNFA extends DepthFirstAdapter
 
       for(int i = unExps.length - 1; i >= 0 ; i--)
       {
-        Object o = getOut(unExps[i]);
+        Object o = this.nodeValues.get(unExps[i]);
         NFA nfa = (o instanceof NFA) ? (NFA) o : new NFA((CharSet) o);
 
         if(result == null)
@@ -204,76 +184,56 @@ public class ConstructNFA extends DepthFirstAdapter
         }
 
         // free memory
-        if(getOut(unExps[i]) != null)
-          setOut(unExps[i], null);
+        this.nodeValues.remove(unExps[i]);
       }
 
-      setOut(node, result);
+      this.nodeValues.put(node, result);
     }
   }
 
   @Override
   public void outAUnExp(AUnExp node)
   {
-    Object o = getOut(node.getBasic());
-
-    char c = ' ';
-    if(node.getUnOp() != null)
-      c = ((Character) getOut(node.getUnOp())).charValue();
+    Object o = this.nodeValues.get(node.getBasic());
 
-    switch(c)
+    if(node.getUnOp() instanceof AStarUnOp)
     {
-    case '*':
-      {
-        NFA n = (o instanceof NFA) ? (NFA) o : new NFA((CharSet) o);
-        setOut(node, n.zeroOrMore());
-      }
-      break;
-    case '?':
-      {
-        NFA n = (o instanceof NFA) ? (NFA) o : new NFA((CharSet) o);
-        setOut(node, n.zeroOrOne());
-      }
-      break;
-    case '+':
-      {
-        NFA n = (o instanceof NFA) ? (NFA) o : new NFA((CharSet) o);
-        setOut(node, n.oneOrMore());
-      }
-      break;
-    default:
-      {
-        setOut(node, o);
-      }
-      break;
+      NFA n = (o instanceof NFA) ? (NFA) o : new NFA((CharSet) o);
+      this.nodeValues.put(node, n.zeroOrMore());
+    }
+    else if(node.getUnOp() instanceof AQMarkUnOp)
+    {
+      NFA n = (o instanceof NFA) ? (NFA) o : new NFA((CharSet) o);
+      this.nodeValues.put(node, n.zeroOrOne());
+    }
+    else if(node.getUnOp() instanceof APlusUnOp)
+    {
+      NFA n = (o instanceof NFA) ? (NFA) o : new NFA((CharSet) o);
+      this.nodeValues.put(node, n.oneOrMore());
+    }
+    else
+    {
+      this.nodeValues.put(node, o);
     }
 
     // free memory
-    if(getOut(node.getBasic()) != null)
-      setOut(node.getBasic(), null);
-    if(getOut(node.getUnOp()) != null)
-      setOut(node.getUnOp(), null);
+    this.nodeValues.remove(node.getBasic());
   }
 
   @Override
   public void outACharBasic(ACharBasic node)
   {
-    char c = ((Character) getOut(node.getChar())).charValue();
-    setOut(node, new CharSet(c));
-
-    // free memory
-    if(getOut(node.getChar()) != null)
-      setOut(node.getChar(), null);
+    char c = charValue(node.getChar());
+    this.nodeValues.put(node, new CharSet(c));
   }
 
   @Override
   public void outASetBasic(ASetBasic node)
   {
-    setOut(node, getOut(node.getSet()));
+    this.nodeValues.put(node, this.nodeValues.get(node.getSet()));
 
     // free memory
-    if(getOut(node.getSet()) != null)
-      setOut(node.getSet(), null);
+    this.nodeValues.remove(node.getSet());
   }
 
   @Override
@@ -282,50 +242,50 @@ public class ConstructNFA extends DepthFirstAdapter
     String s = node.getString().getText();
     s = s.substring(1, s.length() -1);
 
-    setOut(node, new NFA(s));
+    this.nodeValues.put(node, new NFA(s));
   }
 
   @Override
   public void outAIdBasic(AIdBasic node)
   {
-    Object o = getOut(ids.helpers.get(node.getId().getText()));
+    Object o = this.nodeValues.get(ids.helpers.get(node.getId().getText()));
 
     if(o instanceof NFA)
     {
-      setOut(node, ((NFA) o).clone());
+      this.nodeValues.put(node, ((NFA) o).clone());
     }
     else
     {
-      setOut(node, ((CharSet) o).clone());
+      this.nodeValues.put(node, ((CharSet) o).clone());
     }
   }
 
   @Override
   public void outARegExpBasic(ARegExpBasic node)
   {
-    setOut(node, getOut(node.getRegExp()));
+    this.nodeValues.put(node, this.nodeValues.get(node.getRegExp()));
 
     // free memory
-    if(getOut(node.getRegExp()) != null)
-      setOut(node.getRegExp(), null);
-  }
-
-  @Override
-  public void outACharChar(ACharChar node)
-  {
-    setOut(node, node.getChar().getText().charAt(1));
-  }
-
-  @Override
-  public void outADecChar(ADecChar node)
-  {
-    setOut(node, (char)Integer.parseInt(node.getDecChar().getText()));
+    this.nodeValues.remove(node.getRegExp());
   }
 
-  @Override
-  public void outAHexChar(AHexChar node)
-  {
-    setOut(node, (char)Integer.parseInt(node.getHexChar().getText().substring(2), 16));
+  private static char charValue(final PChar node) {
+    if(node instanceof ACharChar)
+    {
+      return ((ACharChar)node).getChar().getText().charAt(1);
+    }
+    else if(node instanceof ADecChar)
+    {
+      return (char)Integer.parseInt(((ADecChar)node).getDecChar().getText());
+    }
+    else if(node instanceof AHexChar)
+    {
+      return (char)Integer.parseInt(((AHexChar)node).getHexChar().getText().substring(2), 16);
+    }
+    else
+    {
+      throw new IllegalArgumentException("Unhandled char type: " + node.getClass());
+    }
   }
 
   @Override
@@ -333,22 +293,16 @@ public class ConstructNFA extends DepthFirstAdapter
   {
     try
     {
-      CharSet cs1 = (CharSet) getOut(node.getLeft());
-      CharSet cs2 = (CharSet) getOut(node.getRight());
-      char binop = (Character) getOut(node.getBinOp());
+      CharSet cs1 = (CharSet) this.nodeValues.get(node.getLeft());
+      CharSet cs2 = (CharSet) this.nodeValues.get(node.getRight());
 
-      switch(binop)
+      if(node.getBinOp() instanceof APlusBinOp)
       {
-      case '+':
-        {
-          setOut(node, cs1.union(cs2));
-        }
-        break;
-      case '-':
-        {
-          setOut(node, cs1.diff(cs2));
-        }
-        break;
+        this.nodeValues.put(node, cs1.union(cs2));
+      }
+      else if(node.getBinOp() instanceof AMinusBinOp)
+      {
+        this.nodeValues.put(node, cs1.diff(cs2));
       }
     }
     catch(Exception e)
@@ -357,84 +311,22 @@ public class ConstructNFA extends DepthFirstAdapter
     }
 
     // free memory
-    if(getOut(node.getLeft()) != null)
-      setOut(node.getLeft(), null);
-    if(getOut(node.getBinOp()) != null)
-      setOut(node.getBinOp(), null);
-    if(getOut(node.getRight()) != null)
-      setOut(node.getRight(), null);
+    this.nodeValues.remove(node.getLeft());
+    this.nodeValues.remove(node.getRight());
   }
 
   @Override
   public void outAIntervalSet(AIntervalSet node)
   {
-    char c1 = ((Character) getOut(node.getLeft())).charValue();
-    char c2 = ((Character) getOut(node.getRight())).charValue();
+    char c1 = charValue(node.getLeft());
+    char c2 = charValue(node.getRight());
 
     if(c1 > c2)
     {
       throw new RuntimeException(node + " is invalid.");
     }
 
-    setOut(node, new CharSet(c1, c2));
-
-    // free memory
-    if(getOut(node.getLeft()) != null)
-      setOut(node.getLeft(), null);
-    if(getOut(node.getRight()) != null)
-      setOut(node.getRight(), null);
-  }
-
-  @Override
-  public void outAStarUnOp(AStarUnOp node)
-  {
-    setOut(node, '*');
-  }
-
-  @Override
-  public void outAQMarkUnOp(AQMarkUnOp node)
-  {
-    setOut(node, '?');
-  }
-
-  @Override
-  public void outAPlusUnOp(APlusUnOp node)
-  {
-    setOut(node, '+');
-  }
-
-  @Override
-  public void outAPlusBinOp(APlusBinOp node)
-  {
-    setOut(node, '+');
-  }
-
-  @Override
-  public void outAMinusBinOp(AMinusBinOp node)
-  {
-    setOut(node, '-');
-  }
-
-  @Override
-  public Object getOut(Node node)
-  {
-    if(node == null)
-    {
-      return null;
-    }
-
-    return super.getOut(node);
-  }
-
-  @Override
-  public void setOut(Node node, Object out)
-  {
-    if(node == null)
-    {
-      throw new NullPointerException();
-    }
-
-    super.setOut(node, out);
+    this.nodeValues.put(node, new CharSet(c1, c2));
   }
 }
 
diff --git a/src/main/java/org/sablecc/sablecc/GenLexer.java b/src/main/java/org/sablecc/sablecc/GenLexer.java
index 7aa607bcfaf9a7be7de12f8142e32367f64f0479..c72a0b4ce5224c9dadc1227f585f92992d0462e3 100644
--- a/src/main/java/org/sablecc/sablecc/GenLexer.java
+++ b/src/main/java/org/sablecc/sablecc/GenLexer.java
@@ -81,7 +81,7 @@ public class GenLexer extends AnalysisAdapter
       tree.apply(nfaConstructor);
       System.out.println();
 
-      NFA nfa = (NFA) nfaConstructor.getOut(tree);
+      NFA nfa = nfaConstructor.getNFA();
       nfaConstructor = null;
 
       System.out.println(" - Constructing DFA.");