Initial import of JSilver templating engine

Change-Id: Ia96153305e5f392ddebf30240a650a6ec02457d2
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..ce1ab5c
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	$(call all-java-files-under, src)
+
+LOCAL_MODULE := jsilver
+
+LOCAL_JAVA_LIBRARIES := guavalib
+LOCAL_JAVA_RESOURCE_DIRS := src
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..cc8d7a1
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,60 @@
+<project name="JSilver" default="jar"> 
+  <property name="jar.dir" value="build/dist" />
+  <property name="jar.file" value="${jar.dir}/jsilver.jar"/>
+	
+  <property name="src" value="src" />
+  <property name="gen" value="build/gen" />
+ 
+	<property name="lib.guava" value="lib/guava-r06.jar" />
+	
+  <target name="gen" description="Code generation" >
+    <mkdir dir="${gen}" />
+    <exec executable="java">
+      <arg value="-jar" />
+      <arg value="sablecc/sablecc.jar" />
+      <arg value="src/com/google/clearsilver/jsilver/syntax/jsilver.sablecc" />
+      <arg value="-d" />
+      <arg value="${gen}" />
+    </exec>
+    
+    <copy file="sablecc/optimizations/AOptimizedMultipleCommand.java"
+          todir="${gen}/com/google/clearsilver/jsilver/syntax/node" />
+  </target>
+ 
+  <target name="compile" description="Compile Java source." depends="gen"> 
+    <mkdir dir="build/classes"/> 
+
+    <javac srcdir="${src}:${gen}"
+         debug="on"
+         destdir="build/classes"
+         source="1.5"
+         target="1.5"
+         extdirs=""
+    	> 
+      <compilerarg value="-Xlint:all"/> 
+      <classpath> 
+        <fileset dir="lib/">
+          <include name="*.jar"/>
+        </fileset>
+      </classpath> 
+    	<exclude name="com/google/clearsilver/jsilver/benchmark/*.java"/>
+    </javac> 
+  </target> 
+ 
+  <target name="jar" depends="compile" description="Build jar."> 
+    <mkdir dir="${jar.dir}"/> 
+    <jar jarfile="${jar.file}"> 
+      <fileset dir="build/classes"/> 
+    	<zipfileset src="${lib.guava}" />
+    	<fileset dir="${gen}">
+         <include name="**/*.dat"/>
+     </fileset>
+    </jar> 
+  </target> 
+
+  <target name="clean"
+      description="Remove generated files."> 
+    <delete dir="build" />
+  </target> 
+ 
+</project> 
diff --git a/src/com/google/clearsilver/jsilver/DataLoader.java b/src/com/google/clearsilver/jsilver/DataLoader.java
new file mode 100644
index 0000000..dfa3b82
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/DataLoader.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver;
+
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException;
+
+import java.io.IOException;
+
+/**
+ * Loads data from resources.
+ */
+public interface DataLoader {
+
+  /**
+   * Create new Data instance, ready to be populated.
+   */
+  Data createData();
+
+  /**
+   * Loads data in Hierarchical Data Format (HDF) into an existing Data object.
+   */
+  void loadData(final String dataFileName, Data output) throws JSilverBadSyntaxException,
+      IOException;
+
+  /**
+   * Loads data in Hierarchical Data Format (HDF) into a new Data object.
+   */
+  Data loadData(String dataFileName) throws IOException;
+}
diff --git a/src/com/google/clearsilver/jsilver/JSilver.java b/src/com/google/clearsilver/jsilver/JSilver.java
new file mode 100644
index 0000000..7365b29
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/JSilver.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver;
+
+import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.compiler.TemplateCompiler;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.data.DataFactory;
+import com.google.clearsilver.jsilver.data.HDFDataFactory;
+import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException;
+import com.google.clearsilver.jsilver.exceptions.JSilverException;
+import com.google.clearsilver.jsilver.functions.Function;
+import com.google.clearsilver.jsilver.functions.FunctionRegistry;
+import com.google.clearsilver.jsilver.functions.TextFilter;
+import com.google.clearsilver.jsilver.functions.bundles.ClearSilverCompatibleFunctions;
+import com.google.clearsilver.jsilver.functions.bundles.CoreOperators;
+import com.google.clearsilver.jsilver.interpreter.InterpretedTemplateLoader;
+import com.google.clearsilver.jsilver.interpreter.LoadingTemplateFactory;
+import com.google.clearsilver.jsilver.interpreter.OptimizerProvider;
+import com.google.clearsilver.jsilver.interpreter.OptimizingTemplateFactory;
+import com.google.clearsilver.jsilver.interpreter.TemplateFactory;
+import com.google.clearsilver.jsilver.output.InstanceOutputBufferProvider;
+import com.google.clearsilver.jsilver.output.OutputBufferProvider;
+import com.google.clearsilver.jsilver.output.ThreadLocalOutputBufferProvider;
+import com.google.clearsilver.jsilver.precompiler.PrecompiledTemplateLoader;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.syntax.DataCommandConsolidator;
+import com.google.clearsilver.jsilver.syntax.SyntaxTreeOptimizer;
+import com.google.clearsilver.jsilver.syntax.StructuralWhitespaceStripper;
+import com.google.clearsilver.jsilver.syntax.node.Switch;
+import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader;
+import com.google.clearsilver.jsilver.template.HtmlWhiteSpaceStripper;
+import com.google.clearsilver.jsilver.template.Template;
+import com.google.clearsilver.jsilver.template.TemplateLoader;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * JSilver templating system.
+ * 
+ * <p>
+ * This is a pure Java version of ClearSilver.
+ * </p>
+ * 
+ * <h2>Example Usage</h2>
+ * 
+ * <pre>
+ * // Load resources (e.g. templates) from directory.
+ * JSilver jSilver = new JSilver(new FileResourceLoader("/path/to/templates"));
+ *
+ * // Set up some data.
+ * Data data = new Data();
+ * data.setValue("name.first", "Mr");
+ * data.setValue("name.last", "Man");
+ *
+ * // Render template to System.out. Writer output = ...;
+ * jSilver.render("say-hello", data, output);
+ * </pre>
+ * 
+ * For example usage, see java/com/google/clearsilver/jsilver/examples.
+ * 
+ * Additional options can be passed to the constructor using JSilverOptions.
+ * 
+ * @see <a href="http://go/jsilver">JSilver Docs</a>
+ * @see <a href="http://clearsilver.net">ClearSilver Docs</a>
+ * @see JSilverOptions
+ * @see Data
+ * @see ResourceLoader
+ */
+public final class JSilver implements TemplateRenderer, DataLoader {
+
+  private final JSilverOptions options;
+
+  private final TemplateLoader templateLoader;
+
+  /**
+   * If caching enabled, the cached wrapper (otherwise null). Kept here so we can call clearCache()
+   * later.
+   */
+
+  private final FunctionRegistry globalFunctions = new ClearSilverCompatibleFunctions();
+
+  private final ResourceLoader defaultResourceLoader;
+
+  private final DataFactory dataFactory;
+
+  // Object used to return Appendable output buffers when needed.
+  private final OutputBufferProvider outputBufferProvider;
+  public static final String VAR_ESCAPE_MODE_KEY = "Config.VarEscapeMode";
+  public static final String AUTO_ESCAPE_KEY = "Config.AutoEscape";
+
+  /**
+   * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g.
+   *        directory, classpath, memory, etc.
+   * @param options Additional options.
+   * @see JSilverOptions
+   */
+  public JSilver(ResourceLoader defaultResourceLoader, JSilverOptions options) {
+    // To ensure that options cannot be changed externally, we clone them and
+    // use the frozen clone.
+    options = options.clone();
+
+    this.defaultResourceLoader = defaultResourceLoader;
+    this.dataFactory =
+        new HDFDataFactory(options.getIgnoreAttributes(), options.getStringInternStrategy());
+    this.options = options;
+
+    // Setup the output buffer provider either with a threadlocal pool
+    // or creating a new instance each time it is asked for.
+    int bufferSize = options.getInitialBufferSize();
+    if (options.getUseOutputBufferPool()) {
+      // Use a ThreadLocal to reuse StringBuilder objects.
+      outputBufferProvider = new ThreadLocalOutputBufferProvider(bufferSize);
+    } else {
+      // Create a new StringBuilder each time.
+      outputBufferProvider = new InstanceOutputBufferProvider(bufferSize);
+    }
+
+    // Loads the template from the resource loader, manipulating the AST as
+    // required for correctness.
+    TemplateFactory templateFactory = new LoadingTemplateFactory();
+
+    // Applies optimizations to improve performance.
+    // These steps are entirely optional, and are not required for correctness.
+    templateFactory = setupOptimizerFactory(templateFactory);
+
+    TemplateLoader templateLoader;
+    List<DelegatingTemplateLoader> delegatingTemplateLoaders =
+        new LinkedList<DelegatingTemplateLoader>();
+    AutoEscapeOptions autoEscapeOptions = new AutoEscapeOptions();
+    autoEscapeOptions.setPropagateEscapeStatus(options.getPropagateEscapeStatus());
+    autoEscapeOptions.setLogEscapedVariables(options.getLogEscapedVariables());
+    if (options.getCompileTemplates()) {
+      // Compiled templates.
+      TemplateCompiler compiler =
+          new TemplateCompiler(templateFactory, globalFunctions, autoEscapeOptions);
+      delegatingTemplateLoaders.add(compiler);
+      templateLoader = compiler;
+    } else {
+      // Walk parse tree every time.
+      InterpretedTemplateLoader interpreter =
+          new InterpretedTemplateLoader(templateFactory, globalFunctions, autoEscapeOptions);
+      delegatingTemplateLoaders.add(interpreter);
+      templateLoader = interpreter;
+    }
+
+    // Do we want to load precompiled Template class objects?
+    if (options.getPrecompiledTemplateMap() != null) {
+      // Load precompiled template classes.
+      PrecompiledTemplateLoader ptl =
+          new PrecompiledTemplateLoader(templateLoader, options.getPrecompiledTemplateMap(),
+              globalFunctions, autoEscapeOptions);
+      delegatingTemplateLoaders.add(ptl);
+      templateLoader = ptl;
+    }
+
+    for (DelegatingTemplateLoader loader : delegatingTemplateLoaders) {
+      loader.setTemplateLoaderDelegate(templateLoader);
+    }
+    this.templateLoader = templateLoader;
+  }
+
+  /**
+   * Applies optimizations to improve performance. These steps are entirely optional, and are not
+   * required for correctness.
+   */
+  private TemplateFactory setupOptimizerFactory(TemplateFactory templateFactory) {
+    // DataCommandConsolidator saves state so we need to create a new one
+    // every time we run it.
+    OptimizerProvider dataCommandConsolidatorProvider = new OptimizerProvider() {
+      public Switch getOptimizer() {
+        return new DataCommandConsolidator();
+      }
+    };
+
+    // SyntaxTreeOptimizer has no state so we can use the same object
+    // concurrently, but it is cheap to make so lets be consistent.
+    OptimizerProvider syntaxTreeOptimizerProvider = new OptimizerProvider() {
+      public Switch getOptimizer() {
+        return new SyntaxTreeOptimizer();
+      }
+    };
+
+    OptimizerProvider stripStructuralWhitespaceProvider = null;
+    if (options.getStripStructuralWhiteSpace()) {
+      // StructuralWhitespaceStripper has state so create a new one each time.
+      stripStructuralWhitespaceProvider = new OptimizerProvider() {
+        public Switch getOptimizer() {
+          return new StructuralWhitespaceStripper();
+        }
+      };
+    }
+
+    return new OptimizingTemplateFactory(templateFactory, dataCommandConsolidatorProvider,
+        syntaxTreeOptimizerProvider, stripStructuralWhitespaceProvider);
+  }
+
+  /**
+   * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g.
+   *        directory, classpath, memory, etc.
+   * @param cacheTemplates Whether to cache templates. Cached templates are much faster but do not
+   *        check the filesystem for updates. Use true in prod, false in dev.
+   * @deprecated Use {@link #JSilver(ResourceLoader, JSilverOptions)}.
+   */
+  @Deprecated
+  public JSilver(ResourceLoader defaultResourceLoader, boolean cacheTemplates) {
+    this(defaultResourceLoader, new JSilverOptions().setCacheTemplates(cacheTemplates));
+  }
+
+  /**
+   * Creates a JSilver instance with default options.
+   * 
+   * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g.
+   *        directory, classpath, memory, etc.
+   * @see JSilverOptions
+   */
+  public JSilver(ResourceLoader defaultResourceLoader) {
+    this(defaultResourceLoader, new JSilverOptions());
+  }
+
+  /**
+   * Renders a given template and provided data, writing to an arbitrary output.
+   * 
+   * @param templateName Name of template to load (e.g. "things/blah.cs").
+   * @param data Data to be used in template.
+   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
+   *        System.out/err), StringBuffer/StringBuilder or anything that implements Appendable
+   * @param resourceLoader How to find the template data to render and any included files it depends
+   *        on.
+   */
+  @Override
+  public void render(String templateName, Data data, Appendable output,
+      ResourceLoader resourceLoader) throws IOException, JSilverException {
+    EscapeMode escapeMode = getEscapeMode(data);
+    render(templateLoader.load(templateName, resourceLoader, escapeMode), data, output,
+        resourceLoader);
+  }
+
+  /**
+   * Renders a given template and provided data, writing to an arbitrary output.
+   * 
+   * @param templateName Name of template to load (e.g. "things/blah.cs").
+   * @param data Data to be used in template.
+   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
+   *        System.out/err), StringBuffer/StringBuilder or anything that implements
+   */
+  @Override
+  public void render(String templateName, Data data, Appendable output) throws IOException,
+      JSilverException {
+    render(templateName, data, output, defaultResourceLoader);
+  }
+
+  /**
+   * Same as {@link TemplateRenderer#render(String, Data, Appendable)}, except returns rendered
+   * template as a String.
+   */
+  @Override
+  public String render(String templateName, Data data) throws IOException, JSilverException {
+    Appendable output = createAppendableBuffer();
+    try {
+      render(templateName, data, output);
+      return output.toString();
+    } finally {
+      releaseAppendableBuffer(output);
+    }
+  }
+
+  /**
+   * Renders a given template and provided data, writing to an arbitrary output.
+   * 
+   * @param template Template to load.
+   * @param data Data to be used in template.
+   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
+   *        System.out/err), StringBuffer/StringBuilder or anything that implements
+   *        java.io.Appendable.
+   */
+  @Override
+  public void render(Template template, Data data, Appendable output, ResourceLoader resourceLoader)
+      throws IOException, JSilverException {
+    if (options.getStripHtmlWhiteSpace() && !(output instanceof HtmlWhiteSpaceStripper)) {
+      // Strip out whitespace from rendered HTML content.
+      output = new HtmlWhiteSpaceStripper(output);
+    }
+    template.render(data, output, resourceLoader);
+  }
+
+  /**
+   * Renders a given template and provided data, writing to an arbitrary output.
+   * 
+   * @param template Template to load.
+   * @param data Data to be used in template.
+   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
+   *        System.out/err), StringBuffer/StringBuilder or anything that implements
+   *        java.io.Appendable.
+   */
+  @Override
+  public void render(Template template, Data data, Appendable output) throws IOException,
+      JSilverException {
+    render(template, data, output, defaultResourceLoader);
+  }
+
+  @Override
+  public String render(Template template, Data data) throws IOException, JSilverException {
+    Appendable output = createAppendableBuffer();
+    try {
+      render(template, data, output);
+      return output.toString();
+    } finally {
+      releaseAppendableBuffer(output);
+    }
+  }
+
+  /**
+   * Renders a given template from the content passed in. That is, the first parameter is the actual
+   * template content rather than the filename to load.
+   * 
+   * @param content Content of template (e.g. "Hello &lt;cs var:name ?&gt;").
+   * @param data Data to be used in template.
+   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
+   *        System.out/err), StringBuffer/StringBuilder or anything that implements
+   *        java.io.Appendable
+   */
+  @Override
+  public void renderFromContent(String content, Data data, Appendable output) throws IOException,
+      JSilverException {
+    EscapeMode escapeMode = getEscapeMode(data);
+    render(templateLoader.createTemp("[renderFromContent]", content, escapeMode), data, output);
+  }
+
+  /**
+   * Same as {@link #renderFromContent(String, Data, Appendable)}, except returns rendered template
+   * as a String.
+   */
+  @Override
+  public String renderFromContent(String content, Data data) throws IOException, JSilverException {
+    Appendable output = createAppendableBuffer();
+    try {
+      renderFromContent(content, data, output);
+      return output.toString();
+    } finally {
+      releaseAppendableBuffer(output);
+    }
+  }
+
+  /**
+   * Determine the escaping to apply based on Config variables in HDF. If there is no escaping
+   * specified in the HDF, check whether JSilverOptions has any escaping configured.
+   * 
+   * @param data HDF Data to check
+   * @return EscapeMode
+   */
+  public EscapeMode getEscapeMode(Data data) {
+    EscapeMode escapeMode =
+        EscapeMode.computeEscapeMode(data.getValue(VAR_ESCAPE_MODE_KEY), data
+            .getBooleanValue(AUTO_ESCAPE_KEY));
+    if (escapeMode.equals(EscapeMode.ESCAPE_NONE)) {
+      escapeMode = options.getEscapeMode();
+    }
+
+    return escapeMode;
+  }
+
+  /**
+   * Override this to change the type of Appendable buffer used in {@link #render(String, Data)}.
+   */
+  public Appendable createAppendableBuffer() {
+    return outputBufferProvider.get();
+  }
+
+  public void releaseAppendableBuffer(Appendable buffer) {
+    outputBufferProvider.release(buffer);
+  }
+
+  /**
+   * Registers a global Function that can be used from any template.
+   */
+  public void registerGlobalFunction(String name, Function function) {
+    globalFunctions.registerFunction(name, function);
+  }
+
+  /**
+   * Registers a global TextFilter as function that can be used from any template.
+   */
+  public void registerGlobalFunction(String name, TextFilter textFilter) {
+    globalFunctions.registerFunction(name, textFilter);
+  }
+
+  /**
+   * Registers a global escaper. This also makes it available as a Function named with "_escape"
+   * suffix (e.g. "html_escape").
+   */
+  public void registerGlobalEscaper(String name, TextFilter escaper) {
+    globalFunctions.registerFunction(name + "_escape", escaper, true);
+    globalFunctions.registerEscapeMode(name, escaper);
+  }
+
+  /**
+   * Create new Data instance, ready to be populated.
+   */
+  public Data createData() {
+    return dataFactory.createData();
+  }
+
+  /**
+   * Loads data in Hierarchical Data Format (HDF) into an existing Data object.
+   */
+  @Override
+  public void loadData(String dataFileName, Data output) throws JSilverBadSyntaxException,
+      IOException {
+    dataFactory.loadData(dataFileName, defaultResourceLoader, output);
+  }
+
+  /**
+   * Loads data in Hierarchical Data Format (HDF) into a new Data object.
+   */
+  @Override
+  public Data loadData(String dataFileName) throws IOException {
+    return dataFactory.loadData(dataFileName, defaultResourceLoader);
+  }
+
+  /**
+   * Gets underlying ResourceLoader so you can access arbitrary files using the same mechanism as
+   * JSilver.
+   */
+  public ResourceLoader getResourceLoader() {
+    return defaultResourceLoader;
+  }
+
+  /**
+   * Force all cached templates to be cleared.
+   */
+  public void clearCache() {
+
+  }
+
+  /**
+   * Returns the TemplateLoader used by this JSilver template renderer. Needed for HDF/CS
+   * compatbility.
+   */
+  public TemplateLoader getTemplateLoader() {
+    return templateLoader;
+  }
+
+  /**
+   * Returns a copy of the JSilverOptions used by this JSilver instance.
+   */
+  public JSilverOptions getOptions() {
+    return options.clone();
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/JSilverOptions.java b/src/com/google/clearsilver/jsilver/JSilverOptions.java
new file mode 100644
index 0000000..c382339
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/JSilverOptions.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.NoOpStringInternStrategy;
+import com.google.clearsilver.jsilver.data.StringInternStrategy;
+
+import java.util.Map;
+
+/**
+ * Options for JSilver.
+ * 
+ * Note: Setter methods also return reference to this, allowing options to be defined in one
+ * statement.
+ * 
+ * e.g. new JSilver(..., new JSilverOptions().setSomething(true).setAnother(false));
+ * 
+ * @see JSilver
+ */
+public class JSilverOptions implements Cloneable {
+
+  private boolean cacheTemplates = true;
+  private boolean compileTemplates = false;
+  private int initialBufferSize = 8192;
+  private boolean ignoreAttributes = false;
+  private Map<Object, String> precompiledTemplateMap = null;
+  private boolean useStrongCacheReferences = false;
+  private EscapeMode escapeMode = EscapeMode.ESCAPE_NONE;
+  private boolean propagateEscapeStatus = false;
+
+  /**
+   * A pool of strings used to optimize HDF parsing.
+   * 
+   * String interning has been shown to improve GC performance, but also to increase CPU usage. To
+   * avoid any possible unexpected changes in behavior it is disabled by default.
+   */
+  private StringInternStrategy stringInternStrategy = new NoOpStringInternStrategy();
+
+  /**
+   * This flag is used to enable logging of all variables whose values are modified by auto escaping
+   * or &lt;cs escape&gt; commands. These will be logged at {@code Level.WARNING}.
+   */
+  private boolean logEscapedVariables = false;
+  private boolean useOutputBufferPool = false;
+  private boolean stripHtmlWhiteSpace = false;
+  private boolean stripStructuralWhiteSpace = false;
+  private boolean allowGlobalDataModification = false;
+  private boolean keepTemplateCacheFresh = false;
+  private int loadPathCacheSize = 1000;
+
+  // When adding fields, ensure you:
+  // * add getter.
+  // * add setter (which returns this).
+  // * add to clone() method if necessary.
+
+  /**
+   * Set the initial size of the load path cache container. Setting this to 0 causes load path cache
+   * to be disabled.
+   */
+  public JSilverOptions setLoadPathCacheSize(int loadPathCacheSize) {
+    this.loadPathCacheSize = loadPathCacheSize;
+    return this;
+  }
+
+  /**
+   * @see #setLoadPathCacheSize(int)
+   */
+  public int getLoadPathCacheSize() {
+    return loadPathCacheSize;
+  }
+
+  /**
+   * Whether to cache templates. This will only ever load and parse a template from disk once. Best
+   * switched on for production but left off for production (so you can update templates without
+   * restarting).
+   */
+  public JSilverOptions setCacheTemplates(boolean cacheTemplates) {
+    this.cacheTemplates = cacheTemplates;
+    return this;
+  }
+
+  /**
+   * @see #setCacheTemplates(boolean)
+   */
+  public boolean getCacheTemplates() {
+    return cacheTemplates;
+  }
+
+  /**
+   * Compile templates to Java byte code. This slows down the initial render as it performs a
+   * compilation step, but then subsequent render are faster.
+   * 
+   * Compiled templates are always cached.
+   * 
+   * WARNING: This functionality is experimental. Use with caution.
+   */
+  public JSilverOptions setCompileTemplates(boolean compileTemplates) {
+    this.compileTemplates = compileTemplates;
+    return this;
+  }
+
+  /**
+   * @see #setCompileTemplates(boolean)
+   */
+  public boolean getCompileTemplates() {
+    return compileTemplates;
+  }
+
+  /**
+   * If set, then HDF attributes in HDF files will be ignored and not stored in the Data object
+   * filled by the parser. Default is {@code false}. Many applications use HDF attributes only in
+   * template preprocessing (like translation support) and never in production servers where the
+   * templates are rendered. By disabling attribute parsing, these applications can save on memory
+   * for storing HDF structures read from files.
+   */
+  public JSilverOptions setIgnoreAttributes(boolean ignoreAttributes) {
+    this.ignoreAttributes = ignoreAttributes;
+    return this;
+  }
+
+  /**
+   * @see #setIgnoreAttributes(boolean)
+   */
+  public boolean getIgnoreAttributes() {
+    return ignoreAttributes;
+  }
+
+  /**
+   * Initial buffer size used when rendering directly to a string.
+   */
+  public JSilverOptions setInitialBufferSize(int initialBufferSize) {
+    this.initialBufferSize = initialBufferSize;
+    return this;
+  }
+
+  /**
+   * @see #setInitialBufferSize(int)
+   */
+  public int getInitialBufferSize() {
+    return initialBufferSize;
+  }
+
+  /**
+   * Optional mapping of TemplateLoader keys to Template instances that will be queried when loading
+   * a template. If the Template is found it is returned. If not, the next template loader is
+   * consulted.
+   * 
+   * @param precompiledTemplateMap map of TemplateLoader keys to corresponding class names that
+   *        should be valid BaseCompiledTemplate subclasses. Set to {@code null} (default) to not
+   *        load precompiled templates.
+   */
+  public JSilverOptions setPrecompiledTemplateMap(Map<Object, String> precompiledTemplateMap) {
+    this.precompiledTemplateMap = precompiledTemplateMap;
+    return this;
+  }
+
+  /**
+   * @see #setPrecompiledTemplateMap(java.util.Map)
+   * @return a mapping of TemplateLoader keys to corresponding BaseCompiledTemplate class names, or
+   *         {@code null} (default) if no precompiled templates should be preloaded.
+   */
+  public Map<Object, String> getPrecompiledTemplateMap() {
+    return precompiledTemplateMap;
+  }
+
+  /**
+   * If {@code true}, then the template cache will use strong persistent references for the values.
+   * If {@code false} (default) it will use soft references. Warning: The cache size is unbounded so
+   * only use this option if you have sufficient memory to load all the Templates into memory.
+   */
+  public JSilverOptions setUseStrongCacheReferences(boolean value) {
+    this.useStrongCacheReferences = value;
+    return this;
+  }
+
+  /**
+   * @see #setUseStrongCacheReferences(boolean)
+   */
+  public boolean getUseStrongCacheReferences() {
+    return useStrongCacheReferences;
+  }
+
+  /**
+   * @see #setEscapeMode(com.google.clearsilver.jsilver.autoescape.EscapeMode)
+   */
+  public EscapeMode getEscapeMode() {
+    return escapeMode;
+  }
+
+  /**
+   * Escape any template being rendered with the given escaping mode. If the mode is ESCAPE_HTML,
+   * ESCAPE_URL or ESCAPE_JS, the corresponding escaping will be all variables in the template. If
+   * the mode is ESCAPE_AUTO, enable <a href="http://go/autoescapecs">auto escaping</a> on
+   * templates. For each variable in the template, this will determine what type of escaping should
+   * be applied to the variable, and automatically apply this escaping. This flag can be overriden
+   * by setting appropriate HDF variables before loading a template. If Config.AutoEscape is 1, auto
+   * escaping is enabled. If Config.VarEscapeMode is set to one of 'html', 'js' or 'url', the
+   * corresponding escaping is applied to all variables.
+   * 
+   * @param escapeMode
+   */
+  public JSilverOptions setEscapeMode(EscapeMode escapeMode) {
+    this.escapeMode = escapeMode;
+    return this;
+  }
+
+  /**
+   * @see #setPropagateEscapeStatus
+   */
+  public boolean getPropagateEscapeStatus() {
+    return propagateEscapeStatus;
+  }
+
+  /**
+   * Only used for templates that are being <a href="http://go/autoescapecs">auto escaped</a>. If
+   * {@code true} and auto escaping is enabled, variables created by &lt;cs set&gt; or &lt;cs
+   * call&gt; commands are not auto escaped if they are assigned constant or escaped values. This is
+   * disabled by default.
+   * 
+   * @see #setEscapeMode
+   */
+  public JSilverOptions setPropagateEscapeStatus(boolean propagateEscapeStatus) {
+    this.propagateEscapeStatus = propagateEscapeStatus;
+    return this;
+  }
+
+  /**
+   * Sets the {@link StringInternStrategy} object that will be used to optimize HDF parsing.
+   * 
+   * <p>
+   * Set value should not be {@code null} as it can cause {@link NullPointerException}.
+   * 
+   * @param stringInternStrategy - {@link StringInternStrategy} object
+   */
+  public void setStringInternStrategy(StringInternStrategy stringInternStrategy) {
+    if (stringInternStrategy == null) {
+      throw new IllegalArgumentException("StringInternStrategy should not be null.");
+    }
+    this.stringInternStrategy = stringInternStrategy;
+  }
+
+  /**
+   * Returns {@link StringInternStrategy} object that is used for optimization of HDF parsing.
+   * 
+   * <p>
+   * The returned value should never be {@code null}.
+   * 
+   * @return currently used {@link StringInternStrategy} object.
+   */
+  public StringInternStrategy getStringInternStrategy() {
+    return stringInternStrategy;
+  }
+
+  /**
+   * If {@code true}, then use a threadlocal buffer pool for holding rendered output when outputting
+   * to String. Assumes that render() is called from a thread and that thread will not reenter
+   * render() while it is running. If {@code false}, a new buffer is allocated for each request
+   * where an Appendable output object was not provided.
+   */
+  public JSilverOptions setUseOutputBufferPool(boolean value) {
+    this.useOutputBufferPool = value;
+    return this;
+  }
+
+  /**
+   * @see #setUseOutputBufferPool(boolean)
+   */
+  public boolean getUseOutputBufferPool() {
+    return useOutputBufferPool;
+  }
+
+  /**
+   * If {@code true}, then unnecessary whitespace will be stripped from the output. 'Unnecessary' is
+   * meant in terms of HTML output. See
+   * {@link com.google.clearsilver.jsilver.template.HtmlWhiteSpaceStripper} for more info.
+   */
+  public JSilverOptions setStripHtmlWhiteSpace(boolean value) {
+    this.stripHtmlWhiteSpace = value;
+    return this;
+  }
+
+  /**
+   * @see #setStripHtmlWhiteSpace(boolean)
+   */
+  public boolean getStripHtmlWhiteSpace() {
+    return stripHtmlWhiteSpace;
+  }
+
+  /**
+   * If {@code true}, then structural whitespace will be stripped from the output. This allows
+   * templates to be written to more closely match normal programming languages. See
+   * {@link com.google.clearsilver.jsilver.syntax.StructuralWhitespaceStripper} for more info.
+   */
+  public JSilverOptions setStripStructuralWhiteSpace(boolean value) {
+    this.stripStructuralWhiteSpace = value;
+    return this;
+  }
+
+  /**
+   * @see #setStripHtmlWhiteSpace(boolean)
+   */
+  public boolean getStripStructuralWhiteSpace() {
+    return stripStructuralWhiteSpace;
+  }
+
+  /**
+   * Use this method to disable wrapping the global HDF with an UnmodifiableData object which
+   * prevents modification. This flag is here in case there are corner cases or performance reasons
+   * that someone may want to disable this protection.
+   * 
+   * Should not be set to {@code true} unless incompatibilities or performance issues found. Note,
+   * that setting to {@code true} could introduce bugs in templates that acquire local references to
+   * the global data structure and then try to modify them, which is not the intended behavior.
+   * Allowing global data modification during rendering is not compatible with the recently fixed
+   * JNI Clearsilver library.
+   * 
+   * TODO: Remove once legacy mode is no longer needed.
+   * 
+   * @param allowGlobalDataModification {@code true} if you want to avoid wrapping the global HDF so
+   *        that all writes to it during rendering are prevented and throw an exception.
+   * @return this object.
+   */
+  public JSilverOptions setAllowGlobalDataModification(boolean allowGlobalDataModification) {
+    this.allowGlobalDataModification = allowGlobalDataModification;
+    return this;
+  }
+
+  /**
+   * @see #setAllowGlobalDataModification(boolean)
+   */
+  public boolean getAllowGlobalDataModification() {
+    return allowGlobalDataModification;
+  }
+
+  /**
+   * @param keepTemplateCacheFresh {@code true} to have the template cache call
+   *        {@link com.google.clearsilver.jsilver.resourceloader.ResourceLoader#getResourceVersionId(String)}
+   *        to check if it should refresh its cache entries (this incurs a small performance penalty
+   *        each time the cache is accessed)
+   * @return this object
+   */
+  public JSilverOptions setKeepTemplateCacheFresh(boolean keepTemplateCacheFresh) {
+    this.keepTemplateCacheFresh = keepTemplateCacheFresh;
+    return this;
+  }
+
+  /**
+   * @see #setKeepTemplateCacheFresh(boolean)
+   */
+  public boolean getKeepTemplateCacheFresh() {
+    return keepTemplateCacheFresh;
+  }
+
+  @Override
+  public JSilverOptions clone() {
+    try {
+      return (JSilverOptions) super.clone();
+    } catch (CloneNotSupportedException impossible) {
+      throw new AssertionError(impossible);
+    }
+  }
+
+  /**
+   * @see #setLogEscapedVariables
+   */
+  public boolean getLogEscapedVariables() {
+    return logEscapedVariables;
+  }
+
+  /**
+   * Use this method to enable logging of all variables whose values are modified by auto escaping
+   * or &lt;cs escape&gt; commands. These will be logged at {@code Level.WARNING}. This is useful
+   * for detecting variables that should be exempt from auto escaping.
+   * 
+   * <p>
+   * It is recommended to only enable this flag during testing or debugging and not for production
+   * jobs.
+   * 
+   * @see #setEscapeMode
+   */
+  public JSilverOptions setLogEscapedVariables(boolean logEscapedVariables) {
+    this.logEscapedVariables = logEscapedVariables;
+    return this;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/TemplateRenderer.java b/src/com/google/clearsilver/jsilver/TemplateRenderer.java
new file mode 100644
index 0000000..bcfacb5
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/TemplateRenderer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver;
+
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.exceptions.JSilverException;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.template.Template;
+
+import java.io.IOException;
+
+/**
+ * Renders a template.
+ */
+public interface TemplateRenderer {
+
+  /**
+   * Renders a given template and provided data, writing to an arbitrary output.
+   * 
+   * @param templateName Name of template to load (e.g. "things/blah.cs").
+   * @param data Data to be used in template.
+   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
+   *        System.out/err), StringBuffer/StringBuilder or anything that implements
+   *        java.io.Appendable
+   * @param resourceLoader ResourceLoader to use when reading in included files.
+   */
+  void render(String templateName, Data data, Appendable output, ResourceLoader resourceLoader)
+      throws IOException, JSilverException;
+
+  /**
+   * Same as {@link #render(String, Data, Appendable, ResourceLoader)}, except it uses the default
+   * ResourceLoader passed in to the JSilver constructor.
+   */
+  void render(String templateName, Data data, Appendable output) throws IOException,
+      JSilverException;
+
+  /**
+   * Same as {@link #render(String, Data, Appendable)}, except returns rendered template as a
+   * String.
+   */
+  String render(String templateName, Data data) throws IOException, JSilverException;
+
+  /**
+   * Renders a given template and provided data, writing to an arbitrary output.
+   * 
+   * @param template Template to render.
+   * @param data Data to be used in template.
+   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
+   *        System.out/err), StringBuffer/StringBuilder or anything that implements
+   *        java.io.Appendable.
+   * @param resourceLoader ResourceLoader to use when reading in included files.
+   * 
+   */
+  void render(Template template, Data data, Appendable output, ResourceLoader resourceLoader)
+      throws IOException, JSilverException;
+
+  /**
+   * Same as {@link #render(Template,Data,Appendable,ResourceLoader)}, except it uses the
+   * ResourceLoader passed into the JSilver constructor.
+   */
+  void render(Template template, Data data, Appendable output) throws IOException, JSilverException;
+
+  /**
+   * Same as {@link #render(Template,Data,Appendable)}, except returns rendered template as a
+   * String.
+   */
+  String render(Template template, Data data) throws IOException, JSilverException;
+
+  /**
+   * Renders a given template from the content passed in. That is, the first parameter is the actual
+   * template content rather than the filename to load.
+   * 
+   * @param content Content of template (e.g. "Hello &lt;cs var:name ?&gt;").
+   * @param data Data to be used in template.
+   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
+   *        System.out/err), StringBuffer/StringBuilder or anything that implements
+   *        java.io.Appendable
+   */
+  void renderFromContent(String content, Data data, Appendable output) throws IOException,
+      JSilverException;
+
+  /**
+   * Same as {@link #renderFromContent(String, Data, Appendable)}, except returns rendered template
+   * as a String.
+   */
+  String renderFromContent(String content, Data data) throws IOException, JSilverException;
+
+}
diff --git a/src/com/google/clearsilver/jsilver/adaptor/JCs.java b/src/com/google/clearsilver/jsilver/adaptor/JCs.java
new file mode 100644
index 0000000..e092a54
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/adaptor/JCs.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.adaptor;
+
+import com.google.clearsilver.jsilver.JSilver;
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.data.LocalAndGlobalData;
+import com.google.clearsilver.jsilver.exceptions.JSilverIOException;
+import com.google.clearsilver.jsilver.template.HtmlWhiteSpaceStripper;
+import com.google.clearsilver.jsilver.template.Template;
+
+import org.clearsilver.CS;
+import org.clearsilver.CSFileLoader;
+import org.clearsilver.HDF;
+
+import java.io.IOException;
+
+/**
+ * Adaptor that wraps a JSilver object so it can be used as an CS object.
+ */
+class JCs implements CS {
+
+  private final JHdf localHdf;
+  private JHdf globalHdf;
+  private final JSilver jSilver;
+  private final LoadPathToFileCache loadPathCache;
+  private Template template = null;
+  private CSFileLoader csFileLoader;
+  private ResourceLoaderAdaptor resourceLoaderAdaptor;
+
+  JCs(JHdf hdf, JSilver jSilver, LoadPathToFileCache loadPathCache) {
+    this.localHdf = hdf;
+    this.jSilver = jSilver;
+    this.loadPathCache = loadPathCache;
+
+    resourceLoaderAdaptor = localHdf.getResourceLoaderAdaptor();
+    csFileLoader = resourceLoaderAdaptor.getCSFileLoader();
+  }
+
+  /**
+   * Want to delay creating the JSilver object so we can specify necessary parameters.
+   */
+  private JSilver getJSilver() {
+    return jSilver;
+  }
+
+  @Override
+  public void setGlobalHDF(HDF global) {
+    globalHdf = JHdf.cast(global);
+  }
+
+  @Override
+  public HDF getGlobalHDF() {
+    return globalHdf;
+  }
+
+  @Override
+  public void close() {
+    // Removing unneeded reference, although this is not expected to have the
+    // performance impact seen in JHdf as in production configurations users
+    // should be using cached templates so they are long-lived.
+    template = null;
+  }
+
+  @Override
+  public void parseFile(String filename) throws IOException {
+    try {
+      if (getEscapeMode().isAutoEscapingMode()) {
+        if (localHdf.getData().getValue("Config.PropagateEscapeStatus") != null) {
+          throw new IllegalArgumentException(
+              "Config.PropagateEscapeStatus does not work with JSilver."
+                  + "Use JSilverOptions.setPropagateEscapeStatus instead");
+        }
+      }
+      template =
+          getJSilver().getTemplateLoader().load(filename, resourceLoaderAdaptor, getEscapeMode());
+    } catch (RuntimeException e) {
+      Throwable th = e;
+      if (th instanceof JSilverIOException) {
+        // JSilverIOException always has an IOException as its cause.
+        throw ((IOException) th.getCause());
+      }
+      throw e;
+    }
+  }
+
+  @Override
+  public void parseStr(String content) {
+    if (getEscapeMode().isAutoEscapingMode()) {
+      if (localHdf.getData().getValue("Config.PropagateEscapeStatus") != null) {
+        throw new IllegalArgumentException(
+            "Config.PropagateEscapeStatus does not work with JSilver."
+                + "Use JSilverOptions.setPropagateEscapeStatus instead");
+      }
+    }
+    template = getJSilver().getTemplateLoader().createTemp("parseStr", content, getEscapeMode());
+  }
+
+  private EscapeMode getEscapeMode() {
+    Data data = localHdf.getData();
+    return getJSilver().getEscapeMode(data);
+  }
+
+  @Override
+  public String render() {
+    if (template == null) {
+      throw new IllegalStateException("Call parseFile() or parseStr() before " + "render()");
+    }
+
+    Data data;
+    if (globalHdf != null) {
+      // For legacy support we allow users to pass in this option to disable
+      // the new modification protection for global HDF.
+      data =
+          new LocalAndGlobalData(localHdf.getData(), globalHdf.getData(), jSilver.getOptions()
+              .getAllowGlobalDataModification());
+    } else {
+      data = localHdf.getData();
+    }
+    Appendable buffer = jSilver.createAppendableBuffer();
+    try {
+      Appendable output = buffer;
+      // For Clearsilver compatibility we check this HDF variable to see if we
+      // need to turn on whitespace stripping. The preferred approach would be
+      // to turn it on in the JSilverOptions passed to JSilverFactory
+      int wsStripLevel = localHdf.getIntValue("ClearSilver.WhiteSpaceStrip", 0);
+      if (wsStripLevel > 0) {
+        output = new HtmlWhiteSpaceStripper(output, wsStripLevel);
+      }
+      jSilver.render(template, data, output, resourceLoaderAdaptor);
+      return output.toString();
+    } catch (IOException ioe) {
+      throw new RuntimeException(ioe);
+    } finally {
+      jSilver.releaseAppendableBuffer(buffer);
+    }
+  }
+
+  @Override
+  public CSFileLoader getFileLoader() {
+    return csFileLoader;
+  }
+
+  @Override
+  public void setFileLoader(CSFileLoader fileLoader) {
+    if (fileLoader == null && csFileLoader == null) {
+      return;
+    }
+    if (fileLoader != null && fileLoader.equals(csFileLoader)) {
+      return;
+    }
+    csFileLoader = fileLoader;
+    resourceLoaderAdaptor = new ResourceLoaderAdaptor(localHdf, loadPathCache, csFileLoader);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/adaptor/JHdf.java b/src/com/google/clearsilver/jsilver/adaptor/JHdf.java
new file mode 100644
index 0000000..16c3d04
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/adaptor/JHdf.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.adaptor;
+
+import com.google.clearsilver.jsilver.JSilverOptions;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.data.DataFactory;
+import com.google.clearsilver.jsilver.data.Parser;
+import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException;
+
+import org.clearsilver.CSFileLoader;
+import org.clearsilver.HDF;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Adaptor that wraps a JSilver Data object so it can be used as an HDF object.
+ */
+public class JHdf implements HDF {
+
+  // Only changed to null on close()
+  private Data data;
+  private final DataFactory dataFactory;
+  private final JSilverOptions options;
+
+  private final LoadPathToFileCache loadPathCache;
+  private ResourceLoaderAdaptor resourceLoader;
+
+
+  JHdf(Data data, DataFactory dataFactory, LoadPathToFileCache loadPathCache, JSilverOptions options) {
+    this.data = data;
+    this.loadPathCache = loadPathCache;
+    this.dataFactory = dataFactory;
+    this.options = options;
+    this.resourceLoader = new ResourceLoaderAdaptor(this, loadPathCache, null);
+  }
+
+  static JHdf cast(HDF hdf) {
+    if (!(hdf instanceof JHdf)) {
+      throw new IllegalArgumentException("HDF object not of type JHdf.  "
+          + "Make sure you use the same ClearsilverFactory to construct all "
+          + "related HDF and CS objects.");
+    }
+    return (JHdf) hdf;
+  }
+
+  Data getData() {
+    return data;
+  }
+
+  ResourceLoaderAdaptor getResourceLoaderAdaptor() {
+    return resourceLoader;
+  }
+
+  @Override
+  public void close() {
+    // This looks pointless but it actually reduces the lifetime of the large
+    // Data object as far as the garbage collector is concerned and
+    // dramatically improves performance.
+    data = null;
+  }
+
+  @Override
+  public boolean readFile(String filename) throws IOException {
+    dataFactory.loadData(filename, resourceLoader, data);
+    return false;
+  }
+
+  @Override
+  public CSFileLoader getFileLoader() {
+    return resourceLoader.getCSFileLoader();
+  }
+
+  @Override
+  public void setFileLoader(CSFileLoader fileLoader) {
+    this.resourceLoader = new ResourceLoaderAdaptor(this, loadPathCache, fileLoader);
+  }
+
+  @Override
+  public boolean writeFile(String filename) throws IOException {
+    FileWriter writer = new FileWriter(filename);
+    try {
+      data.write(writer, 2);
+    } finally {
+      writer.close();
+    }
+    return true;
+  }
+
+  @Override
+  public boolean readString(String content) {
+    Parser hdfParser = dataFactory.getParser();
+    try {
+      hdfParser.parse(new StringReader(content), data, new Parser.ErrorHandler() {
+        public void error(int line, String lineContent, String fileName, String errorMessage) {
+          throw new JSilverBadSyntaxException("HDF parsing error : '" + errorMessage + "'",
+              lineContent, fileName, line, JSilverBadSyntaxException.UNKNOWN_POSITION, null);
+        }
+      }, resourceLoader, null, options.getIgnoreAttributes());
+      return true;
+    } catch (IOException e) {
+      return false;
+    }
+  }
+
+  @Override
+  public int getIntValue(String hdfName, int defaultValue) {
+    return data.getIntValue(hdfName, defaultValue);
+  }
+
+  @Override
+  public String getValue(String hdfName, String defaultValue) {
+    return data.getValue(hdfName, defaultValue);
+  }
+
+  @Override
+  public void setValue(String hdfName, String value) {
+    data.setValue(hdfName, value);
+  }
+
+  @Override
+  public void removeTree(String hdfName) {
+    data.removeTree(hdfName);
+  }
+
+  @Override
+  public void setSymLink(String hdfNameSrc, String hdfNameDest) {
+    data.setSymlink(hdfNameSrc, hdfNameDest);
+  }
+
+  @Override
+  public void exportDate(String hdfName, TimeZone timeZone, Date date) {
+    throw new UnsupportedOperationException("TBD");
+  }
+
+  @Override
+  public void exportDate(String hdfName, String tz, int tt) {
+    throw new UnsupportedOperationException("TBD");
+  }
+
+  @Override
+  public HDF getObj(String hdfpath) {
+    Data d = data.getChild(hdfpath);
+    return d == null ? null : new JHdf(d, dataFactory, loadPathCache, options);
+  }
+
+  @Override
+  public HDF getChild(String hdfpath) {
+    Data d = data.getChild(hdfpath);
+    if (d == null) {
+      return null;
+    }
+    for (Data child : d.getChildren()) {
+      if (child.isFirstSibling()) {
+        return new JHdf(child, dataFactory, loadPathCache, options);
+      } else {
+        // The first child returned should be the first sibling. Throw an error
+        // if not.
+        throw new IllegalStateException("First child was not first sibling.");
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public HDF getRootObj() {
+    Data root = data.getRoot();
+    if (root == data) {
+      return this;
+    } else {
+      return new JHdf(root, dataFactory, loadPathCache, options);
+    }
+  }
+
+  @Override
+  public boolean belongsToSameRoot(HDF hdf) {
+    JHdf jHdf = cast(hdf);
+    return this.data.getRoot() == jHdf.data.getRoot();
+  }
+
+  @Override
+  public HDF getOrCreateObj(String hdfpath) {
+    return new JHdf(data.createChild(hdfpath), dataFactory, loadPathCache, options);
+  }
+
+  @Override
+  public String objName() {
+    return data.getName();
+  }
+
+  @Override
+  public String objValue() {
+    return data.getValue();
+  }
+
+  @Override
+  public HDF objChild() {
+    for (Data child : data.getChildren()) {
+      if (child.isFirstSibling()) {
+        return new JHdf(child, dataFactory, loadPathCache, options);
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public HDF objNext() {
+    Data next = data.getNextSibling();
+    return next == null ? null : new JHdf(next, dataFactory, loadPathCache, options);
+  }
+
+  @Override
+  public void copy(String hdfpath, HDF src) {
+    JHdf srcJHdf = cast(src);
+    if (hdfpath.equals("")) {
+      data.copy(srcJHdf.data);
+    } else {
+      data.copy(hdfpath, srcJHdf.data);
+    }
+  }
+
+  @Override
+  public String dump() {
+    StringBuilder sb = new StringBuilder();
+    try {
+      data.write(sb, 0);
+      return sb.toString();
+    } catch (IOException e) {
+      return null;
+    }
+  }
+
+  @Override
+  public String writeString() {
+    return dump();
+  }
+
+  @Override
+  public String toString() {
+    return dump();
+  }
+
+  /**
+   * JSilver-specific method that optimizes the underlying data object. Should only be used on
+   * long-lived HDF objects (e.g. global HDF).
+   */
+  public void optimize() {
+    data.optimize();
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/adaptor/JSilverFactory.java b/src/com/google/clearsilver/jsilver/adaptor/JSilverFactory.java
new file mode 100644
index 0000000..9604264
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/adaptor/JSilverFactory.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.adaptor;
+
+import com.google.clearsilver.jsilver.JSilver;
+import com.google.clearsilver.jsilver.JSilverOptions;
+import com.google.clearsilver.jsilver.data.DataFactory;
+import com.google.clearsilver.jsilver.data.DefaultData;
+import com.google.clearsilver.jsilver.data.HDFDataFactory;
+
+import org.clearsilver.ClearsilverFactory;
+import org.clearsilver.DelegatedHdf;
+import org.clearsilver.HDF;
+
+/**
+ * ClearsilverFactory that adapts JSilver for use as any HDF/CS rendering engine.
+ */
+public class JSilverFactory implements ClearsilverFactory {
+
+  private static final JSilverOptions DEFAULT_OPTIONS = new JSilverOptions();
+
+  private final boolean unwrapDelegatedHdfs;
+  private final JSilver jSilver;
+  private final JSilverOptions options;
+  private final DataFactory dataFactory;
+
+  private final LoadPathToFileCache loadPathCache;
+
+  /**
+   * Default constructor. enables unwrapping of DelegatedHdfs.
+   */
+  public JSilverFactory() {
+    this(DEFAULT_OPTIONS);
+  }
+
+  public JSilverFactory(JSilverOptions options) {
+    this(options, true);
+  }
+
+  public JSilverFactory(JSilverOptions options, boolean unwrapDelegatedHdfs) {
+    this(new JSilver(null, options), unwrapDelegatedHdfs);
+  }
+
+  /**
+   * This constructor is available for those who already use JSilver and want to use the same
+   * attributes and caches for their Java Clearsilver Framework code. Users who use only JCF should
+   * use a different constructor.
+   * 
+   * @param jSilver existing instance of JSilver to use for parsing and rendering.
+   * @param unwrapDelegatedHdfs whether to unwrap DelegetedHdfs or not before casting.
+   */
+  public JSilverFactory(JSilver jSilver, boolean unwrapDelegatedHdfs) {
+    this.unwrapDelegatedHdfs = unwrapDelegatedHdfs;
+    this.jSilver = jSilver;
+    this.options = jSilver.getOptions();
+    if (this.options.getLoadPathCacheSize() == 0) {
+      this.loadPathCache = null;
+    } else {
+      this.loadPathCache = new LoadPathToFileCache(this.options.getLoadPathCacheSize());
+    }
+    this.dataFactory =
+        new HDFDataFactory(options.getIgnoreAttributes(), options.getStringInternStrategy());
+  }
+
+  @Override
+  public JCs newCs(HDF hdf) {
+    if (unwrapDelegatedHdfs) {
+      hdf = DelegatedHdf.getFullyUnwrappedHdf(hdf);
+    }
+    return new JCs(JHdf.cast(hdf), jSilver, loadPathCache);
+  }
+
+  @Override
+  public JCs newCs(HDF hdf, HDF globalHdf) {
+    if (unwrapDelegatedHdfs) {
+      hdf = DelegatedHdf.getFullyUnwrappedHdf(hdf);
+      globalHdf = DelegatedHdf.getFullyUnwrappedHdf(globalHdf);
+    }
+    JCs cs = new JCs(JHdf.cast(hdf), jSilver, loadPathCache);
+    cs.setGlobalHDF(globalHdf);
+    return cs;
+  }
+
+  @Override
+  public JHdf newHdf() {
+    return new JHdf(new DefaultData(), dataFactory, loadPathCache, options);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/adaptor/LoadPathToFileCache.java b/src/com/google/clearsilver/jsilver/adaptor/LoadPathToFileCache.java
new file mode 100644
index 0000000..84e8a58
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/adaptor/LoadPathToFileCache.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.adaptor;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * This class implements a cache of a list of loadpaths and a file name to the absolute file name
+ * where the file is located on the filesystem. The purpose is to avoid filesystem calls for common
+ * operations, like in which of these directories does this file exist? This class is threadsafe.
+ * 
+ * Some of this code is copied from {@link com.google.clearsilver.base.CSFileCache}.
+ */
+public class LoadPathToFileCache {
+
+  private final LRUCache<String, String> cache;
+  private final ReadWriteLock cacheLock = new ReentrantReadWriteLock();
+
+  public LoadPathToFileCache(int capacity) {
+    cache = new LRUCache<String, String>(capacity);
+  }
+
+  /**
+   * Lookup in the cache to see if we have a mapping from the given loadpaths and filename to an
+   * absolute file path.
+   * 
+   * @param loadPaths the ordered list of directories to search for the file.
+   * @param filename the name of the file.
+   * @return the absolute filepath location of the file, or {@code null} if not in the cache.
+   */
+  public String lookup(List<String> loadPaths, String filename) {
+    String filePathMapKey = makeCacheKey(loadPaths, filename);
+    cacheLock.readLock().lock();
+    try {
+      return cache.get(filePathMapKey);
+    } finally {
+      cacheLock.readLock().unlock();
+    }
+  }
+
+  /**
+   * Add a new mapping to the cache.
+   * 
+   * @param loadPaths the ordered list of directories to search for the file.
+   * @param filename the name of the file.
+   * @param filePath the absolute filepath location of the file
+   */
+  public void add(List<String> loadPaths, String filename, String filePath) {
+    String filePathMapKey = makeCacheKey(loadPaths, filename);
+    cacheLock.writeLock().lock();
+    try {
+      cache.put(filePathMapKey, filePath);
+    } finally {
+      cacheLock.writeLock().unlock();
+    }
+  }
+
+  public static String makeCacheKey(List<String> loadPaths, String filename) {
+    if (loadPaths == null) {
+      throw new NullPointerException("Loadpaths cannot be null");
+    }
+    if (filename == null) {
+      throw new NullPointerException("Filename cannot be null");
+    }
+    String loadPathsHash = Long.toHexString(hashLoadPath(loadPaths));
+    StringBuilder sb = new StringBuilder(loadPathsHash);
+    sb.append('|').append(filename);
+    return sb.toString();
+  }
+
+  /**
+   * Generate a hashCode to represent the ordered list of loadpaths. Used as a key into the fileMap
+   * since a concatenation of the loadpaths is likely to be huge (greater than 1K) but very
+   * repetitive. Algorithm comes from Effective Java by Joshua Bloch.
+   * <p>
+   * We don't use the builtin hashCode because it is only 32-bits, and while the expect different
+   * values of loadpaths is very small, we want to minimize any chance of collision since we use the
+   * hash as the key and throw away the loadpath list
+   * 
+   * @param list an ordered list of loadpaths.
+   * @return a long representing a hash of the loadpaths.
+   */
+  static long hashLoadPath(List<String> list) {
+    long hash = 17;
+    for (String path : list) {
+      hash = 37 * hash + path.hashCode();
+    }
+    return hash;
+  }
+
+  /**
+   * This code is copied from {@link com.google.common.cache.LRUCache} but is distilled to basics in
+   * order to not require importing Google code. Hopefully there is an open-source implementation,
+   * although given the design of LinkHashMap, this is trivial.
+   */
+  static class LRUCache<K, V> extends LinkedHashMap<K, V> {
+
+    private final int capacity;
+
+    LRUCache(int capacity) {
+      super(capacity, 0.75f, true);
+      this.capacity = capacity;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Necessary to override because HashMap increases the capacity of the hashtable before
+     * inserting the elements. However, here we have set the max capacity already and will instead
+     * remove eldest elements instead of increasing capacity.
+     */
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+      for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
+        put(e.getKey(), e.getValue());
+      }
+    }
+
+    /**
+     * This method is called by LinkedHashMap to check whether the eldest entry should be removed.
+     * 
+     * @param eldest
+     * @return true if element should be removed.
+     */
+    @Override
+    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+      return size() > capacity;
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/adaptor/ResourceLoaderAdaptor.java b/src/com/google/clearsilver/jsilver/adaptor/ResourceLoaderAdaptor.java
new file mode 100644
index 0000000..8fcf211
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/adaptor/ResourceLoaderAdaptor.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.adaptor;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+
+import org.clearsilver.CSFileLoader;
+import org.clearsilver.CSUtil;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.List;
+
+/**
+ * Wrap a CSFileLoader with a ResourceLoader
+ */
+public class ResourceLoaderAdaptor implements ResourceLoader {
+
+  private final JHdf hdf;
+  private final LoadPathToFileCache loadPathCache;
+  private final CSFileLoader csFileLoader;
+  private List<String> loadPaths;
+
+  ResourceLoaderAdaptor(JHdf hdf, LoadPathToFileCache loadPathCache, CSFileLoader csFileLoader) {
+    this.hdf = hdf;
+    this.loadPathCache = loadPathCache;
+    this.csFileLoader = csFileLoader;
+  }
+
+  @Override
+  public Reader open(String name) throws IOException {
+    if (csFileLoader != null) {
+      if (hdf.getData() == null) {
+        throw new IllegalStateException("HDF is already closed");
+      }
+      return new StringReader(csFileLoader.load(hdf, name));
+    } else {
+      File file = locateFile(name);
+      if (file == null) {
+        throw new FileNotFoundException("Could not locate file " + name);
+      }
+      return new InputStreamReader(new FileInputStream(file), "UTF-8");
+    }
+  }
+
+  @Override
+  public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException {
+    Reader reader = open(name);
+    if (reader == null) {
+      final StringBuffer text = new StringBuffer();
+      text.append("No file '");
+      text.append(name);
+      text.append("' ");
+      if (loadPaths == null || loadPaths.isEmpty()) {
+        text.append("with no load paths");
+      } else if (loadPaths.size() == 1) {
+        text.append("inside directory '");
+        text.append(loadPaths.get(0));
+        text.append("'");
+      } else {
+        text.append("inside directories ( ");
+        for (String path : getLoadPaths()) {
+          text.append("'");
+          text.append(path);
+          text.append("' ");
+        }
+        text.append(")");
+      }
+      throw new JSilverTemplateNotFoundException(text.toString());
+    } else {
+      return reader;
+    }
+  }
+
+  /**
+   * 
+   * @param name name of the file to locate.
+   * @return a File object corresponding to the existing file or {@code null} if it does not exist.
+   */
+  File locateFile(String name) {
+    if (name.startsWith(File.separator)) {
+      // Full path to file was given.
+      File file = newFile(name);
+      return file.exists() ? file : null;
+    }
+    File file = null;
+    // loadPathCache is null when load path caching is disabled at the
+    // JSilverFactory level. This is implied by setting cache size
+    // to 0 using JSilverOptions.setLoadPathCacheSize(0).
+    if (loadPathCache != null) {
+      String filePath = loadPathCache.lookup(getLoadPaths(), name);
+      if (filePath != null) {
+        file = newFile(filePath);
+        return file.exists() ? file : null;
+      }
+    }
+
+    file = locateFile(getLoadPaths(), name);
+    if (file != null && loadPathCache != null) {
+      loadPathCache.add(getLoadPaths(), name, file.getAbsolutePath());
+    }
+    return file;
+  }
+
+  /**
+   * Given an ordered list of directories to look in, locate the specified file. Returns
+   * <code>null</code> if file not found.
+   * <p>
+   * This is copied from {@link org.clearsilver.CSUtil#locateFile(java.util.List, String)} but has
+   * one important difference. It calls our subclassable newFile method.
+   * 
+   * @param loadPaths the ordered list of paths to search.
+   * @param filename the name of the file.
+   * @return a File object corresponding to the file. <code>null</code> if file not found.
+   */
+  File locateFile(List<String> loadPaths, String filename) {
+    if (filename == null) {
+      throw new NullPointerException("No filename provided");
+    }
+    if (loadPaths == null) {
+      throw new NullPointerException("No loadpaths provided.");
+    }
+    for (String path : loadPaths) {
+      File file = newFile(path, filename);
+      if (file.exists()) {
+        return file;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Separate methods to allow tests to subclass and override File creation and return mocks or
+   * fakes.
+   */
+  File newFile(String filename) {
+    return new File(filename);
+  }
+
+  File newFile(String path, String filename) {
+    return new File(path, filename);
+  }
+
+  @Override
+  public void close(Reader reader) throws IOException {
+    reader.close();
+  }
+
+  @Override
+  public Object getKey(String filename) {
+    if (filename.startsWith(File.separator)) {
+      return filename;
+    } else {
+      File file = locateFile(filename);
+      if (file == null) {
+        // The file does not exist, use the full loadpath and file name as the
+        // key.
+        return LoadPathToFileCache.makeCacheKey(getLoadPaths(), filename);
+      } else {
+        return file.getAbsolutePath();
+      }
+    }
+  }
+
+  /**
+   * Some applications, e.g. online help, need to know when a file has changed due to a symlink
+   * modification hence the use of {@link File#getCanonicalFile()}, if possible.
+   */
+  @Override
+  public Object getResourceVersionId(String filename) {
+    File file = locateFile(filename);
+    if (file == null) {
+      return null;
+    }
+
+    String fullPath;
+    try {
+      fullPath = file.getCanonicalPath();
+    } catch (IOException e) {
+      fullPath = file.getAbsolutePath();
+    }
+    return String.format("%s@%s", fullPath, file.lastModified());
+  }
+
+  final CSFileLoader getCSFileLoader() {
+    return csFileLoader;
+  }
+
+  private synchronized List<String> getLoadPaths() {
+    if (loadPaths == null) {
+      if (hdf.getData() == null) {
+        throw new IllegalStateException("HDF is already closed");
+      }
+      loadPaths = CSUtil.getLoadPaths(hdf, true);
+    }
+    return loadPaths;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/autoescape/AutoEscapeContext.java b/src/com/google/clearsilver/jsilver/autoescape/AutoEscapeContext.java
new file mode 100644
index 0000000..7adf161
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/autoescape/AutoEscapeContext.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.autoescape;
+
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR_CSS;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR_JS;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR_UNQUOTED_JS;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR_URI;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_ATTR_URI_START;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_HTML;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_JS;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_JS_UNQUOTED;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_STYLE;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR_CSS;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR_JS;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR_UNQUOTED_JS;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR_URI;
+import static com.google.clearsilver.jsilver.autoescape.EscapeMode.ESCAPE_AUTO_UNQUOTED_ATTR_URI_START;
+import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException;
+import com.google.streamhtmlparser.ExternalState;
+import com.google.streamhtmlparser.HtmlParser;
+import com.google.streamhtmlparser.HtmlParserFactory;
+import com.google.streamhtmlparser.ParseException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+/**
+ * Encapsulates auto escaping logic.
+ */
+public class AutoEscapeContext {
+  /**
+   * Map of content-type to corresponding {@code HtmlParser.Mode}, used by {@code setContentType} to
+   * specify the content type of provided input. Valid values and the corresponding mode are: <br>
+   * <table>
+   * <tr>
+   * <td>text/html</td>
+   * <td>HtmlParser.Mode.HTML</td>
+   * </tr>
+   * <tr>
+   * <td>text/plain</td>
+   * <td>HtmlParser.Mode.HTML</td>
+   * </tr>
+   * <tr>
+   * <td>application/javascript</td>
+   * <td>HtmlParser.Mode.JS</td>
+   * </tr>
+   * <tr>
+   * <td>application/json</td>
+   * <td>HtmlParser.Mode.JS</td>
+   * </tr>
+   * <tr>
+   * <td>text/javascript</td>
+   * <td>HtmlParser.Mode.JS</td>
+   * </tr>
+   * <tr>
+   * <td>text/css</td>
+   * <td>HtmlParser.Mode.CSS</td>
+   * </tr>
+   * </table>
+   * 
+   * @see #setContentType
+   */
+  public static final Map<String, HtmlParser.Mode> CONTENT_TYPE_LIST;
+
+  // These options are used to provide extra information to HtmlParserFactory.createParserInMode or
+  // HtmlParserFactory.createParserInAttribute, which is required for certain modes.
+  private static final HashSet<HtmlParserFactory.AttributeOptions> quotedJsAttributeOption;
+  private static final HashSet<HtmlParserFactory.AttributeOptions> partialUrlAttributeOption;
+  private static final HashSet<HtmlParserFactory.ModeOptions> jsModeOption;
+
+  private HtmlParser htmlParser;
+
+  static {
+    quotedJsAttributeOption = new HashSet<HtmlParserFactory.AttributeOptions>();
+    quotedJsAttributeOption.add(HtmlParserFactory.AttributeOptions.JS_QUOTED);
+
+    partialUrlAttributeOption = new HashSet<HtmlParserFactory.AttributeOptions>();
+    partialUrlAttributeOption.add(HtmlParserFactory.AttributeOptions.URL_PARTIAL);
+
+    jsModeOption = new HashSet<HtmlParserFactory.ModeOptions>();
+    jsModeOption.add(HtmlParserFactory.ModeOptions.JS_QUOTED);
+
+    CONTENT_TYPE_LIST = new HashMap<String, HtmlParser.Mode>();
+    CONTENT_TYPE_LIST.put("text/html", HtmlParser.Mode.HTML);
+    CONTENT_TYPE_LIST.put("text/plain", HtmlParser.Mode.HTML);
+    CONTENT_TYPE_LIST.put("application/javascript", HtmlParser.Mode.JS);
+    CONTENT_TYPE_LIST.put("application/json", HtmlParser.Mode.JS);
+    CONTENT_TYPE_LIST.put("text/javascript", HtmlParser.Mode.JS);
+    CONTENT_TYPE_LIST.put("text/css", HtmlParser.Mode.CSS);
+  }
+
+  /**
+   * Name of resource being auto escaped. Will be used in error and display messages.
+   */
+  private String resourceName;
+
+  public AutoEscapeContext() {
+    this(EscapeMode.ESCAPE_AUTO, null);
+  }
+
+  /**
+   * Create a new context in the state represented by mode.
+   * 
+   * @param mode EscapeMode object.
+   */
+  public AutoEscapeContext(EscapeMode mode) {
+    this(mode, null);
+  }
+
+  /**
+   * Create a new context in the state represented by mode. If a non-null resourceName is provided,
+   * it will be used in displaying error messages.
+   * 
+   * @param mode The initial EscapeMode for this context
+   * @param resourceName Name of the resource being auto escaped.
+   */
+  public AutoEscapeContext(EscapeMode mode, String resourceName) {
+    this.resourceName = resourceName;
+    htmlParser = createHtmlParser(mode);
+  }
+
+  /**
+   * Create a new context that is a copy of the current state of this context.
+   * 
+   * @return New {@code AutoEscapeContext} that is a snapshot of the current state of this context.
+   */
+  public AutoEscapeContext cloneCurrentEscapeContext() {
+    AutoEscapeContext autoEscapeContext = new AutoEscapeContext();
+    autoEscapeContext.resourceName = resourceName;
+    autoEscapeContext.htmlParser = HtmlParserFactory.createParser(htmlParser);
+    return autoEscapeContext;
+  }
+
+  /**
+   * Sets the current position in the resource being auto escaped. Useful for generating detailed
+   * error messages.
+   * 
+   * @param line line number.
+   * @param column column number within line.
+   */
+  public void setCurrentPosition(int line, int column) {
+    htmlParser.setLineNumber(line);
+    htmlParser.setColumnNumber(column);
+  }
+
+  /**
+   * Returns the name of the resource currently being auto escaped.
+   */
+  public String getResourceName() {
+    return resourceName;
+  }
+
+  /**
+   * Returns the current line number within the resource being auto escaped.
+   */
+  public int getLineNumber() {
+    return htmlParser.getLineNumber();
+  }
+
+  /**
+   * Returns the current column number within the resource being auto escaped.
+   */
+  public int getColumnNumber() {
+    return htmlParser.getColumnNumber();
+  }
+
+  private HtmlParser createHtmlParser(EscapeMode mode) {
+    switch (mode) {
+      case ESCAPE_AUTO:
+      case ESCAPE_AUTO_HTML:
+        return HtmlParserFactory.createParser();
+
+      case ESCAPE_AUTO_JS_UNQUOTED:
+        // <script>START HERE
+        return HtmlParserFactory.createParserInMode(HtmlParser.Mode.JS, null);
+
+      case ESCAPE_AUTO_JS:
+        // <script> var a = 'START HERE
+        return HtmlParserFactory.createParserInMode(HtmlParser.Mode.JS, jsModeOption);
+
+      case ESCAPE_AUTO_STYLE:
+        // <style>START HERE
+        return HtmlParserFactory.createParserInMode(HtmlParser.Mode.CSS, null);
+
+      case ESCAPE_AUTO_ATTR:
+        // <input text="START HERE
+        return HtmlParserFactory.createParserInAttribute(HtmlParser.ATTR_TYPE.REGULAR, true, null);
+
+      case ESCAPE_AUTO_UNQUOTED_ATTR:
+        // <input text=START HERE
+        return HtmlParserFactory.createParserInAttribute(HtmlParser.ATTR_TYPE.REGULAR, false, null);
+
+      case ESCAPE_AUTO_ATTR_URI:
+        // <a href="http://www.google.com/a?START HERE
+        return HtmlParserFactory.createParserInAttribute(HtmlParser.ATTR_TYPE.URI, true,
+            partialUrlAttributeOption);
+
+      case ESCAPE_AUTO_UNQUOTED_ATTR_URI:
+        // <a href=http://www.google.com/a?START HERE
+        return HtmlParserFactory.createParserInAttribute(HtmlParser.ATTR_TYPE.URI, false,
+            partialUrlAttributeOption);
+
+      case ESCAPE_AUTO_ATTR_URI_START:
+        // <a href="START HERE
+        return HtmlParserFactory.createParserInAttribute(HtmlParser.ATTR_TYPE.URI, true, null);
+
+      case ESCAPE_AUTO_UNQUOTED_ATTR_URI_START:
+        // <a href=START HERE
+        return HtmlParserFactory.createParserInAttribute(HtmlParser.ATTR_TYPE.URI, false, null);
+
+      case ESCAPE_AUTO_ATTR_JS:
+        // <input onclick="doClick('START HERE
+        return HtmlParserFactory.createParserInAttribute(HtmlParser.ATTR_TYPE.JS, true,
+            quotedJsAttributeOption);
+
+      case ESCAPE_AUTO_ATTR_UNQUOTED_JS:
+        // <input onclick="doClick(START HERE
+        return HtmlParserFactory.createParserInAttribute(HtmlParser.ATTR_TYPE.JS, true, null);
+
+      case ESCAPE_AUTO_UNQUOTED_ATTR_JS:
+        // <input onclick=doClick('START HERE
+        throw new JSilverAutoEscapingException(
+            "Attempting to start HTML parser in unsupported mode" + mode, resourceName);
+
+      case ESCAPE_AUTO_UNQUOTED_ATTR_UNQUOTED_JS:
+        // <input onclick=doClick(START HERE
+        return HtmlParserFactory.createParserInAttribute(HtmlParser.ATTR_TYPE.JS, false, null);
+
+      case ESCAPE_AUTO_ATTR_CSS:
+        // <input style="START HERE
+        return HtmlParserFactory.createParserInAttribute(HtmlParser.ATTR_TYPE.STYLE, true, null);
+
+      case ESCAPE_AUTO_UNQUOTED_ATTR_CSS:
+        // <input style=START HERE
+        return HtmlParserFactory.createParserInAttribute(HtmlParser.ATTR_TYPE.STYLE, false, null);
+
+      default:
+        throw new JSilverAutoEscapingException("Attempting to start HTML parser in invalid mode"
+            + mode, resourceName);
+    }
+  }
+
+  /**
+   * Parse the given data and update internal state accordingly.
+   * 
+   * @param data Input to parse, usually the contents of a template.
+   */
+  public void parseData(String data) {
+    try {
+      htmlParser.parse(data);
+    } catch (ParseException e) {
+      // ParseException displays the proper position, so do not store line and column
+      // number here.
+      throw new JSilverAutoEscapingException("Error in HtmlParser: " + e, resourceName);
+    }
+  }
+
+  /**
+   * Lets the AutoEscapeContext know that some input was skipped.
+   * 
+   * This method will usually be called for variables in the input stream. The AutoEscapeContext is
+   * told that the input stream contained some additional data but does not get to see the data. It
+   * can adjust its internal state accordingly.
+   */
+  public void insertText() {
+    try {
+      htmlParser.insertText();
+    } catch (ParseException e) {
+      throw new JSilverAutoEscapingException("Error during insertText(): " + e, resourceName,
+          htmlParser.getLineNumber(), htmlParser.getColumnNumber());
+    }
+  }
+
+  /**
+   * Determines whether an included template that begins in state {@code start} is allowed to end in
+   * state {@code end}. Usually included templates are only allowed to end in the same context they
+   * begin in. This lets auto escaping parse the remainder of the parent template without needing to
+   * know the ending context of the included template. However, there is one exception where auto
+   * escaping will allow a different ending context: if the included template is a URI attribute
+   * value, it is allowed to change context from {@code ATTR_URI_START} to {@code ATTR_URI}. This
+   * does not cause any issues because the including template will call {@code insertText} when it
+   * encounters the include command, and {@code insertText} will cause the HTML parser to switch its
+   * internal state in the same way.
+   */
+  public boolean isPermittedStateChangeForIncludes(AutoEscapeState start, AutoEscapeState end) {
+    return start.equals(end)
+        || (start.equals(AutoEscapeState.ATTR_URI_START) && end.equals(AutoEscapeState.ATTR_URI))
+        || (start.equals(AutoEscapeState.UNQUOTED_ATTR_URI_START) && end
+            .equals(AutoEscapeState.UNQUOTED_ATTR_URI));
+  }
+
+  /**
+   * Determine the correct escaping to apply for a variable.
+   * 
+   * Looks at the current state of the htmlParser, and determines what escaping to apply to a
+   * variable in this state.
+   * 
+   * @return Name of escaping function to use in this state.
+   */
+  public String getEscapingFunctionForCurrentState() {
+    return getCurrentState().getFunctionName();
+  }
+
+  /**
+   * Returns the EscapeMode which will bring AutoEscapeContext into this state.
+   * 
+   * Initializing a new AutoEscapeContext with this EscapeMode will bring it into the state that the
+   * current AutoEscapeContext object is in.
+   * 
+   * @return An EscapeMode object.
+   */
+  public EscapeMode getEscapeModeForCurrentState() {
+    return getCurrentState().getEscapeMode();
+  }
+
+  /**
+   * Calls the HtmlParser API to determine current state.
+   * 
+   * This function is mostly a wrapper around the HtmlParser API. It gathers all the necessary
+   * information using that API and returns a single enum representing the current state.
+   * 
+   * @return AutoEscapeState enum representing the current state.
+   */
+  public AutoEscapeState getCurrentState() {
+    ExternalState state = htmlParser.getState();
+    String tag = htmlParser.getTag();
+
+    // Currently we do not do any escaping inside CSS blocks, so ignore them.
+    if (state.equals(HtmlParser.STATE_CSS_FILE) || tag.equals("style")) {
+
+      return AutoEscapeState.STYLE;
+    }
+
+    // Handle variables inside <script> tags.
+    if (htmlParser.inJavascript() && !state.equals(HtmlParser.STATE_VALUE)) {
+      if (htmlParser.isJavascriptQuoted()) {
+        // <script> var a = "<?cs var: Blah ?>"; </script>
+        return AutoEscapeState.JS;
+      } else {
+        // <script> var a = <?cs var: Blah ?>; </script>
+        // No quotes around the variable, hence it can inject arbitrary javascript.
+        // So severely restrict the values it may contain.
+        return AutoEscapeState.JS_UNQUOTED;
+      }
+    }
+
+    // Inside an HTML tag or attribute name
+    if (state.equals(HtmlParser.STATE_ATTR) || state.equals(HtmlParser.STATE_TAG)) {
+      return AutoEscapeState.ATTR;
+      // TODO: Need a strict validation function for tag and attribute names.
+    } else if (state.equals(HtmlParser.STATE_VALUE)) {
+      // Inside an HTML attribute value
+      return getCurrentAttributeState();
+    } else if (state.equals(HtmlParser.STATE_COMMENT) || state.equals(HtmlParser.STATE_TEXT)) {
+      // Default is assumed to be HTML body
+      // <b>Hello <?cs var: UserName ?></b> :
+      return AutoEscapeState.HTML;
+    }
+
+    throw new JSilverAutoEscapingException("Invalid state received from HtmlParser: "
+        + state.toString(), resourceName, htmlParser.getLineNumber(), htmlParser.getColumnNumber());
+  }
+
+  private AutoEscapeState getCurrentAttributeState() {
+    HtmlParser.ATTR_TYPE type = htmlParser.getAttributeType();
+    boolean attrQuoted = htmlParser.isAttributeQuoted();
+
+    switch (type) {
+      case REGULAR:
+        // <input value="<?cs var: Blah ?>"> :
+        if (attrQuoted) {
+          return AutoEscapeState.ATTR;
+        } else {
+          return AutoEscapeState.UNQUOTED_ATTR;
+        }
+
+      case URI:
+        if (htmlParser.isUrlStart()) {
+          // <a href="<?cs var: X ?>">
+          if (attrQuoted) {
+            return AutoEscapeState.ATTR_URI_START;
+          } else {
+            return AutoEscapeState.UNQUOTED_ATTR_URI_START;
+          }
+        } else {
+          // <a href="http://www.google.com/a?x=<?cs var: X ?>">
+          if (attrQuoted) {
+            // TODO: Html escaping because that is what Clearsilver does right now.
+            // May change this to url escaping soon.
+            return AutoEscapeState.ATTR_URI;
+          } else {
+            return AutoEscapeState.UNQUOTED_ATTR_URI;
+          }
+        }
+
+      case JS:
+        if (htmlParser.isJavascriptQuoted()) {
+          /*
+           * Note: js_escape() hex encodes all html metacharacters. Therefore it is safe to not do
+           * an HTML escape around this.
+           */
+          if (attrQuoted) {
+            // <input onclick="alert('<?cs var:Blah ?>');">
+            return AutoEscapeState.ATTR_JS;
+          } else {
+            // <input onclick=alert('<?cs var: Blah ?>');>
+            return AutoEscapeState.UNQUOTED_ATTR_JS;
+          }
+        } else {
+          if (attrQuoted) {
+            /* <input onclick="alert(<?cs var:Blah ?>);"> */
+            return AutoEscapeState.ATTR_UNQUOTED_JS;
+          } else {
+
+            /* <input onclick=alert(<?cs var:Blah ?>);> */
+            return AutoEscapeState.UNQUOTED_ATTR_UNQUOTED_JS;
+          }
+        }
+
+      case STYLE:
+        // <input style="border:<?cs var: FancyBorder ?>"> :
+        if (attrQuoted) {
+          return AutoEscapeState.ATTR_CSS;
+        } else {
+          return AutoEscapeState.UNQUOTED_ATTR_CSS;
+        }
+
+      default:
+        throw new JSilverAutoEscapingException("Invalid attribute type in HtmlParser: " + type,
+            resourceName, htmlParser.getLineNumber(), htmlParser.getColumnNumber());
+    }
+  }
+
+  /**
+   * Resets the state of the underlying html parser to a state consistent with the {@code
+   * contentType} provided. This method should be used when the starting auto escaping context of a
+   * resource cannot be determined from its contents - for example, a CSS stylesheet or a javascript
+   * source file.
+   * 
+   * @param contentType MIME type header representing the content being parsed.
+   * @see #CONTENT_TYPE_LIST
+   */
+  public void setContentType(String contentType) {
+    HtmlParser.Mode mode = CONTENT_TYPE_LIST.get(contentType);
+    if (mode == null) {
+      throw new JSilverAutoEscapingException("Invalid content type specified: " + contentType,
+          resourceName, htmlParser.getLineNumber(), htmlParser.getColumnNumber());
+
+    }
+    htmlParser.resetMode(mode);
+  }
+
+  /**
+   * Enum representing states of the data being parsed.
+   * 
+   * This enumeration lists all the states in which autoescaping would have some effect.
+   * 
+   */
+  public static enum AutoEscapeState {
+    HTML("html", ESCAPE_AUTO_HTML), JS("js", ESCAPE_AUTO_JS), STYLE("css", ESCAPE_AUTO_STYLE), JS_UNQUOTED(
+        "js_check_number", ESCAPE_AUTO_JS_UNQUOTED), ATTR("html", ESCAPE_AUTO_ATTR), UNQUOTED_ATTR(
+        "html_unquoted", ESCAPE_AUTO_UNQUOTED_ATTR), ATTR_URI("html", ESCAPE_AUTO_ATTR_URI), UNQUOTED_ATTR_URI(
+        "html_unquoted", ESCAPE_AUTO_UNQUOTED_ATTR_URI), ATTR_URI_START("url_validate",
+        ESCAPE_AUTO_ATTR_URI_START), UNQUOTED_ATTR_URI_START("url_validate_unquoted",
+        ESCAPE_AUTO_UNQUOTED_ATTR_URI_START), ATTR_JS("js", ESCAPE_AUTO_ATTR_JS), ATTR_UNQUOTED_JS(
+        "js_check_number", ESCAPE_AUTO_ATTR_UNQUOTED_JS), UNQUOTED_ATTR_JS("js_attr_unquoted",
+        ESCAPE_AUTO_UNQUOTED_ATTR_JS), UNQUOTED_ATTR_UNQUOTED_JS("js_check_number",
+        ESCAPE_AUTO_UNQUOTED_ATTR_UNQUOTED_JS), ATTR_CSS("css", ESCAPE_AUTO_ATTR_CSS), UNQUOTED_ATTR_CSS(
+        "css_unquoted", ESCAPE_AUTO_UNQUOTED_ATTR_CSS);
+
+    private final String functionName;
+    private final EscapeMode escapeMode;
+
+    private AutoEscapeState(String functionName, EscapeMode mode) {
+      this.functionName = functionName;
+      this.escapeMode = mode;
+    }
+
+    public String getFunctionName() {
+      return functionName;
+    }
+
+    public EscapeMode getEscapeMode() {
+      return escapeMode;
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/autoescape/AutoEscapeOptions.java b/src/com/google/clearsilver/jsilver/autoescape/AutoEscapeOptions.java
new file mode 100644
index 0000000..a038411
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/autoescape/AutoEscapeOptions.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.autoescape;
+
+/**
+ * Global configuration options specific to <a href="http://go/autoescapecs">auto escaping</a>.
+ */
+public class AutoEscapeOptions {
+
+  private boolean propagateEscapeStatus = false;
+  private boolean logEscapedVariables = false;
+
+  public boolean getLogEscapedVariables() {
+    return logEscapedVariables;
+  }
+
+  public void setLogEscapedVariables(boolean logEscapedVariables) {
+    this.logEscapedVariables = logEscapedVariables;
+  }
+
+  public boolean getPropagateEscapeStatus() {
+    return propagateEscapeStatus;
+  }
+
+  public void setPropagateEscapeStatus(boolean propagateEscapeStatus) {
+    this.propagateEscapeStatus = propagateEscapeStatus;
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/autoescape/EscapeMode.java b/src/com/google/clearsilver/jsilver/autoescape/EscapeMode.java
new file mode 100644
index 0000000..1ddbd39
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/autoescape/EscapeMode.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.autoescape;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException;
+
+public enum EscapeMode {
+  ESCAPE_NONE("none", false), ESCAPE_HTML("html", false), ESCAPE_JS("js", false), ESCAPE_URL("url",
+      false), ESCAPE_IS_CONSTANT("constant", false),
+
+  // These modes are used as starting modes, and a parser parses the
+  // subsequent template contents to determine the right escaping command to use.
+  ESCAPE_AUTO("auto", true), // Identical to ESCAPE_AUTO_HTML
+  ESCAPE_AUTO_HTML("auto_html", true), ESCAPE_AUTO_JS("auto_js", true), ESCAPE_AUTO_JS_UNQUOTED(
+      "auto_js_unquoted", true), ESCAPE_AUTO_STYLE("auto_style", true), ESCAPE_AUTO_ATTR(
+      "auto_attr", true), ESCAPE_AUTO_UNQUOTED_ATTR("auto_attr_unquoted", true), ESCAPE_AUTO_ATTR_URI(
+      "auto_attr_uri", true), ESCAPE_AUTO_UNQUOTED_ATTR_URI("auto_attr_uri_unquoted", true), ESCAPE_AUTO_ATTR_URI_START(
+      "auto_attr_uri_start", true), ESCAPE_AUTO_UNQUOTED_ATTR_URI_START(
+      "auto_attr_uri_start_unquoted", true), ESCAPE_AUTO_ATTR_JS("auto_attr_js", true), ESCAPE_AUTO_ATTR_UNQUOTED_JS(
+      "auto_attr_unquoted_js", true), ESCAPE_AUTO_UNQUOTED_ATTR_JS("auto_attr_js_unquoted", true), ESCAPE_AUTO_UNQUOTED_ATTR_UNQUOTED_JS(
+      "auto_attr_js_unquoted_js", true), ESCAPE_AUTO_ATTR_CSS("auto_attr_style", true), ESCAPE_AUTO_UNQUOTED_ATTR_CSS(
+      "auto_attr_style_unquoted", true);
+
+  private String escapeCmd;
+  private boolean autoEscaper;
+
+  private EscapeMode(String escapeCmd, boolean autoEscaper) {
+    this.escapeCmd = escapeCmd;
+    this.autoEscaper = autoEscaper;
+  }
+
+  /**
+   * This function maps the type of escaping requested (escapeCmd) to the appropriate EscapeMode. If
+   * no explicit escaping is requested, but doAutoEscape is true, the function chooses auto escaping
+   * (EscapeMode.ESCAPE_AUTO). This mirrors the behaviour of ClearSilver.
+   * 
+   * @param escapeCmd A string indicating type of escaping requested.
+   * @param doAutoEscape Whether auto escaping should be applied if escapeCmd is null. Corresponds
+   *        to the Config.AutoEscape HDF variable.
+   * @return
+   */
+  public static EscapeMode computeEscapeMode(String escapeCmd, boolean doAutoEscape) {
+    EscapeMode escapeMode;
+
+    // If defined, the explicit escaping mode (configured using "Config.VarEscapeMode")
+    // takes preference over auto escaping
+    if (escapeCmd != null) {
+      for (EscapeMode e : EscapeMode.values()) {
+        if (e.escapeCmd.equals(escapeCmd)) {
+          return e;
+        }
+      }
+      throw new JSilverAutoEscapingException("Invalid escaping mode specified: " + escapeCmd);
+
+    } else {
+      if (doAutoEscape) {
+        escapeMode = ESCAPE_AUTO;
+      } else {
+        escapeMode = ESCAPE_NONE;
+      }
+      return escapeMode;
+    }
+  }
+
+  /**
+   * Calls {@link #computeEscapeMode(String, boolean)} with {@code doAutoEscape = false}.
+   * 
+   * @param escapeCmd A string indicating type of escaping requested.
+   * @return EscapeMode
+   * @throws JSilverAutoEscapingException if {@code escapeCmd} is not recognized.
+   */
+  public static EscapeMode computeEscapeMode(String escapeCmd) {
+    return computeEscapeMode(escapeCmd, false);
+  }
+
+  /**
+   * Computes the EscapeMode of the result of concatenating two values. The EscapeModes of the two
+   * values are provided by {@code left} and {@code right} respectively. For now, if either of the
+   * values was escaped or a constant, we return {@code ESCAPE_IS_CONSTANT}. This is how ClearSilver
+   * behaves.
+   * 
+   * @return {@code ESCAPE_NONE} if either of the values was not escaped or constant. {@code
+   *         ESCAPE_IS_CONSTANT} otherwise.
+   */
+  public static EscapeMode combineModes(EscapeMode left, EscapeMode right) {
+    if (left.equals(ESCAPE_NONE) || right.equals(ESCAPE_NONE)) {
+      // If either of the values has not been escaped,
+      // do not trust the result.
+      return ESCAPE_NONE;
+    } else {
+      // For now, indicate that this result is always safe in all contexts.
+      // This is what ClearSilver does. We may introduce a stricter autoescape
+      // rule later on which also requires that the escaping be the same as the
+      // context its used in.
+      return ESCAPE_IS_CONSTANT;
+    }
+  }
+
+  public boolean isAutoEscapingMode() {
+    return autoEscaper;
+  }
+
+  // TODO: Simplify enum names, and just use toString() instead.
+  public String getEscapeCommand() {
+    return escapeCmd;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/compatibility/ClearsilverRenderer.java b/src/com/google/clearsilver/jsilver/compatibility/ClearsilverRenderer.java
new file mode 100644
index 0000000..0d10bce
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/compatibility/ClearsilverRenderer.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.compatibility;
+
+import com.google.clearsilver.jsilver.TemplateRenderer;
+import com.google.clearsilver.jsilver.template.Template;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.exceptions.JSilverException;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+
+import org.clearsilver.CS;
+import org.clearsilver.CSFileLoader;
+import org.clearsilver.ClearsilverFactory;
+import org.clearsilver.HDF;
+import org.clearsilver.jni.JniClearsilverFactory;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * A {@link TemplateRenderer} implemented using ClearSilver itself.
+ */
+public class ClearsilverRenderer implements TemplateRenderer {
+  private final ClearsilverFactory factory;
+  private final ResourceLoader defaultResourceLoader;
+
+  /**
+   * Creates an implementation using the provided ClearSilver factory and JSilver resource loader.
+   */
+  public ClearsilverRenderer(ClearsilverFactory factory, ResourceLoader resourceLoader) {
+    this.factory = factory;
+    this.defaultResourceLoader = resourceLoader;
+  }
+
+  /**
+   * Creates a JSilver implementation using the JNI ClearSilver factory and provided JSilver
+   * resource loader.
+   */
+  public ClearsilverRenderer(ResourceLoader resourceLoader) {
+    this(new JniClearsilverFactory(), resourceLoader);
+  }
+
+  @Override
+  public void render(String templateName, Data data, Appendable output,
+      final ResourceLoader resourceLoader) throws IOException, JSilverException {
+    CSFileLoader fileLoader = new CSFileLoader() {
+      @Override
+      public String load(HDF hdf, String filename) throws IOException {
+        return loadResource(filename, resourceLoader);
+      }
+    };
+
+    HDF hdf = factory.newHdf();
+    try {
+      // Copy the Data into the HDF.
+      hdf.readString(data.toString());
+
+      CS cs = factory.newCs(hdf);
+      try {
+        cs.setFileLoader(fileLoader);
+        cs.parseFile(templateName);
+        output.append(cs.render());
+      } finally {
+        cs.close();
+      }
+    } finally {
+      hdf.close();
+    }
+  }
+
+  @Override
+  public void render(String templateName, Data data, Appendable output) throws IOException,
+      JSilverException {
+    render(templateName, data, output, defaultResourceLoader);
+  }
+
+  @Override
+  public String render(String templateName, Data data) throws IOException, JSilverException {
+    Appendable output = new StringBuilder(8192);
+    render(templateName, data, output);
+    return output.toString();
+  }
+
+  @Override
+  public void render(Template template, Data data, Appendable output, ResourceLoader resourceLoader)
+      throws IOException, JSilverException {
+    throw new UnsupportedOperationException("ClearsilverRenderer only expects "
+        + "template names, not Templates");
+  }
+
+  @Override
+  public void render(Template template, Data data, Appendable output) throws IOException,
+      JSilverException {
+    render(template, data, output, defaultResourceLoader);
+  }
+
+  @Override
+  public String render(Template template, Data data) throws IOException, JSilverException {
+    Appendable output = new StringBuilder(8192);
+    render(template, data, output);
+    return output.toString();
+  }
+
+  @Override
+  public void renderFromContent(String content, Data data, Appendable output) throws IOException,
+      JSilverException {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public String renderFromContent(String content, Data data) throws IOException, JSilverException {
+    Appendable output = new StringBuilder(8192);
+    renderFromContent(content, data, output);
+    return output.toString();
+  }
+
+  /**
+   * @return the contents of a resource, or null if the resource was not found.
+   */
+  private String loadResource(String filename, ResourceLoader resourceLoader) throws IOException {
+    Reader reader = resourceLoader.open(filename);
+    if (reader == null) {
+      throw new FileNotFoundException(filename);
+    }
+    StringBuilder sb = new StringBuilder();
+    char buf[] = new char[4096];
+    int count;
+    while ((count = reader.read(buf)) != -1) {
+      sb.append(buf, 0, count);
+    }
+    return sb.toString();
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/compiler/BaseCompiledTemplate.java b/src/com/google/clearsilver/jsilver/compiler/BaseCompiledTemplate.java
new file mode 100644
index 0000000..2e40d9a
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/compiler/BaseCompiledTemplate.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.compiler;
+
+import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.data.DataContext;
+import com.google.clearsilver.jsilver.data.DefaultDataContext;
+import com.google.clearsilver.jsilver.data.TypeConverter;
+import com.google.clearsilver.jsilver.exceptions.ExceptionUtil;
+import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
+import com.google.clearsilver.jsilver.functions.FunctionExecutor;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.template.DefaultRenderingContext;
+import com.google.clearsilver.jsilver.template.Macro;
+import com.google.clearsilver.jsilver.template.RenderingContext;
+import com.google.clearsilver.jsilver.template.Template;
+import com.google.clearsilver.jsilver.template.TemplateLoader;
+import com.google.clearsilver.jsilver.values.Value;
+
+import java.io.IOException;
+import java.util.Collections;
+
+/**
+ * Base class providing help to generated templates.
+ * 
+ * Note, many of the methods are public as they are also used by macros.
+ */
+public abstract class BaseCompiledTemplate implements Template {
+
+  private FunctionExecutor functionExecutor;
+  private String templateName;
+  private TemplateLoader templateLoader;
+  private EscapeMode escapeMode = EscapeMode.ESCAPE_NONE;
+  private AutoEscapeOptions autoEscapeOptions;
+
+  public void setFunctionExecutor(FunctionExecutor functionExecutor) {
+    this.functionExecutor = functionExecutor;
+  }
+
+  public void setTemplateName(String templateName) {
+    this.templateName = templateName;
+  }
+
+  public void setTemplateLoader(TemplateLoader templateLoader) {
+    this.templateLoader = templateLoader;
+  }
+
+  /**
+   * Set auto escaping options so they can be passed to the rendering context.
+   * 
+   * @see AutoEscapeOptions
+   */
+  public void setAutoEscapeOptions(AutoEscapeOptions autoEscapeOptions) {
+    this.autoEscapeOptions = autoEscapeOptions;
+  }
+
+  @Override
+  public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException {
+
+    render(createRenderingContext(data, out, resourceLoader));
+  }
+
+  @Override
+  public RenderingContext createRenderingContext(Data data, Appendable out,
+      ResourceLoader resourceLoader) {
+    DataContext dataContext = new DefaultDataContext(data);
+    return new DefaultRenderingContext(dataContext, resourceLoader, out, functionExecutor,
+        autoEscapeOptions);
+  }
+
+  @Override
+  public String getTemplateName() {
+    return templateName;
+  }
+
+  /**
+   * Sets the EscapeMode in which this template was generated.
+   * 
+   * @param mode EscapeMode
+   */
+  public void setEscapeMode(EscapeMode mode) {
+    this.escapeMode = mode;
+  }
+
+  @Override
+  public EscapeMode getEscapeMode() {
+    return escapeMode;
+  }
+
+  @Override
+  public String getDisplayName() {
+    return templateName;
+  }
+
+  /**
+   * Verify that the loop arguments are valid. If not, we will skip the loop.
+   */
+  public static boolean validateLoopArgs(int start, int end, int increment) {
+    if (increment == 0) {
+      return false; // No increment. Avoid infinite loop.
+    }
+    if (increment > 0 && start > end) {
+      return false; // Incrementing the wrong way. Avoid infinite loop.
+    }
+    if (increment < 0 && start < end) {
+      return false; // Incrementing the wrong way. Avoid infinite loop.
+    }
+    return true;
+  }
+
+
+  public static boolean exists(Data data) {
+    return TypeConverter.exists(data);
+  }
+
+  public static int asInt(String value) {
+    return TypeConverter.asNumber(value);
+  }
+
+  public static int asInt(int value) {
+    return value;
+  }
+
+  public static int asInt(boolean value) {
+    return value ? 1 : 0;
+  }
+
+  public static int asInt(Value value) {
+    return value.asNumber();
+  }
+
+  public static int asInt(Data data) {
+    return TypeConverter.asNumber(data);
+  }
+
+  public static String asString(String value) {
+    return value;
+  }
+
+  public static String asString(int value) {
+    return Integer.toString(value);
+  }
+
+  public static String asString(boolean value) {
+    return value ? "1" : "0";
+  }
+
+  public static String asString(Value value) {
+    return value.asString();
+  }
+
+  public static String asString(Data data) {
+    return TypeConverter.asString(data);
+  }
+
+  public static Value asValue(String value) {
+    // Compiler mode does not use the Value's escapeMode or partiallyEscaped
+    // variables. TemplateTranslator uses other means to determine the proper
+    // escaping to apply. So just set the default escaping flags here.
+    return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false);
+  }
+
+  public static Value asValue(int value) {
+    // Compiler mode does not use the Value's escapeMode or partiallyEscaped
+    // variables. TemplateTranslator uses other means to determine the proper
+    // escaping to apply. So just set the default escaping flags here.
+    return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false);
+  }
+
+  public static Value asValue(boolean value) {
+    // Compiler mode does not use the Value's escapeMode or partiallyEscaped
+    // variables. TemplateTranslator uses other means to determine the proper
+    // escaping to apply. So just set the default escaping flags here.
+    return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false);
+  }
+
+  public static Value asValue(Value value) {
+    return value;
+  }
+
+  public static Value asVariableValue(String variableName, DataContext context) {
+    return Value.variableValue(variableName, context);
+  }
+
+  public static boolean asBoolean(boolean value) {
+    return value;
+  }
+
+  public static boolean asBoolean(String value) {
+    return TypeConverter.asBoolean(value);
+  }
+
+  public static boolean asBoolean(int value) {
+    return value != 0;
+  }
+
+  public static boolean asBoolean(Value value) {
+    return value.asBoolean();
+  }
+
+  public static boolean asBoolean(Data data) {
+    return TypeConverter.asBoolean(data);
+  }
+
+  /**
+   * Gets the name of the node for writing. Used by cs name command. Returns empty string if not
+   * found.
+   */
+  public static String getNodeName(Data data) {
+    return data == null ? "" : data.getSymlink().getName();
+  }
+
+  /**
+   * Returns child nodes of parent. Parent may be null, in which case an empty iterable is returned.
+   */
+  public Iterable<? extends Data> getChildren(Data parent) {
+    if (parent == null) {
+      return Collections.emptySet();
+    } else {
+      return parent.getChildren();
+    }
+  }
+
+  protected TemplateLoader getTemplateLoader() {
+    return templateLoader;
+  }
+
+  public abstract class CompiledMacro implements Macro {
+
+    private final String macroName;
+    private final String[] argumentsNames;
+
+    protected CompiledMacro(String macroName, String... argumentsNames) {
+      this.macroName = macroName;
+      this.argumentsNames = argumentsNames;
+    }
+
+    @Override
+    public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException {
+      render(createRenderingContext(data, out, resourceLoader));
+    }
+
+    @Override
+    public RenderingContext createRenderingContext(Data data, Appendable out,
+        ResourceLoader resourceLoader) {
+      return BaseCompiledTemplate.this.createRenderingContext(data, out, resourceLoader);
+    }
+
+    @Override
+    public String getTemplateName() {
+      return BaseCompiledTemplate.this.getTemplateName();
+    }
+
+    @Override
+    public String getMacroName() {
+      return macroName;
+    }
+
+    @Override
+    public String getArgumentName(int index) {
+      if (index >= argumentsNames.length) {
+        // TODO: Make sure this behavior of failing if too many
+        // arguments are passed to a macro is consistent with JNI / interpreter.
+        throw new JSilverInterpreterException("Too many arguments supplied to macro " + macroName);
+      }
+      return argumentsNames[index];
+    }
+
+    public int getArgumentCount() {
+      return argumentsNames.length;
+    }
+
+    protected TemplateLoader getTemplateLoader() {
+      return templateLoader;
+    }
+
+    @Override
+    public EscapeMode getEscapeMode() {
+      return BaseCompiledTemplate.this.getEscapeMode();
+    }
+
+    @Override
+    public String getDisplayName() {
+      return BaseCompiledTemplate.this.getDisplayName() + ":" + macroName;
+    }
+  }
+
+  /**
+   * Code common to all three include commands.
+   * 
+   * @param templateName String representing name of file to include.
+   * @param ignoreMissingFile {@code true} if any FileNotFound error generated by the template
+   *        loader should be ignored, {@code false} otherwise.
+   * @param context Rendering context to use for the included template.
+   */
+  protected void include(String templateName, boolean ignoreMissingFile, RenderingContext context) {
+    if (!context.pushIncludeStackEntry(templateName)) {
+      throw new JSilverInterpreterException(createIncludeLoopErrorMessage(templateName, context
+          .getIncludedTemplateNames()));
+    }
+
+    loadAndRenderIncludedTemplate(templateName, ignoreMissingFile, context);
+
+    if (!context.popIncludeStackEntry(templateName)) {
+      // Include stack trace is corrupted
+      throw new IllegalStateException("Unable to find on include stack: " + templateName);
+    }
+  }
+
+  // This method should ONLY be called from include()
+  private void loadAndRenderIncludedTemplate(String templateName, boolean ignoreMissingFile,
+      RenderingContext context) {
+    Template template = null;
+    try {
+      template =
+          templateLoader.load(templateName, context.getResourceLoader(), context
+              .getAutoEscapeMode());
+    } catch (RuntimeException e) {
+      if (ignoreMissingFile && ExceptionUtil.isFileNotFoundException(e)) {
+        return;
+      } else {
+        throw e;
+      }
+    }
+    // Intepret loaded template.
+    try {
+      template.render(context);
+    } catch (IOException e) {
+      throw new JSilverInterpreterException(e.getMessage());
+    }
+  }
+
+  private String createIncludeLoopErrorMessage(String templateName, Iterable<String> includeStack) {
+    StringBuilder message = new StringBuilder();
+    message.append("File included twice: ");
+    message.append(templateName);
+
+    message.append(" Include stack:");
+    for (String fileName : includeStack) {
+      message.append("\n -> ");
+      message.append(fileName);
+    }
+    message.append("\n -> ");
+    message.append(templateName);
+    return message.toString();
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/compiler/CompilingClassLoader.java b/src/com/google/clearsilver/jsilver/compiler/CompilingClassLoader.java
new file mode 100644
index 0000000..0e6b4f4
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/compiler/CompilingClassLoader.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.compiler;
+
+import java.net.URISyntaxException;
+import java.net.URI;
+import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import static java.util.Collections.singleton;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.LinkedList;
+
+import javax.tools.JavaCompiler;
+import javax.tools.ToolProvider;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.JavaFileManager;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.FileObject;
+import javax.tools.DiagnosticListener;
+
+/**
+ * This is a Java ClassLoader that will attempt to load a class from a string of source code.
+ * 
+ * <h3>Example</h3>
+ * 
+ * <pre>
+ * String className = "com.foo.MyClass";
+ * String classSource =
+ *   "package com.foo;\n" +
+ *   "public class MyClass implements Runnable {\n" +
+ *   "  @Override public void run() {\n" +
+ *   "    System.out.println(\"Hello world\");\n" +
+ *   "  }\n" +
+ *   "}";
+ *
+ * // Load class from source.
+ * ClassLoader classLoader = new CompilingClassLoader(
+ *     parentClassLoader, className, classSource);
+ * Class myClass = classLoader.loadClass(className);
+ *
+ * // Use it.
+ * Runnable instance = (Runnable)myClass.newInstance();
+ * instance.run();
+ * </pre>
+ * 
+ * Only one chunk of source can be compiled per instance of CompilingClassLoader. If you need to
+ * compile more, create multiple CompilingClassLoader instances.
+ * 
+ * Uses Java 1.6's in built compiler API.
+ * 
+ * If the class cannot be compiled, loadClass() will throw a ClassNotFoundException and log the
+ * compile errors to System.err. If you don't want the messages logged, or want to explicitly handle
+ * the messages you can provide your own {@link javax.tools.DiagnosticListener} through
+ * {#setDiagnosticListener()}.
+ * 
+ * @see java.lang.ClassLoader
+ * @see javax.tools.JavaCompiler
+ */
+public class CompilingClassLoader extends ClassLoader {
+
+  /**
+   * Thrown when code cannot be compiled.
+   */
+  public static class CompilerException extends Exception {
+
+    public CompilerException(String message) {
+      super(message);
+    }
+  }
+
+  private Map<String, ByteArrayOutputStream> byteCodeForClasses =
+      new HashMap<String, ByteArrayOutputStream>();
+
+  private static final URI EMPTY_URI;
+
+  static {
+    try {
+      // Needed to keep SimpleFileObject constructor happy.
+      EMPTY_URI = new URI("");
+    } catch (URISyntaxException e) {
+      throw new Error(e);
+    }
+  }
+
+  /**
+   * @param parent Parent classloader to resolve dependencies from.
+   * @param className Name of class to compile. eg. "com.foo.MyClass".
+   * @param sourceCode Java source for class. e.g. "package com.foo; class MyClass { ... }".
+   * @param diagnosticListener Notified of compiler errors (may be null).
+   */
+  public CompilingClassLoader(ClassLoader parent, String className, CharSequence sourceCode,
+      DiagnosticListener<JavaFileObject> diagnosticListener) throws CompilerException {
+    super(parent);
+    if (!compileSourceCodeToByteCode(className, sourceCode, diagnosticListener)) {
+      throw new CompilerException("Could not compile " + className);
+    }
+  }
+
+  /**
+   * Override ClassLoader's class resolving method. Don't call this directly, instead use
+   * {@link ClassLoader#loadClass(String)}.
+   */
+  @Override
+  public Class findClass(String name) throws ClassNotFoundException {
+    ByteArrayOutputStream byteCode = byteCodeForClasses.get(name);
+    if (byteCode == null) {
+      throw new ClassNotFoundException(name);
+    }
+    return defineClass(name, byteCode.toByteArray(), 0, byteCode.size());
+  }
+
+  /**
+   * @return Whether compilation was successful.
+   */
+  private boolean compileSourceCodeToByteCode(String className, CharSequence sourceCode,
+      DiagnosticListener<JavaFileObject> diagnosticListener) {
+    JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
+
+    // Set up the in-memory filesystem.
+    InMemoryFileManager fileManager =
+        new InMemoryFileManager(javaCompiler.getStandardFileManager(null, null, null));
+    JavaFileObject javaFile = new InMemoryJavaFile(className, sourceCode);
+
+    // Javac option: remove these when the javac zip impl is fixed
+    // (http://b/issue?id=1822932)
+    System.setProperty("useJavaUtilZip", "true"); // setting value to any non-null string
+    List<String> options = new LinkedList<String>();
+    // this is ignored by javac currently but useJavaUtilZip should be
+    // a valid javac XD option, which is another bug
+    options.add("-XDuseJavaUtilZip");
+
+    // Now compile!
+    JavaCompiler.CompilationTask compilationTask = javaCompiler.getTask(null, // Null: log any
+                                                                              // unhandled errors to
+                                                                              // stderr.
+        fileManager, diagnosticListener, options, null, singleton(javaFile));
+    return compilationTask.call();
+  }
+
+  /**
+   * Provides an in-memory representation of JavaFileManager abstraction, so we do not need to write
+   * any files to disk.
+   * 
+   * When files are written to, rather than putting the bytes on disk, they are appended to buffers
+   * in byteCodeForClasses.
+   * 
+   * @see javax.tools.JavaFileManager
+   */
+  private class InMemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
+
+    public InMemoryFileManager(JavaFileManager fileManager) {
+      super(fileManager);
+    }
+
+    @Override
+    public JavaFileObject getJavaFileForOutput(Location location, final String className,
+        JavaFileObject.Kind kind, FileObject sibling) throws IOException {
+      return new SimpleJavaFileObject(EMPTY_URI, kind) {
+        public OutputStream openOutputStream() throws IOException {
+          ByteArrayOutputStream outputStream = byteCodeForClasses.get(className);
+          if (outputStream != null) {
+            throw new IllegalStateException("Cannot write more than once");
+          }
+          // Reasonable size for a simple .class.
+          outputStream = new ByteArrayOutputStream(256);
+          byteCodeForClasses.put(className, outputStream);
+          return outputStream;
+        }
+      };
+    }
+  }
+
+  private static class InMemoryJavaFile extends SimpleJavaFileObject {
+
+    private final CharSequence sourceCode;
+
+    public InMemoryJavaFile(String className, CharSequence sourceCode) {
+      super(makeUri(className), Kind.SOURCE);
+      this.sourceCode = sourceCode;
+    }
+
+    private static URI makeUri(String className) {
+      try {
+        return new URI(className.replaceAll("\\.", "/") + Kind.SOURCE.extension);
+      } catch (URISyntaxException e) {
+        throw new RuntimeException(e); // Not sure what could cause this.
+      }
+    }
+
+    @Override
+    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+      return sourceCode;
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/compiler/EscapingEvaluator.java b/src/com/google/clearsilver/jsilver/compiler/EscapingEvaluator.java
new file mode 100644
index 0000000..a3f9c28
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/compiler/EscapingEvaluator.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.compiler;
+
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.BooleanLiteralExpression;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.string;
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.AAddExpression;
+import com.google.clearsilver.jsilver.syntax.node.AAndExpression;
+import com.google.clearsilver.jsilver.syntax.node.ADecimalExpression;
+import com.google.clearsilver.jsilver.syntax.node.ADescendVariable;
+import com.google.clearsilver.jsilver.syntax.node.ADivideExpression;
+import com.google.clearsilver.jsilver.syntax.node.AEqExpression;
+import com.google.clearsilver.jsilver.syntax.node.AExistsExpression;
+import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression;
+import com.google.clearsilver.jsilver.syntax.node.AGtExpression;
+import com.google.clearsilver.jsilver.syntax.node.AGteExpression;
+import com.google.clearsilver.jsilver.syntax.node.AHexExpression;
+import com.google.clearsilver.jsilver.syntax.node.ALtExpression;
+import com.google.clearsilver.jsilver.syntax.node.ALteExpression;
+import com.google.clearsilver.jsilver.syntax.node.AModuloExpression;
+import com.google.clearsilver.jsilver.syntax.node.AMultiplyExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
+import com.google.clearsilver.jsilver.syntax.node.ANeExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANegativeExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANotExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericAddExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericEqExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericNeExpression;
+import com.google.clearsilver.jsilver.syntax.node.AOrExpression;
+import com.google.clearsilver.jsilver.syntax.node.AStringExpression;
+import com.google.clearsilver.jsilver.syntax.node.ASubtractExpression;
+import com.google.clearsilver.jsilver.syntax.node.AVariableExpression;
+import com.google.clearsilver.jsilver.syntax.node.PExpression;
+
+import java.util.LinkedList;
+
+/**
+ * Generates a JavaExpression to determine whether a given CS expression should be escaped before
+ * displaying. If propagateEscapeStatus is enabled, string and numeric literals are not escaped, nor
+ * is the output of an escaping function. If not, any expression that contains an escaping function
+ * is not escaped. This maintains compatibility with the way ClearSilver works.
+ */
+public class EscapingEvaluator extends DepthFirstAdapter {
+
+  private JavaExpression currentEscapingExpression;
+  private boolean propagateEscapeStatus;
+  private final VariableTranslator variableTranslator;
+
+  public EscapingEvaluator(VariableTranslator variableTranslator) {
+    super();
+    this.variableTranslator = variableTranslator;
+  }
+
+  /**
+   * Returns a JavaExpression that can be used to decide whether a given variable should be escaped.
+   * 
+   * @param expression variable expression to be evaluated.
+   * @param propagateEscapeStatus Whether to propagate the variable's escape status.
+   * 
+   * @return Returns a {@code JavaExpression} representing a boolean expression that evaluates to
+   *         {@code true} if {@code expression} should be exempted from escaping and {@code false}
+   *         otherwise.
+   */
+  public JavaExpression computeIfExemptFromEscaping(PExpression expression,
+      boolean propagateEscapeStatus) {
+    if (propagateEscapeStatus) {
+      return computeForPropagateStatus(expression);
+    }
+    return computeEscaping(expression, propagateEscapeStatus);
+  }
+
+  private JavaExpression computeForPropagateStatus(PExpression expression) {
+    // This function generates a boolean expression that evaluates to true
+    // if the input should be exempt from escaping. As this should only be
+    // called when PropagateStatus is enabled we must check EscapeMode as
+    // well as isPartiallyEscaped.
+    // The interpreter mode equivalent of this boolean expression would be :
+    // ((value.getEscapeMode() != EscapeMode.ESCAPE_NONE) || value.isPartiallyEscaped())
+
+    JavaExpression escapeMode = computeEscaping(expression, true);
+    JavaExpression partiallyEscaped = computeEscaping(expression, false);
+
+    JavaExpression escapeModeCheck =
+        JavaExpression.infix(JavaExpression.Type.BOOLEAN, "!=", escapeMode, JavaExpression
+            .symbol("EscapeMode.ESCAPE_NONE"));
+
+    return JavaExpression.infix(JavaExpression.Type.BOOLEAN, "||", escapeModeCheck,
+        partiallyEscaped);
+  }
+
+  /**
+   * Compute the escaping applied to the given expression. Uses {@code propagateEscapeStatus} to
+   * determine how to treat constants, and whether escaping is required on a part of the expression
+   * or the whole expression.
+   */
+  public JavaExpression computeEscaping(PExpression expression, boolean propagateEscapeStatus) {
+    try {
+      assert currentEscapingExpression == null : "Not reentrant";
+      this.propagateEscapeStatus = propagateEscapeStatus;
+      expression.apply(this);
+      assert currentEscapingExpression != null : "No escaping calculated";
+      return currentEscapingExpression;
+    } finally {
+      currentEscapingExpression = null;
+    }
+  }
+
+  private void setEscaping(JavaExpression escaping) {
+    currentEscapingExpression = escaping;
+  }
+
+  /**
+   * String concatenation. Do not escape the combined string, if either of the halves has been
+   * escaped.
+   */
+  @Override
+  public void caseAAddExpression(AAddExpression node) {
+    node.getLeft().apply(this);
+    JavaExpression left = currentEscapingExpression;
+    node.getRight().apply(this);
+    JavaExpression right = currentEscapingExpression;
+
+    setEscaping(or(left, right));
+  }
+
+  /**
+   * Process AST node for a function (e.g. dosomething(...)).
+   */
+  @Override
+  public void caseAFunctionExpression(AFunctionExpression node) {
+    LinkedList<PExpression> argsList = node.getArgs();
+    PExpression[] args = argsList.toArray(new PExpression[argsList.size()]);
+
+    // Because the function name may have dots in, the parser would have broken
+    // it into a little node tree which we need to walk to reconstruct the
+    // full name.
+    final StringBuilder fullFunctionName = new StringBuilder();
+    node.getName().apply(new DepthFirstAdapter() {
+
+      @Override
+      public void caseANameVariable(ANameVariable node11) {
+        fullFunctionName.append(node11.getWord().getText());
+      }
+
+      @Override
+      public void caseADescendVariable(ADescendVariable node12) {
+        node12.getParent().apply(this);
+        fullFunctionName.append('.');
+        node12.getChild().apply(this);
+      }
+    });
+
+    setEscaping(function(fullFunctionName.toString(), args));
+  }
+
+  /**
+   * Do not escape the output of a function if either the function is an escaping function, or any
+   * of its parameters have been escaped.
+   */
+  private JavaExpression function(String name, PExpression... csExpressions) {
+    if (propagateEscapeStatus) {
+      // context.isEscapingFunction("name") ? EscapeMode.ESCAPE_IS_CONSTANT : EscapeMode.ESCAPE_NONE
+      return JavaExpression.inlineIf(JavaExpression.Type.UNKNOWN, callOn(
+          JavaExpression.Type.BOOLEAN, TemplateTranslator.CONTEXT, "isEscapingFunction",
+          string(name)), JavaExpression.symbol("EscapeMode.ESCAPE_IS_CONSTANT"), JavaExpression
+          .symbol("EscapeMode.ESCAPE_NONE"));
+    }
+    JavaExpression finalExpression = BooleanLiteralExpression.FALSE;
+    for (int i = 0; i < csExpressions.length; i++) {
+      // Will put result in currentEscapingExpression.
+      csExpressions[i].apply(this);
+      finalExpression = or(finalExpression, currentEscapingExpression);
+    }
+    JavaExpression funcExpr =
+        callOn(JavaExpression.Type.BOOLEAN, TemplateTranslator.CONTEXT, "isEscapingFunction",
+            string(name));
+    return or(finalExpression, funcExpr);
+  }
+
+  /*
+   * This function tries to optimize the output expression where possible: instead of
+   * "(false || context.isEscapingFunction())" it returns "context.isEscapingFunction()".
+   */
+  private JavaExpression or(JavaExpression first, JavaExpression second) {
+    if (propagateEscapeStatus) {
+      return JavaExpression.callOn(JavaExpression.symbol("EscapeMode"), "combineModes", first,
+          second);
+    }
+
+    if (first instanceof BooleanLiteralExpression) {
+      BooleanLiteralExpression expr = (BooleanLiteralExpression) first;
+      if (expr.getValue()) {
+        return expr;
+      } else {
+        return second;
+      }
+    }
+    if (second instanceof BooleanLiteralExpression) {
+      BooleanLiteralExpression expr = (BooleanLiteralExpression) second;
+      if (expr.getValue()) {
+        return expr;
+      } else {
+        return first;
+      }
+    }
+    return JavaExpression.infix(JavaExpression.Type.BOOLEAN, "||", first, second);
+  }
+
+  /*
+   * All the following operators have no effect on escaping, so just default to 'false'.
+   */
+
+  /**
+   * Process AST node for a variable (e.g. a.b.c).
+   */
+  @Override
+  public void caseAVariableExpression(AVariableExpression node) {
+    if (propagateEscapeStatus) {
+      JavaExpression varName = variableTranslator.translate(node.getVariable());
+      setEscaping(callOn(TemplateTranslator.DATA_CONTEXT, "findVariableEscapeMode", varName));
+    } else {
+      setDefaultEscaping();
+    }
+  }
+
+  private void setDefaultEscaping() {
+    if (propagateEscapeStatus) {
+      setEscaping(JavaExpression.symbol("EscapeMode.ESCAPE_IS_CONSTANT"));
+    } else {
+      setEscaping(BooleanLiteralExpression.FALSE);
+    }
+  }
+
+  /**
+   * Process AST node for a string (e.g. "hello").
+   */
+  @Override
+  public void caseAStringExpression(AStringExpression node) {
+    setDefaultEscaping();
+  }
+
+  /**
+   * Process AST node for a decimal integer (e.g. 123).
+   */
+  @Override
+  public void caseADecimalExpression(ADecimalExpression node) {
+    setDefaultEscaping();
+  }
+
+  /**
+   * Process AST node for a hex integer (e.g. 0x1AB).
+   */
+  @Override
+  public void caseAHexExpression(AHexExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseANumericExpression(ANumericExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseANotExpression(ANotExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseAExistsExpression(AExistsExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseAEqExpression(AEqExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseANumericEqExpression(ANumericEqExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseANeExpression(ANeExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseANumericNeExpression(ANumericNeExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseALtExpression(ALtExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseAGtExpression(AGtExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseALteExpression(ALteExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseAGteExpression(AGteExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseAAndExpression(AAndExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseAOrExpression(AOrExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseANumericAddExpression(ANumericAddExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseASubtractExpression(ASubtractExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseAMultiplyExpression(AMultiplyExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseADivideExpression(ADivideExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseAModuloExpression(AModuloExpression node) {
+    setDefaultEscaping();
+  }
+
+  @Override
+  public void caseANegativeExpression(ANegativeExpression node) {
+    setDefaultEscaping();
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/compiler/ExpressionTranslator.java b/src/com/google/clearsilver/jsilver/compiler/ExpressionTranslator.java
new file mode 100644
index 0000000..f984393
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/compiler/ExpressionTranslator.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.compiler;
+
+import com.google.clearsilver.jsilver.compiler.JavaExpression.Type;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.bool;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.call;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.callFindVariable;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.declare;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.integer;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.string;
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.AAddExpression;
+import com.google.clearsilver.jsilver.syntax.node.AAndExpression;
+import com.google.clearsilver.jsilver.syntax.node.ADecimalExpression;
+import com.google.clearsilver.jsilver.syntax.node.ADescendVariable;
+import com.google.clearsilver.jsilver.syntax.node.ADivideExpression;
+import com.google.clearsilver.jsilver.syntax.node.AEqExpression;
+import com.google.clearsilver.jsilver.syntax.node.AExistsExpression;
+import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression;
+import com.google.clearsilver.jsilver.syntax.node.AGtExpression;
+import com.google.clearsilver.jsilver.syntax.node.AGteExpression;
+import com.google.clearsilver.jsilver.syntax.node.AHexExpression;
+import com.google.clearsilver.jsilver.syntax.node.ALtExpression;
+import com.google.clearsilver.jsilver.syntax.node.ALteExpression;
+import com.google.clearsilver.jsilver.syntax.node.AModuloExpression;
+import com.google.clearsilver.jsilver.syntax.node.AMultiplyExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
+import com.google.clearsilver.jsilver.syntax.node.ANeExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANegativeExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANotExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericAddExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericEqExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericNeExpression;
+import com.google.clearsilver.jsilver.syntax.node.AOrExpression;
+import com.google.clearsilver.jsilver.syntax.node.AStringExpression;
+import com.google.clearsilver.jsilver.syntax.node.ASubtractExpression;
+import com.google.clearsilver.jsilver.syntax.node.AVariableExpression;
+import com.google.clearsilver.jsilver.syntax.node.PExpression;
+
+import java.util.LinkedList;
+
+/**
+ * Translates a CS expression (from the AST) into an equivalent Java expression.
+ * 
+ * In order to optimize the expressions nicely this class emits code using a series of wrapper
+ * functions for casting to/from various types. Rather than the old style of saying:
+ * 
+ * <pre>ValueX.asFoo()</pre>
+ * 
+ * we now write:
+ * 
+ * <pre>asFoo(ValueX)</pre>
+ * 
+ * This is actually very important because it means that as we optimize the expressions to return
+ * fundamental types, we just have different versions of the {@code asFoo()} methods that take the
+ * appropriate types. The user of the expression is responsible for casting it and the producer of
+ * the expression is now free to produce optimized expressions.
+ */
+public class ExpressionTranslator extends DepthFirstAdapter {
+
+  private JavaExpression currentJavaExpression;
+
+  /**
+   * Translate a template AST expression into a Java String expression.
+   */
+  public JavaExpression translateToString(PExpression csExpression) {
+    return translateUntyped(csExpression).cast(Type.STRING);
+  }
+
+  /**
+   * Translate a template AST expression into a Java boolean expression.
+   */
+  public JavaExpression translateToBoolean(PExpression csExpression) {
+    return translateUntyped(csExpression).cast(Type.BOOLEAN);
+  }
+
+  /**
+   * Translate a template AST expression into a Java integer expression.
+   */
+  public JavaExpression translateToNumber(PExpression csExpression) {
+    return translateUntyped(csExpression).cast(Type.INT);
+  }
+
+  /**
+   * Translate a template AST expression into a Java Data expression.
+   */
+  public JavaExpression translateToData(PExpression csExpression) {
+    return translateUntyped(csExpression).cast(Type.DATA);
+  }
+
+  /**
+   * Translate a template AST expression into a Java Data expression.
+   */
+  public JavaExpression translateToVarName(PExpression csExpression) {
+    return translateUntyped(csExpression).cast(Type.VAR_NAME);
+  }
+
+  /**
+   * Translate a template AST expression into a Java Value expression.
+   */
+  public JavaExpression translateToValue(PExpression csExpression) {
+    return translateUntyped(csExpression).cast(Type.VALUE);
+  }
+
+  /**
+   * Declares the (typed) expression as a variable with the given name. (e.g. "int foo = 5" or
+   * "Data foo = Data.getChild("a.b")"
+   */
+  public JavaExpression declareAsVariable(String name, PExpression csExpression) {
+    JavaExpression expression = translateUntyped(csExpression);
+    Type type = expression.getType();
+    assert type != null : "all subexpressions should be typed";
+    return declare(type, name, expression);
+  }
+
+  /**
+   * Translate a template AST expression into an untyped expression.
+   */
+  public JavaExpression translateUntyped(PExpression csExpression) {
+    try {
+      assert currentJavaExpression == null : "Not reentrant";
+      csExpression.apply(this);
+      assert currentJavaExpression != null : "No expression created";
+      return currentJavaExpression;
+    } finally {
+      currentJavaExpression = null;
+    }
+  }
+
+  private void setResult(JavaExpression javaExpression) {
+    this.currentJavaExpression = javaExpression;
+  }
+
+  /**
+   * Process AST node for a variable (e.g. a.b.c).
+   */
+  @Override
+  public void caseAVariableExpression(AVariableExpression node) {
+    JavaExpression varName = new VariableTranslator(this).translate(node.getVariable());
+    setResult(varName);
+  }
+
+  /**
+   * Process AST node for a string (e.g. "hello").
+   */
+  @Override
+  public void caseAStringExpression(AStringExpression node) {
+    String value = node.getValue().getText();
+    value = value.substring(1, value.length() - 1); // Remove enclosing quotes.
+    setResult(string(value));
+  }
+
+  /**
+   * Process AST node for a decimal integer (e.g. 123).
+   */
+  @Override
+  public void caseADecimalExpression(ADecimalExpression node) {
+    String value = node.getValue().getText();
+    setResult(integer(value));
+  }
+
+  /**
+   * Process AST node for a hex integer (e.g. 0x1AB).
+   */
+  @Override
+  public void caseAHexExpression(AHexExpression node) {
+    String value = node.getValue().getText();
+    // Luckily ClearSilver hex representation is a subset of the Java hex
+    // representation so we can just use the literal directly.
+    // TODO: add well-formedness checks whenever literals are used
+    setResult(integer(value));
+  }
+
+  /*
+   * The next block of functions all convert CS operators into dynamically looked up functions.
+   */
+
+  @Override
+  public void caseANumericExpression(ANumericExpression node) {
+    setResult(cast(Type.INT, node.getExpression()));
+  }
+
+  @Override
+  public void caseANotExpression(ANotExpression node) {
+    setResult(prefix(Type.BOOLEAN, Type.BOOLEAN, "!", node.getExpression()));
+  }
+
+  @Override
+  public void caseAExistsExpression(AExistsExpression node) {
+    // Special case. Exists is only ever an issue for variables, all
+    // other expressions unconditionally exist.
+    PExpression expression = node.getExpression();
+    if (expression instanceof AVariableExpression) {
+      expression.apply(this);
+      if (currentJavaExpression.getType() == Type.VAR_NAME) {
+        currentJavaExpression = callFindVariable(currentJavaExpression, false);
+      }
+      setResult(call(Type.BOOLEAN, "exists", currentJavaExpression));
+    } else {
+      // If it's not a variable, it always exists
+      // NOTE: It's not clear if we must evaluate the sub-expression
+      // here (is there anything that can have side effects??)
+      setResult(bool(true));
+    }
+  }
+
+  @Override
+  public void caseAEqExpression(AEqExpression node) {
+    JavaExpression left = cast(Type.STRING, node.getLeft());
+    JavaExpression right = cast(Type.STRING, node.getRight());
+    setResult(callOn(Type.BOOLEAN, left, "equals", right));
+  }
+
+  @Override
+  public void caseANumericEqExpression(ANumericEqExpression node) {
+    setResult(infix(Type.BOOLEAN, Type.INT, "==", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseANeExpression(ANeExpression node) {
+    JavaExpression left = cast(Type.STRING, node.getLeft());
+    JavaExpression right = cast(Type.STRING, node.getRight());
+    setResult(JavaExpression.prefix(Type.BOOLEAN, "!", callOn(Type.BOOLEAN, left, "equals", right)));
+  }
+
+  @Override
+  public void caseANumericNeExpression(ANumericNeExpression node) {
+    setResult(infix(Type.BOOLEAN, Type.INT, "!=", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseALtExpression(ALtExpression node) {
+    setResult(infix(Type.BOOLEAN, Type.INT, "<", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseAGtExpression(AGtExpression node) {
+    setResult(infix(Type.BOOLEAN, Type.INT, ">", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseALteExpression(ALteExpression node) {
+    setResult(infix(Type.BOOLEAN, Type.INT, "<=", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseAGteExpression(AGteExpression node) {
+    setResult(infix(Type.BOOLEAN, Type.INT, ">=", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseAAndExpression(AAndExpression node) {
+    setResult(infix(Type.BOOLEAN, Type.BOOLEAN, "&&", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseAOrExpression(AOrExpression node) {
+    setResult(infix(Type.BOOLEAN, Type.BOOLEAN, "||", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseAAddExpression(AAddExpression node) {
+    setResult(infix(Type.STRING, Type.STRING, "+", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseANumericAddExpression(ANumericAddExpression node) {
+    setResult(infix(Type.INT, Type.INT, "+", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseASubtractExpression(ASubtractExpression node) {
+    setResult(infix(Type.INT, Type.INT, "-", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseAMultiplyExpression(AMultiplyExpression node) {
+    setResult(infix(Type.INT, Type.INT, "*", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseADivideExpression(ADivideExpression node) {
+    setResult(infix(Type.INT, Type.INT, "/", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseAModuloExpression(AModuloExpression node) {
+    setResult(infix(Type.INT, Type.INT, "%", node.getLeft(), node.getRight()));
+  }
+
+  @Override
+  public void caseANegativeExpression(ANegativeExpression node) {
+    setResult(prefix(Type.INT, Type.INT, "-", node.getExpression()));
+  }
+
+  /**
+   * Process AST node for a function (e.g. dosomething(...)).
+   */
+  @Override
+  public void caseAFunctionExpression(AFunctionExpression node) {
+    LinkedList<PExpression> argsList = node.getArgs();
+    PExpression[] args = argsList.toArray(new PExpression[argsList.size()]);
+
+    // Because the function name may have dots in, the parser would have broken
+    // it into a little node tree which we need to walk to reconstruct the
+    // full name.
+    final StringBuilder fullFunctionName = new StringBuilder();
+    node.getName().apply(new DepthFirstAdapter() {
+
+      @Override
+      public void caseANameVariable(ANameVariable node11) {
+        fullFunctionName.append(node11.getWord().getText());
+      }
+
+      @Override
+      public void caseADescendVariable(ADescendVariable node12) {
+        node12.getParent().apply(this);
+        fullFunctionName.append('.');
+        node12.getChild().apply(this);
+      }
+    });
+
+    setResult(function(fullFunctionName.toString(), args));
+  }
+
+  /**
+   * Generate a JavaExpression for calling a function.
+   */
+  private JavaExpression function(String name, PExpression... csExpressions) {
+    // Outputs: context.executeFunction("myfunc", args...);
+    JavaExpression[] args = new JavaExpression[1 + csExpressions.length];
+    args[0] = string(name);
+    for (int i = 0; i < csExpressions.length; i++) {
+      args[i + 1] = cast(Type.VALUE, csExpressions[i]);
+    }
+    return callOn(Type.VALUE, TemplateTranslator.CONTEXT, "executeFunction", args);
+  }
+
+  private JavaExpression infix(Type destType, Type srcType, String infix, PExpression leftNode,
+      PExpression rightNode) {
+    JavaExpression left = cast(srcType, leftNode);
+    JavaExpression right = cast(srcType, rightNode);
+    return JavaExpression.infix(destType, infix, left, right);
+  }
+
+  private JavaExpression prefix(Type destType, Type srcType, String prefix, PExpression node) {
+    return JavaExpression.prefix(destType, prefix, cast(srcType, node));
+  }
+
+  private JavaExpression cast(Type type, PExpression node) {
+    node.apply(this);
+    return currentJavaExpression.cast(type);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/compiler/JSilverCompilationException.java b/src/com/google/clearsilver/jsilver/compiler/JSilverCompilationException.java
new file mode 100644
index 0000000..13c8605
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/compiler/JSilverCompilationException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.compiler;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverException;
+
+/**
+ * Thrown when a template cannot be compiled.
+ */
+public class JSilverCompilationException extends JSilverException {
+  public JSilverCompilationException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public JSilverCompilationException(String message) {
+    super(message);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/compiler/JavaExpression.java b/src/com/google/clearsilver/jsilver/compiler/JavaExpression.java
new file mode 100644
index 0000000..a75d845
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/compiler/JavaExpression.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.compiler;
+
+import com.google.clearsilver.jsilver.data.TypeConverter;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Represents a node of a Java expression.
+ * 
+ * This class contains static helper methods for common types of expressions, or you can just create
+ * your own subclass.
+ */
+public abstract class JavaExpression {
+
+  /**
+   * Simple type enumeration to allow us to compare the return types of expressions easily and cast
+   * expressions nicely.
+   */
+  public enum Type {
+    STRING("String") {
+      @Override
+      protected JavaExpression cast(JavaExpression expression) {
+        if (expression.getType() == VAR_NAME) {
+          expression = expression.cast(DATA);
+        }
+        return call(Type.STRING, "asString", expression);
+      }
+    },
+    INT("int") {
+      @Override
+      protected JavaExpression cast(JavaExpression expression) {
+        if (expression.getType() == VAR_NAME) {
+          expression = expression.cast(DATA);
+        }
+        return call(Type.INT, "asInt", expression);
+      }
+    },
+    BOOLEAN("boolean") {
+      @Override
+      protected JavaExpression cast(JavaExpression expression) {
+        if (expression.getType() == VAR_NAME) {
+          expression = expression.cast(DATA);
+        }
+        return call(Type.BOOLEAN, "asBoolean", expression);
+      }
+    },
+    VALUE("Value") {
+      @Override
+      protected JavaExpression cast(JavaExpression expression) {
+        if (expression.getType() == VAR_NAME) {
+          return call(Type.VALUE, "asVariableValue", expression, TemplateTranslator.DATA_CONTEXT);
+        } else {
+          return call(Type.VALUE, "asValue", expression);
+        }
+      }
+    },
+    DATA("Data") {
+      @Override
+      protected JavaExpression cast(JavaExpression expression) {
+        if (expression.getType() == VAR_NAME) {
+          return callFindVariable(expression, false);
+        } else {
+          throw new JSilverCompilationException("Cannot cast to 'Data' for expression:\n"
+              + expression.toString());
+        }
+      }
+    },
+    // This is a string that represents the name of a Data path.
+    VAR_NAME("String") {
+      @Override
+      protected JavaExpression cast(JavaExpression expression) {
+        final JavaExpression stringExpr = expression.cast(Type.STRING);
+        return new JavaExpression(Type.VAR_NAME) {
+          public void write(PrintWriter out) {
+            stringExpr.write(out);
+          }
+        };
+      }
+    },
+    // This is a special type because we only cast from DataContext, never to it.
+    DATA_CONTEXT("DataContext") {
+      @Override
+      protected JavaExpression cast(JavaExpression expression) {
+        throw new JSilverCompilationException("Cannot cast to 'DataContext' for expression:\n"
+            + expression.toString());
+      }
+    },
+    // This is a special type because we only cast from Data, never to it.
+    MACRO("Macro") {
+      @Override
+      protected JavaExpression cast(JavaExpression expression) {
+        throw new JSilverCompilationException("Cannot cast to 'Macro' for expression:\n"
+            + expression.toString());
+      }
+    },
+    // Use this type for JavaExpressions that have no type (such as method
+    // calls with no return value). Wraps the input expression with a
+    // JavaExpression of Type VOID.
+    VOID("Void") {
+      @Override
+      protected JavaExpression cast(final JavaExpression expression) {
+        return new JavaExpression(Type.VOID) {
+          @Override
+          public void write(PrintWriter out) {
+            expression.write(out);
+          }
+        };
+      }
+    };
+
+    /** Useful constant for unknown types */
+    public static final Type UNKNOWN = null;
+
+    /**
+     * The Java literal representing the type (e.g. "int", "boolean", "Value")
+     */
+    public final String symbol;
+
+    /**
+     * Unconditionally casts the given expression to the type. This should only be called after it
+     * has been determined that the destination type is not the same as the expression type.
+     */
+    protected abstract JavaExpression cast(JavaExpression expression);
+
+    private Type(String symbol) {
+      this.symbol = symbol;
+    }
+  }
+
+  private final Type type;
+
+  /**
+   * Creates a typed expression. Typed expressions allow for greater optimization by avoiding
+   * unnecessary casting operations.
+   * 
+   * @param type the Type of the expression. Must be from the enum above and represent a primitive
+   *        or a Class name or void.
+   */
+  public JavaExpression(Type type) {
+    this.type = type;
+  }
+
+  /**
+   * Cast this expression to the destination type (possibly a no-op)
+   */
+  public JavaExpression cast(Type destType) {
+    return (type != destType) ? destType.cast(this) : this;
+  }
+
+  /**
+   * Gets the type of this expression (or {@code null} if unknown).
+   */
+  public Type getType() {
+    return type;
+  }
+
+  /**
+   * Implementations use this to output the expression as Java code.
+   */
+  public abstract void write(PrintWriter out);
+
+  @Override
+  public String toString() {
+    StringWriter out = new StringWriter();
+    write(new PrintWriter(out));
+    return out.toString();
+  }
+
+  /**
+   * An untyped method call (e.g. doStuff(x, "y")).
+   */
+  public static JavaExpression call(final String method, final JavaExpression... params) {
+    return call(null, method, params);
+  }
+
+  /**
+   * A typed method call (e.g. doStuff(x, "y")).
+   */
+  public static JavaExpression call(Type type, final String method, final JavaExpression... params) {
+    return new JavaExpression(type) {
+      @Override
+      public void write(PrintWriter out) {
+        JavaSourceWriter.writeJavaSymbol(out, method);
+        out.append('(');
+        boolean seenAnyParams = false;
+        for (JavaExpression param : params) {
+          if (seenAnyParams) {
+            out.append(", ");
+          } else {
+            seenAnyParams = true;
+          }
+          param.write(out);
+        }
+        out.append(')');
+      }
+    };
+  }
+
+  /**
+   * An untyped method call on an instance (e.g. thingy.doStuff(x, "y")). We assume it returns VOID
+   * and thus there is no return value.
+   */
+  public static JavaExpression callOn(final JavaExpression instance, final String method,
+      final JavaExpression... params) {
+    return callOn(Type.VOID, instance, method, params);
+  }
+
+  /**
+   * A typed method call on an instance (e.g. thingy.doStuff(x, "y")).
+   */
+  public static JavaExpression callOn(Type type, final JavaExpression instance,
+      final String method, final JavaExpression... params) {
+    return new JavaExpression(type) {
+      @Override
+      public void write(PrintWriter out) {
+        instance.write(out);
+        out.append('.');
+        call(method, params).write(out);
+      }
+    };
+  }
+
+  /**
+   * A Java string (e.g. "hello\nworld").
+   */
+  public static JavaExpression string(String value) {
+    return new StringExpression(value);
+  }
+
+  public static class StringExpression extends JavaExpression {
+
+    private final String value;
+
+    public StringExpression(String value) {
+      super(Type.STRING);
+      this.value = value;
+    }
+
+    public String getValue() {
+      return value;
+    }
+
+    @Override
+    public void write(PrintWriter out) {
+      // TODO: This is not production ready yet - needs more
+      // thorough escaping mechanism.
+      out.append('"');
+      char[] chars = value.toCharArray();
+      for (char c : chars) {
+        switch (c) {
+          // Single quote (') does not need to be escaped as it's in a
+          // double-quoted (") string.
+          case '\n':
+            out.append("\\n");
+            break;
+          case '\r':
+            out.append("\\r");
+            break;
+          case '\t':
+            out.append("\\t");
+            break;
+          case '\\':
+            out.append("\\\\");
+            break;
+          case '"':
+            out.append("\\\"");
+            break;
+          case '\b':
+            out.append("\\b");
+            break;
+          case '\f':
+            out.append("\\f");
+            break;
+          default:
+            out.append(c);
+        }
+      }
+      out.append('"');
+    }
+  }
+
+  /**
+   * A JavaExpression to represent boolean literal values ('true' or 'false').
+   */
+  public static class BooleanLiteralExpression extends JavaExpression {
+
+    private final boolean value;
+
+    public static final BooleanLiteralExpression FALSE = new BooleanLiteralExpression(false);
+    public static final BooleanLiteralExpression TRUE = new BooleanLiteralExpression(true);
+
+    private BooleanLiteralExpression(boolean value) {
+      super(Type.BOOLEAN);
+      this.value = value;
+    }
+
+    public boolean getValue() {
+      return value;
+    }
+
+    @Override
+    public void write(PrintWriter out) {
+      out.append(String.valueOf(value));
+    }
+  }
+
+  /**
+   * An integer.
+   */
+  public static JavaExpression integer(String value) {
+    // Just parse it to to check that it is valid
+    TypeConverter.parseNumber(value);
+    return literal(Type.INT, value);
+  }
+
+  /**
+   * An integer.
+   */
+  public static JavaExpression integer(int value) {
+    return literal(Type.INT, String.valueOf(value));
+  }
+
+  /**
+   * A boolean
+   */
+  public static JavaExpression bool(boolean value) {
+    return literal(Type.BOOLEAN, value ? "true" : "false");
+  }
+
+  /**
+   * An untyped symbol (e.g. myVariable).
+   */
+  public static JavaExpression symbol(final String value) {
+    return new JavaExpression(Type.UNKNOWN) {
+      @Override
+      public void write(PrintWriter out) {
+        JavaSourceWriter.writeJavaSymbol(out, value);
+      }
+    };
+  }
+
+  /**
+   * A typed symbol (e.g. myVariable).
+   */
+  public static JavaExpression symbol(Type type, final String value) {
+    return new JavaExpression(type) {
+      @Override
+      public void write(PrintWriter out) {
+        JavaSourceWriter.writeJavaSymbol(out, value);
+      }
+    };
+  }
+
+  public static JavaExpression macro(final String value) {
+    return symbol(Type.MACRO, value);
+  }
+
+  /**
+   * A typed assignment (e.g. stuff = doSomething()).
+   */
+  public static JavaExpression assign(Type type, final String name, final JavaExpression value) {
+    return new JavaExpression(type) {
+      @Override
+      public void write(PrintWriter out) {
+        JavaSourceWriter.writeJavaSymbol(out, name);
+        out.append(" = ");
+        value.write(out);
+      }
+    };
+  }
+
+  /**
+   * A typed assignment with declaration (e.g. String stuff = doSomething()). Use this in preference
+   * when declaring variables from typed expressions.
+   */
+  public static JavaExpression declare(final Type type, final String name,
+      final JavaExpression value) {
+    return new JavaExpression(type) {
+      @Override
+      public void write(PrintWriter out) {
+        JavaSourceWriter.writeJavaSymbol(out, type.symbol);
+        out.append(' ');
+        assign(type, name, value).write(out);
+      }
+    };
+  }
+
+  /**
+   * An infix expression (e.g. (a + b) ).
+   */
+  public static JavaExpression infix(Type type, final String operator, final JavaExpression left,
+      final JavaExpression right) {
+    return new JavaExpression(type) {
+      @Override
+      public void write(PrintWriter out) {
+        out.append("(");
+        left.write(out);
+        out.append(" ").append(operator).append(" ");
+        right.write(out);
+        out.append(")");
+      }
+    };
+  }
+
+  /**
+   * An prefix expression (e.g. (-a) ).
+   */
+  public static JavaExpression prefix(Type type, final String operator,
+      final JavaExpression expression) {
+    return new JavaExpression(type) {
+      @Override
+      public void write(PrintWriter out) {
+        out.append("(").append(operator);
+        expression.write(out);
+        out.append(")");
+      }
+    };
+  }
+
+  /**
+   * A three term inline if expression (e.g. (a ? b : c) ).
+   */
+  public static JavaExpression inlineIf(Type type, final JavaExpression query,
+      final JavaExpression trueExp, final JavaExpression falseExp) {
+    if (query.getType() != Type.BOOLEAN) {
+      throw new IllegalArgumentException("Expect BOOLEAN expression");
+    }
+    return new JavaExpression(type) {
+      @Override
+      public void write(PrintWriter out) {
+        out.append("(");
+        query.write(out);
+        out.append(" ? ");
+        trueExp.write(out);
+        out.append(" : ");
+        falseExp.write(out);
+        out.append(")");
+      }
+    };
+  }
+
+  /**
+   * An increment statement (e.g. a += b). The difference with infix is that this does not wrap the
+   * expression in parentheses as that is not a valid statement.
+   */
+  public static JavaExpression increment(Type type, final JavaExpression accumulator,
+      final JavaExpression incr) {
+    return new JavaExpression(type) {
+      @Override
+      public void write(PrintWriter out) {
+        accumulator.write(out);
+        out.append(" += ");
+        incr.write(out);
+      }
+    };
+  }
+
+  /**
+   * A literal expression (e.g. anything!). This method injects whatever string it is given into the
+   * Java code - use only in cases where there can be no ambiguity about how the string could be
+   * interpreted by the compiler.
+   */
+  public static JavaExpression literal(Type type, final String value) {
+    return new JavaExpression(type) {
+      @Override
+      public void write(PrintWriter out) {
+        out.append(value);
+      }
+    };
+  }
+
+  public static JavaExpression callFindVariable(JavaExpression expression, boolean create) {
+    if (expression.getType() != Type.VAR_NAME) {
+      throw new IllegalArgumentException("Expect VAR_NAME expression");
+    }
+    return callOn(Type.DATA, TemplateTranslator.DATA_CONTEXT, "findVariable", expression,
+        JavaExpression.bool(create));
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/compiler/JavaSourceWriter.java b/src/com/google/clearsilver/jsilver/compiler/JavaSourceWriter.java
new file mode 100644
index 0000000..ab726a1
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/compiler/JavaSourceWriter.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.compiler;
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Simple API for generating Java source code. Easier than lots of string manipulation.
+ * 
+ * <h3>Example</h3>
+ * 
+ * <pre>
+ * java = new JavaSourceWriter(out);
+ *
+ * java.writeComment("// Auto generated file");
+ * java.writePackage("com.something.mypackage");
+ * java.writeImports(SomeClassToImport.class, Another.class);
+ *
+ * java.startClass("SomeClass", "InterfaceA");
+ * java.startMethod(Object.class.getMethod("toString"));
+ * java.writeStatement(call("System.out.println", string("hello")));
+ * java.endClass();
+ * </pre>
+ * 
+ * Note: For writing statements/expressions, staticly import the methods on {@link JavaExpression}.
+ */
+public class JavaSourceWriter implements Closeable, Flushable {
+
+  private final PrintWriter out;
+  private int indent;
+
+  public JavaSourceWriter(Writer out) {
+    this.out = new PrintWriter(out);
+  }
+
+  public void writePackage(String packageName) {
+    // TODO: Verify packageName is valid.
+    if (packageName != null) {
+      startLine();
+      out.append("package ").append(packageName).append(';');
+      endLine();
+      emptyLine();
+    }
+  }
+
+  public void writeImports(Class... javaClasses) {
+    for (Class javaClass : javaClasses) {
+      startLine();
+      out.append("import ").append(javaClass.getName()).append(';');
+      endLine();
+    }
+    if (javaClasses.length > 0) {
+      emptyLine();
+    }
+  }
+
+  public void writeComment(String comment) {
+    // TODO: Handle line breaks in comments.
+    startLine();
+    out.append("// ").append(comment);
+    endLine();
+  }
+
+  public void startClass(String className, String baseClassName, String... interfaceNames) {
+    startLine();
+    out.append("public class ");
+    writeJavaSymbol(out, className);
+
+    if (baseClassName != null) {
+      out.append(" extends ");
+      writeJavaSymbol(out, baseClassName);
+    }
+
+    boolean seenAnyInterfaces = false;
+    for (String interfaceName : interfaceNames) {
+      if (!seenAnyInterfaces) {
+        seenAnyInterfaces = true;
+        out.append(" implements ");
+      } else {
+        out.append(", ");
+      }
+      writeJavaSymbol(out, interfaceName);
+    }
+
+    out.append(' ');
+    startBlock();
+    emptyLine();
+  }
+
+  public void startAnonymousClass(String baseClass, JavaExpression... constructorArgs) {
+    out.append("new ");
+    writeJavaSymbol(out, baseClass);
+    out.append('(');
+
+    boolean seenAnyArgs = false;
+    for (JavaExpression constructorArg : constructorArgs) {
+      if (seenAnyArgs) {
+        out.append(", ");
+      }
+      seenAnyArgs = true;
+      constructorArg.write(out);
+    }
+
+    out.append(") ");
+    startBlock();
+    emptyLine();
+  }
+
+  public void endAnonymousClass() {
+    endBlock();
+  }
+
+  /**
+   * Start a method. The signature is based on that of an existing method.
+   */
+  public void startMethod(Method method, String... paramNames) {
+    // This currently does not support generics, varargs or arrays.
+    // If you need it - add the support. Don't want to overcomplicate it
+    // until necessary.
+
+    if (paramNames.length != method.getParameterTypes().length) {
+      throw new IllegalArgumentException("Did not specifiy correct "
+          + "number of parameter names for method signature " + method);
+    }
+
+    startLine();
+
+    // @Override abstract methods.
+    int modifiers = method.getModifiers();
+    if (Modifier.isAbstract(modifiers)) {
+      out.append("@Override");
+      endLine();
+      startLine();
+    }
+
+    // Modifiers: (public, protected, static)
+    if (modifiers != 0) {
+      // Modifiers we care about. Ditch the rest. Specifically NOT ABSTRACT.
+      modifiers &= Modifier.PUBLIC | Modifier.PROTECTED | Modifier.STATIC;
+      out.append(Modifier.toString(modifiers)).append(' ');
+    }
+
+    // Return type and name: (e.g. "void doStuff(")
+    out.append(method.getReturnType().getSimpleName()).append(' ').append(method.getName()).append(
+        '(');
+
+    // Parameters.
+    int paramIndex = 0;
+    for (Class<?> paramType : method.getParameterTypes()) {
+      if (paramIndex > 0) {
+        out.append(", ");
+      }
+      writeJavaSymbol(out, paramType.getSimpleName());
+      out.append(' ');
+      writeJavaSymbol(out, paramNames[paramIndex]);
+      paramIndex++;
+    }
+
+    out.append(')');
+
+    // Exceptions thrown.
+    boolean seenAnyExceptions = false;
+    for (Class exception : method.getExceptionTypes()) {
+      if (!seenAnyExceptions) {
+        seenAnyExceptions = true;
+        endLine();
+        startLine();
+        out.append("    throws ");
+      } else {
+        out.append(", ");
+      }
+      writeJavaSymbol(out, exception.getSimpleName());
+    }
+
+    out.append(' ');
+    startBlock();
+  }
+
+  public void startIfBlock(JavaExpression expression) {
+    startLine();
+    out.append("if (");
+    writeExpression(expression);
+    out.append(") ");
+    startBlock();
+  }
+
+  public void endIfStartElseBlock() {
+    endBlock();
+    out.append(" else ");
+    startBlock();
+  }
+
+  public void endIfBlock() {
+    endBlock();
+    endLine();
+  }
+
+  public void startScopedBlock() {
+    startLine();
+    startBlock();
+  }
+
+  public void endScopedBlock() {
+    endBlock();
+    endLine();
+  }
+
+  public void startIterableForLoop(String type, String name, JavaExpression expression) {
+    startLine();
+    out.append("for (");
+    writeJavaSymbol(out, type);
+    out.append(' ');
+    writeJavaSymbol(out, name);
+    out.append(" : ");
+    writeExpression(expression);
+    out.append(") ");
+    startBlock();
+  }
+
+  public void startForLoop(JavaExpression start, JavaExpression end, JavaExpression increment) {
+    startLine();
+    out.append("for (");
+    writeExpression(start);
+    out.append("; ");
+    writeExpression(end);
+    out.append("; ");
+    writeExpression(increment);
+    out.append(") ");
+    startBlock();
+  }
+
+  public void endLoop() {
+    endBlock();
+    endLine();
+  }
+
+  public void writeStatement(JavaExpression expression) {
+    startLine();
+    writeExpression(expression);
+    out.append(';');
+    endLine();
+  }
+
+  public void writeExpression(JavaExpression expression) {
+    expression.write(out);
+  }
+
+  public void endMethod() {
+    endBlock();
+    endLine();
+    emptyLine();
+  }
+
+  public void endClass() {
+    endBlock();
+    endLine();
+    emptyLine();
+  }
+
+  @Override
+  public void flush() {
+    out.flush();
+  }
+
+  @Override
+  public void close() {
+    out.close();
+  }
+
+  private void startBlock() {
+    out.append('{');
+    endLine();
+    indent++;
+  }
+
+  private void endBlock() {
+    indent--;
+    startLine();
+    out.append('}');
+  }
+
+  private void startLine() {
+    for (int i = 0; i < indent; i++) {
+      out.append("  ");
+    }
+  }
+
+  private void endLine() {
+    out.append('\n');
+  }
+
+  private void emptyLine() {
+    out.append('\n');
+  }
+
+  public static void writeJavaSymbol(PrintWriter out, String symbol) {
+    out.append(symbol); // TODO Make safe and validate.
+  }
+
+  public void startField(String type, JavaExpression name) {
+    startLine();
+    out.append("private final ");
+    writeJavaSymbol(out, type);
+    out.append(' ');
+    name.write(out);
+    out.append(" = ");
+  }
+
+  public void endField() {
+    out.append(';');
+    endLine();
+    emptyLine();
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/compiler/TemplateCompiler.java b/src/com/google/clearsilver/jsilver/compiler/TemplateCompiler.java
new file mode 100644
index 0000000..6e008cf
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/compiler/TemplateCompiler.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.compiler;
+
+import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.functions.FunctionExecutor;
+import com.google.clearsilver.jsilver.interpreter.TemplateFactory;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree;
+import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader;
+import com.google.clearsilver.jsilver.template.Template;
+import com.google.clearsilver.jsilver.template.TemplateLoader;
+
+import java.io.StringWriter;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaFileObject;
+
+/**
+ * Takes a template AST and compiles it into a Java class, which executes much faster than the
+ * intepreter.
+ */
+public class TemplateCompiler implements DelegatingTemplateLoader {
+
+  private static final Logger logger = Logger.getLogger(TemplateCompiler.class.getName());
+
+  private static final String PACKAGE_NAME = "com.google.clearsilver.jsilver.compiler";
+
+  // Because each template is isolated in its own ClassLoader, it doesn't
+  // matter if there are naming clashes between templates.
+  private static final String CLASS_NAME = "$CompiledTemplate";
+
+  private final TemplateFactory templateFactory;
+
+  private final FunctionExecutor globalFunctionExecutor;
+  private final AutoEscapeOptions autoEscapeOptions;
+  private TemplateLoader templateLoaderDelegate = this;
+
+  public TemplateCompiler(TemplateFactory templateFactory, FunctionExecutor globalFunctionExecutor,
+      AutoEscapeOptions autoEscapeOptions) {
+    this.templateFactory = templateFactory;
+    this.globalFunctionExecutor = globalFunctionExecutor;
+    this.autoEscapeOptions = autoEscapeOptions;
+  }
+
+  @Override
+  public void setTemplateLoaderDelegate(TemplateLoader templateLoaderDelegate) {
+    this.templateLoaderDelegate = templateLoaderDelegate;
+  }
+
+  @Override
+  public Template load(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode) {
+    return compile(templateFactory.find(templateName, resourceLoader, escapeMode), templateName,
+        escapeMode);
+  }
+
+  @Override
+  public Template createTemp(String name, String content, EscapeMode escapeMode) {
+    return compile(templateFactory.createTemp(content, escapeMode), name, escapeMode);
+  }
+
+  /**
+   * Compile AST into Java class.
+   * 
+   * @param ast A template AST.
+   * @param templateName Name of template (e.g. "foo.cs"). Used for error reporting. May be null,
+   * @return Template that can be executed (again and again).
+   */
+  private Template compile(TemplateSyntaxTree ast, String templateName, EscapeMode mode) {
+    CharSequence javaSource = translateAstToJavaSource(ast, mode);
+
+    String errorMessage = "Could not compile template: " + templateName;
+    Class<?> templateClass = compileAndLoad(javaSource, errorMessage);
+
+    try {
+      BaseCompiledTemplate compiledTemplate = (BaseCompiledTemplate) templateClass.newInstance();
+      compiledTemplate.setFunctionExecutor(globalFunctionExecutor);
+      compiledTemplate.setTemplateName(templateName);
+      compiledTemplate.setTemplateLoader(templateLoaderDelegate);
+      compiledTemplate.setEscapeMode(mode);
+      compiledTemplate.setAutoEscapeOptions(autoEscapeOptions);
+      return compiledTemplate;
+    } catch (InstantiationException e) {
+      throw new Error(e); // Should not be possible. Throw Error if it does.
+    } catch (IllegalAccessException e) {
+      throw new Error(e); // Should not be possible. Throw Error if it does.
+    }
+  }
+
+  private CharSequence translateAstToJavaSource(TemplateSyntaxTree ast, EscapeMode mode) {
+    StringWriter sourceBuffer = new StringWriter(256);
+    boolean propagateStatus =
+        autoEscapeOptions.getPropagateEscapeStatus() && mode.isAutoEscapingMode();
+    ast.apply(new TemplateTranslator(PACKAGE_NAME, CLASS_NAME, sourceBuffer, propagateStatus));
+    StringBuffer javaSource = sourceBuffer.getBuffer();
+    logger.log(Level.FINEST, "Compiled template:\n{0}", javaSource);
+    return javaSource;
+  }
+
+  private Class<?> compileAndLoad(CharSequence javaSource, String errorMessage)
+      throws JSilverCompilationException {
+    // Need a parent class loader to load dependencies from.
+    // This does not use any libraries outside of JSilver (e.g. custom user
+    // libraries), so using this class's ClassLoader should be fine.
+    ClassLoader parentClassLoader = getClass().getClassLoader();
+
+    // Collect any compiler errors/warnings.
+    DiagnosticCollector<JavaFileObject> diagnosticCollector =
+        new DiagnosticCollector<JavaFileObject>();
+
+    try {
+      // Magical ClassLoader that compiles source code on the fly.
+      CompilingClassLoader templateClassLoader =
+          new CompilingClassLoader(parentClassLoader, CLASS_NAME, javaSource, diagnosticCollector);
+      return templateClassLoader.loadClass(PACKAGE_NAME + "." + CLASS_NAME);
+    } catch (Exception e) {
+      // Ordinarily, this shouldn't happen as the code is generated. However,
+      // in case there's a bug in JSilver, it will be helpful to have as much
+      // info as possible in the exception to diagnose the problem.
+      throwExceptionWithLotsOfDiagnosticInfo(javaSource, errorMessage, diagnosticCollector
+          .getDiagnostics(), e);
+      return null; // Keep compiler happy.
+    }
+  }
+
+  private void throwExceptionWithLotsOfDiagnosticInfo(CharSequence javaSource, String errorMessage,
+      List<Diagnostic<? extends JavaFileObject>> diagnostics, Exception cause)
+      throws JSilverCompilationException {
+    // Create exception with lots of info in it.
+    StringBuilder message = new StringBuilder(errorMessage).append('\n');
+    message.append("------ Source code ------\n").append(javaSource);
+    message.append("------ Compiler messages ------\n");
+    for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
+      message.append(diagnostic).append('\n');
+    }
+    message.append("------ ------\n");
+    throw new JSilverCompilationException(message.toString(), cause);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/compiler/TemplateTranslator.java b/src/com/google/clearsilver/jsilver/compiler/TemplateTranslator.java
new file mode 100644
index 0000000..9478091
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/compiler/TemplateTranslator.java
@@ -0,0 +1,828 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.compiler;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.BooleanLiteralExpression;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.Type;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.call;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.callFindVariable;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.callOn;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.declare;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.increment;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.infix;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.inlineIf;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.integer;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.literal;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.macro;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.string;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.symbol;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.data.DataContext;
+import com.google.clearsilver.jsilver.functions.Function;
+import com.google.clearsilver.jsilver.functions.FunctionExecutor;
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.AAltCommand;
+import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ACallCommand;
+import com.google.clearsilver.jsilver.syntax.node.ADataCommand;
+import com.google.clearsilver.jsilver.syntax.node.ADefCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEachCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AIfCommand;
+import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopIncCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopToCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.ANameCommand;
+import com.google.clearsilver.jsilver.syntax.node.ANoopCommand;
+import com.google.clearsilver.jsilver.syntax.node.ASetCommand;
+import com.google.clearsilver.jsilver.syntax.node.AUvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AVarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AWithCommand;
+import com.google.clearsilver.jsilver.syntax.node.PCommand;
+import com.google.clearsilver.jsilver.syntax.node.PExpression;
+import com.google.clearsilver.jsilver.syntax.node.PPosition;
+import com.google.clearsilver.jsilver.syntax.node.PVariable;
+import com.google.clearsilver.jsilver.syntax.node.Start;
+import com.google.clearsilver.jsilver.syntax.node.TCsOpen;
+import com.google.clearsilver.jsilver.syntax.node.TWord;
+import com.google.clearsilver.jsilver.template.Macro;
+import com.google.clearsilver.jsilver.template.RenderingContext;
+import com.google.clearsilver.jsilver.template.Template;
+import com.google.clearsilver.jsilver.values.Value;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+
+/**
+ * Translates a JSilver AST into compilable Java code. This executes much faster than the
+ * interpreter.
+ * 
+ * @see TemplateCompiler
+ */
+public class TemplateTranslator extends DepthFirstAdapter {
+
+  // Root data
+  public static final JavaExpression DATA = symbol(Type.DATA, "data");
+  // RenderingContext
+  public static final JavaExpression CONTEXT = symbol("context");
+  // DataContext
+  public static final JavaExpression DATA_CONTEXT = symbol(Type.DATA_CONTEXT, "dataContext");
+  public static final JavaExpression NULL = symbol("null");
+  // Accessed from macros as well.
+  public static final JavaExpression RESOURCE_LOADER = callOn(CONTEXT, "getResourceLoader");
+  public static final JavaExpression TEMPLATE_LOADER = symbol("getTemplateLoader()");
+  public static final JavaExpression THIS_TEMPLATE = symbol("this");
+
+  private final JavaSourceWriter java;
+
+  private final String packageName;
+  private final String className;
+
+  private final ExpressionTranslator expressionTranslator = new ExpressionTranslator();
+  private final VariableTranslator variableTranslator =
+      new VariableTranslator(expressionTranslator);
+  private final EscapingEvaluator escapingEvaluator = new EscapingEvaluator(variableTranslator);
+
+  private static final Method RENDER_METHOD;
+
+  private int tempVariable = 0;
+  /**
+   * Used to determine the escaping to apply before displaying a variable. If propagateEscapeStatus
+   * is enabled, string and numeric literals are not escaped, nor is the output of an escaping
+   * function. If not, any expression that contains an escaping function is not escaped. This
+   * maintains compatibility with the way ClearSilver works.
+   */
+  private boolean propagateEscapeStatus;
+
+  /**
+   * Holds Macro information used while generating code.
+   */
+  private static class MacroInfo {
+    /**
+     * JavaExpression used for outputting the static Macro variable name.
+     */
+    JavaExpression symbol;
+
+    /**
+     * Parser node for the definition. Stored for evaluation after main render method is output.
+     */
+    ADefCommand defNode;
+  }
+
+  /**
+   * Map of macro names to definition nodes and java expressions used to refer to them.
+   */
+  private final Map<String, MacroInfo> macroMap = new HashMap<String, MacroInfo>();
+
+  /**
+   * Used to iterate through list of macros. We can't rely on Map's iterator because we may be
+   * adding to the map as we iterate through the values() list and that would throw a
+   * ConcurrentModificationException.
+   */
+  private final Queue<MacroInfo> macroQueue = new LinkedList<MacroInfo>();
+
+  /**
+   * Creates a MacroInfo object and adds it to the data structures. Also outputs statement to
+   * register the macro.
+   * 
+   * @param name name of the macro as defined in the template.
+   * @param symbol static variable name of the macro definition.
+   * @param defNode parser node holding the macro definition to be evaluated later.
+   */
+  private void addMacro(String name, JavaExpression symbol, ADefCommand defNode) {
+    if (macroMap.get(name) != null) {
+      // TODO: This macro is already defined. Should throw an error.
+    }
+    MacroInfo info = new MacroInfo();
+    info.symbol = symbol;
+    info.defNode = defNode;
+    macroMap.put(name, info);
+    macroQueue.add(info);
+
+    // Register the macro.
+    java.writeStatement(callOn(CONTEXT, "registerMacro", string(name), symbol));
+  }
+
+  static {
+    try {
+      RENDER_METHOD = Template.class.getMethod("render", RenderingContext.class);
+    } catch (NoSuchMethodException e) {
+      throw new Error("Cannot find CompiledTemplate.render() method! " + "Has signature changed?",
+          e);
+    }
+  }
+
+  public TemplateTranslator(String packageName, String className, Writer output,
+      boolean propagateEscapeStatus) {
+    this.packageName = packageName;
+    this.className = className;
+    java = new JavaSourceWriter(output);
+    this.propagateEscapeStatus = propagateEscapeStatus;
+  }
+
+  @Override
+  public void caseStart(Start node) {
+    java.writeComment("This class is autogenerated by JSilver. Do not edit.");
+    java.writePackage(packageName);
+    java.writeImports(BaseCompiledTemplate.class, Template.class, Macro.class,
+        RenderingContext.class, Data.class, DataContext.class, Function.class,
+        FunctionExecutor.class, Value.class, EscapeMode.class, IOException.class);
+    java.startClass(className, BaseCompiledTemplate.class.getSimpleName());
+
+    // Implement render() method.
+    java.startMethod(RENDER_METHOD, "context");
+    java
+        .writeStatement(declare(Type.DATA_CONTEXT, "dataContext", callOn(CONTEXT, "getDataContext")));
+    java.writeStatement(callOn(CONTEXT, "pushExecutionContext", THIS_TEMPLATE));
+    super.caseStart(node); // Walk template AST.
+    java.writeStatement(callOn(CONTEXT, "popExecutionContext"));
+    java.endMethod();
+
+    // The macros have to be defined outside of the render method.
+    // (Well actually they *could* be defined inline as anon classes, but it
+    // would make the generated code quite hard to understand).
+    MacroTransformer macroTransformer = new MacroTransformer();
+
+    while (!macroQueue.isEmpty()) {
+      MacroInfo curr = macroQueue.remove();
+      macroTransformer.parseDefNode(curr.symbol, curr.defNode);
+    }
+
+    java.endClass();
+  }
+
+  /**
+   * Chunk of data (i.e. not a CS command).
+   */
+  @Override
+  public void caseADataCommand(ADataCommand node) {
+    String content = node.getData().getText();
+    java.writeStatement(callOn(CONTEXT, "writeUnescaped", string(content)));
+  }
+
+  /**
+   * &lt;?cs var:blah &gt; expression. Evaluate as string and write output, using default escaping.
+   */
+  @Override
+  public void caseAVarCommand(AVarCommand node) {
+    capturePosition(node.getPosition());
+
+    String tempVariableName = generateTempVariable("result");
+    JavaExpression result = symbol(Type.STRING, tempVariableName);
+    java.writeStatement(declare(Type.STRING, tempVariableName, expressionTranslator
+        .translateToString(node.getExpression())));
+
+    JavaExpression escaping =
+        escapingEvaluator.computeIfExemptFromEscaping(node.getExpression(), propagateEscapeStatus);
+    writeVariable(result, escaping);
+  }
+
+  /**
+   * &lt;?cs uvar:blah &gt; expression. Evaluate as string and write output, but don't escape.
+   */
+  @Override
+  public void caseAUvarCommand(AUvarCommand node) {
+    capturePosition(node.getPosition());
+    java.writeStatement(callOn(CONTEXT, "writeUnescaped", expressionTranslator
+        .translateToString(node.getExpression())));
+  }
+
+  /**
+   * &lt;?cs set:x='y' &gt; command.
+   */
+  @Override
+  public void caseASetCommand(ASetCommand node) {
+    capturePosition(node.getPosition());
+    String tempVariableName = generateTempVariable("setNode");
+
+    // Data setNode1 = dataContext.findVariable("x", true);
+    JavaExpression setNode = symbol(Type.DATA, tempVariableName);
+    java.writeStatement(declare(Type.DATA, tempVariableName, callFindVariable(variableTranslator
+        .translate(node.getVariable()), true)));
+    // setNode1.setValue("hello");
+    java.writeStatement(callOn(setNode, "setValue", expressionTranslator.translateToString(node
+        .getExpression())));
+
+    if (propagateEscapeStatus) {
+      // setNode1.setEscapeMode(EscapeMode.ESCAPE_IS_CONSTANT);
+      java.writeStatement(callOn(setNode, "setEscapeMode", escapingEvaluator.computeEscaping(node
+          .getExpression(), propagateEscapeStatus)));
+    }
+  }
+
+  /**
+   * &lt;?cs name:blah &gt; command. Writes out the name of the original variable referred to by a
+   * given node.
+   */
+  @Override
+  public void caseANameCommand(ANameCommand node) {
+    capturePosition(node.getPosition());
+    JavaExpression readNode =
+        callFindVariable(variableTranslator.translate(node.getVariable()), false);
+    java.writeStatement(callOn(CONTEXT, "writeEscaped", call("getNodeName", readNode)));
+  }
+
+  /**
+   * &lt;?cs if:blah &gt; ... &lt;?cs else &gt; ... &lt;?cs /if &gt; command.
+   */
+  @Override
+  public void caseAIfCommand(AIfCommand node) {
+    capturePosition(node.getPosition());
+
+    java.startIfBlock(expressionTranslator.translateToBoolean(node.getExpression()));
+    node.getBlock().apply(this);
+    if (!(node.getOtherwise() instanceof ANoopCommand)) {
+      java.endIfStartElseBlock();
+      node.getOtherwise().apply(this);
+    }
+    java.endIfBlock();
+  }
+
+  /**
+   * &lt;?cs each:x=Stuff &gt; ... &lt;?cs /each &gt; command. Loops over child items of a data
+   * node.
+   */
+  @Override
+  public void caseAEachCommand(AEachCommand node) {
+    capturePosition(node.getPosition());
+
+    JavaExpression parent = expressionTranslator.translateToData(node.getExpression());
+    writeEach(node.getVariable(), parent, node.getCommand());
+  }
+
+  /**
+   * &lt;?cs with:x=Something &gt; ... &lt;?cs /with &gt; command. Aliases a value within a specific
+   * scope.
+   */
+  @Override
+  public void caseAWithCommand(AWithCommand node) {
+    capturePosition(node.getPosition());
+
+    java.startScopedBlock();
+    java.writeComment("with:");
+
+    // Extract the value first in case the temp variable has the same name.
+    JavaExpression value = expressionTranslator.translateUntyped(node.getExpression());
+    String methodName = null;
+    if (value.getType() == Type.VAR_NAME) {
+      String withValueName = generateTempVariable("withValue");
+      java.writeStatement(declare(Type.STRING, withValueName, value));
+      value = symbol(Type.VAR_NAME, withValueName);
+      methodName = "createLocalVariableByPath";
+
+      // We need to check if the variable exists. If not, we skip the with
+      // call.
+      java.startIfBlock(JavaExpression.infix(Type.BOOLEAN, "!=", value.cast(Type.DATA), literal(
+          Type.DATA, "null")));
+    } else {
+      // Cast to string so we support numeric or boolean values as well.
+      value = value.cast(Type.STRING);
+      methodName = "createLocalVariableByValue";
+    }
+
+    JavaExpression itemKey = variableTranslator.translate(node.getVariable());
+
+    // Push a new local variable scope for the with local variable
+    java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope"));
+
+    java.writeStatement(callOn(DATA_CONTEXT, methodName, itemKey, value));
+    node.getCommand().apply(this);
+
+    // Release the variable scope used by the with statement
+    java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope"));
+
+    if (value.getType() == Type.VAR_NAME) {
+      // End of if block that checks that the Data node exists.
+      java.endIfBlock();
+    }
+
+    java.endScopedBlock();
+  }
+
+  /**
+   * &lt;?cs loop:10 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers, starting at
+   * zero.
+   */
+  @Override
+  public void caseALoopToCommand(ALoopToCommand node) {
+    capturePosition(node.getPosition());
+
+    JavaExpression start = integer(0);
+    JavaExpression end = expressionTranslator.translateToNumber(node.getExpression());
+    JavaExpression incr = integer(1);
+    writeLoop(node.getVariable(), start, end, incr, node.getCommand());
+  }
+
+  /**
+   * &lt;?cs loop:0,10 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers.
+   */
+  @Override
+  public void caseALoopCommand(ALoopCommand node) {
+    capturePosition(node.getPosition());
+
+    JavaExpression start = expressionTranslator.translateToNumber(node.getStart());
+    JavaExpression end = expressionTranslator.translateToNumber(node.getEnd());
+    JavaExpression incr = integer(1);
+    writeLoop(node.getVariable(), start, end, incr, node.getCommand());
+  }
+
+  /**
+   * &lt;?cs loop:0,10,2 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers, with a
+   * specific increment.
+   */
+  @Override
+  public void caseALoopIncCommand(ALoopIncCommand node) {
+    capturePosition(node.getPosition());
+
+    JavaExpression start = expressionTranslator.translateToNumber(node.getStart());
+    JavaExpression end = expressionTranslator.translateToNumber(node.getEnd());
+    JavaExpression incr = expressionTranslator.translateToNumber(node.getIncrement());
+    writeLoop(node.getVariable(), start, end, incr, node.getCommand());
+  }
+
+  private void writeLoop(PVariable itemVariable, JavaExpression start, JavaExpression end,
+      JavaExpression incr, PCommand command) {
+
+    java.startScopedBlock();
+
+    String startVarName = generateTempVariable("start");
+    java.writeStatement(declare(Type.INT, startVarName, start));
+    JavaExpression startVar = symbol(Type.INT, startVarName);
+
+    String endVarName = generateTempVariable("end");
+    java.writeStatement(declare(Type.INT, endVarName, end));
+    JavaExpression endVar = symbol(Type.INT, endVarName);
+
+    String incrVarName = generateTempVariable("incr");
+    java.writeStatement(declare(Type.INT, incrVarName, incr));
+    JavaExpression incrVar = symbol(Type.INT, incrVarName);
+
+    // TODO: Test correctness of values.
+    java.startIfBlock(call(Type.BOOLEAN, "validateLoopArgs", startVar, endVar, incrVar));
+
+    JavaExpression itemKey = variableTranslator.translate(itemVariable);
+
+    // Push a new local variable scope for the loop local variable
+    java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope"));
+
+    String loopVariable = generateTempVariable("loop");
+    JavaExpression loopVar = symbol(Type.INT, loopVariable);
+    JavaExpression ifStart = declare(Type.INT, loopVariable, startVar);
+    JavaExpression ifEnd =
+        inlineIf(Type.BOOLEAN, infix(Type.BOOLEAN, ">=", incrVar, integer(0)), infix(Type.BOOLEAN,
+            "<=", loopVar, endVar), infix(Type.BOOLEAN, ">=", loopVar, endVar));
+    java.startForLoop(ifStart, ifEnd, increment(Type.INT, loopVar, incrVar));
+
+    java.writeStatement(callOn(DATA_CONTEXT, "createLocalVariableByValue", itemKey, symbol(
+        loopVariable).cast(Type.STRING), infix(Type.BOOLEAN, "==", symbol(loopVariable), startVar),
+        infix(Type.BOOLEAN, "==", symbol(loopVariable), endVar)));
+    command.apply(this);
+
+    java.endLoop();
+
+    // Release the variable scope used by the loop statement
+    java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope"));
+
+    java.endIfBlock();
+    java.endScopedBlock();
+  }
+
+  private void writeEach(PVariable itemVariable, JavaExpression parentData, PCommand command) {
+
+    JavaExpression itemKey = variableTranslator.translate(itemVariable);
+
+    // Push a new local variable scope for the each local variable
+    java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope"));
+
+    String childDataVariable = generateTempVariable("child");
+    java.startIterableForLoop("Data", childDataVariable, call("getChildren", parentData));
+
+    java.writeStatement(callOn(DATA_CONTEXT, "createLocalVariableByPath", itemKey, callOn(
+        Type.STRING, symbol(childDataVariable), "getFullPath")));
+    command.apply(this);
+
+    java.endLoop();
+
+    // Release the variable scope used by the each statement
+    java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope"));
+  }
+
+  /**
+   * &lt;?cs alt:someValue &gt; ... &lt;?cs /alt &gt; command. If value exists, write it, otherwise
+   * write the body of the command.
+   */
+  @Override
+  public void caseAAltCommand(AAltCommand node) {
+    capturePosition(node.getPosition());
+    String tempVariableName = generateTempVariable("altVar");
+
+    JavaExpression declaration =
+        expressionTranslator.declareAsVariable(tempVariableName, node.getExpression());
+    JavaExpression reference = symbol(declaration.getType(), tempVariableName);
+    java.writeStatement(declaration);
+    java.startIfBlock(reference.cast(Type.BOOLEAN));
+
+    JavaExpression escaping =
+        escapingEvaluator.computeIfExemptFromEscaping(node.getExpression(), propagateEscapeStatus);
+    writeVariable(reference, escaping);
+    java.endIfStartElseBlock();
+    node.getCommand().apply(this);
+    java.endIfBlock();
+  }
+
+  /*
+   * Generates a statement that will write out a variable expression, after determining whether the
+   * variable expression should be exempted from any global escaping that may currently be in
+   * effect. We try to make this determination during translation if possible, and if we cannot, we
+   * output an if/else statement to check the escaping status of the expression at run time.
+   * 
+   * Currently, unless the expression contains a function call, we know at translation tmie that it
+   * does not need to be exempted.
+   */
+  private void writeVariable(JavaExpression result, JavaExpression escapingExpression) {
+
+    if (escapingExpression instanceof BooleanLiteralExpression) {
+      BooleanLiteralExpression expr = (BooleanLiteralExpression) escapingExpression;
+      if (expr.getValue()) {
+        java.writeStatement(callOn(CONTEXT, "writeUnescaped", result.cast(Type.STRING)));
+      } else {
+        java.writeStatement(callOn(CONTEXT, "writeEscaped", result.cast(Type.STRING)));
+      }
+
+    } else {
+      java.startIfBlock(escapingExpression);
+      java.writeStatement(callOn(CONTEXT, "writeUnescaped", result.cast(Type.STRING)));
+      java.endIfStartElseBlock();
+      java.writeStatement(callOn(CONTEXT, "writeEscaped", result.cast(Type.STRING)));
+      java.endIfBlock();
+    }
+  }
+
+  /**
+   * &lt;?cs escape:'html' &gt; command. Changes default escaping function.
+   */
+  @Override
+  public void caseAEscapeCommand(AEscapeCommand node) {
+    capturePosition(node.getPosition());
+    java.writeStatement(callOn(CONTEXT, "pushEscapingFunction", expressionTranslator
+        .translateToString(node.getExpression())));
+    node.getCommand().apply(this);
+    java.writeStatement(callOn(CONTEXT, "popEscapingFunction"));
+  }
+
+  /**
+   * A fake command injected by AutoEscaper.
+   * 
+   * AutoEscaper determines the html context in which an include or lvar or evar command is called
+   * and stores this context in the AAutoescapeCommand node. This function loads the include or lvar
+   * template in this stored context.
+   */
+  @Override
+  public void caseAAutoescapeCommand(AAutoescapeCommand node) {
+    capturePosition(node.getPosition());
+
+    java.writeStatement(callOn(CONTEXT, "pushAutoEscapeMode", callOn(symbol("EscapeMode"),
+        "computeEscapeMode", expressionTranslator.translateToString(node.getExpression()))));
+    node.getCommand().apply(this);
+    java.writeStatement(callOn(CONTEXT, "popAutoEscapeMode"));
+
+  }
+
+  /**
+   * &lt;?cs linclude:'somefile.cs' &gt; command. Lazily includes another template (at render time).
+   * Throw an error if file does not exist.
+   */
+  @Override
+  public void caseAHardLincludeCommand(AHardLincludeCommand node) {
+    capturePosition(node.getPosition());
+    java.writeStatement(call("include", expressionTranslator
+        .translateToString(node.getExpression()), JavaExpression.bool(false), CONTEXT));
+  }
+
+  /**
+   * &lt;?cs linclude:'somefile.cs' &gt; command. Lazily includes another template (at render time).
+   * Silently ignore if the included file does not exist.
+   */
+  @Override
+  public void caseALincludeCommand(ALincludeCommand node) {
+    capturePosition(node.getPosition());
+    java.writeStatement(call("include", expressionTranslator
+        .translateToString(node.getExpression()), JavaExpression.bool(true), CONTEXT));
+  }
+
+  /**
+   * &lt;?cs include!'somefile.cs' &gt; command. Throw an error if file does not exist.
+   */
+  @Override
+  public void caseAHardIncludeCommand(AHardIncludeCommand node) {
+    capturePosition(node.getPosition());
+    java.writeStatement(call("include", expressionTranslator
+        .translateToString(node.getExpression()), JavaExpression.bool(false), CONTEXT));
+  }
+
+  /**
+   * &lt;?cs include:'somefile.cs' &gt; command. Silently ignore if the included file does not
+   * exist.
+   */
+  @Override
+  public void caseAIncludeCommand(AIncludeCommand node) {
+    capturePosition(node.getPosition());
+    java.writeStatement(call("include", expressionTranslator
+        .translateToString(node.getExpression()), JavaExpression.bool(true), CONTEXT));
+  }
+
+  /**
+   * &lt;?cs lvar:blah &gt; command. Evaluate expression and execute commands within.
+   */
+  @Override
+  public void caseALvarCommand(ALvarCommand node) {
+    capturePosition(node.getPosition());
+    evaluateVariable(node.getExpression(), "[lvar expression]");
+  }
+
+  /**
+   * &lt;?cs evar:blah &gt; command. Evaluate expression and execute commands within.
+   */
+  @Override
+  public void caseAEvarCommand(AEvarCommand node) {
+    capturePosition(node.getPosition());
+    evaluateVariable(node.getExpression(), "[evar expression]");
+  }
+
+  private void evaluateVariable(PExpression expression, String stackTraceDescription) {
+    java.writeStatement(callOn(callOn(TEMPLATE_LOADER, "createTemp", string(stackTraceDescription),
+        expressionTranslator.translateToString(expression), callOn(CONTEXT, "getAutoEscapeMode")),
+        "render", CONTEXT));
+  }
+
+  /**
+   * &lt;?cs def:someMacro(x,y) &gt; ... &lt;?cs /def &gt; command. Define a macro (available for
+   * the remainder of the context).
+   */
+  @Override
+  public void caseADefCommand(ADefCommand node) {
+    capturePosition(node.getPosition());
+
+    // This doesn't actually define the macro body yet, it just calls:
+    // registerMacro("someMacroName", someReference);
+    // where someReference is defined as a field later on (with the body).
+    String name = makeWord(node.getMacro());
+    if (macroMap.containsKey(name)) {
+      // this is a duplicated definition.
+      // TODO: Handle duplicates correctly.
+    }
+    // Keep track of the macro so we can generate the body later.
+    // See MacroTransformer.
+    addMacro(name, macro("macro" + macroMap.size()), node);
+  }
+
+  /**
+   * This is a special tree walker that's called after the render() method has been generated to
+   * create the macro definitions and their bodies.
+   * 
+   * It basically generates fields that look like this:
+   * 
+   * private final Macro macro1 = new CompiledMacro("myMacro", "arg1", "arg2"...) { public void
+   * render(Data data, RenderingContext context) { // macro body. } };
+   */
+  private class MacroTransformer {
+
+    public void parseDefNode(JavaExpression macroName, ADefCommand node) {
+      java.startField("Macro", macroName);
+
+      // Parameters passed to constructor. First is name of macro, the rest
+      // are the name of the arguments.
+      // e.g. cs def:doStuff(person, cheese)
+      // -> new CompiledMacro("doStuff", "person", "cheese") { .. }.
+      int i = 0;
+      JavaExpression[] args = new JavaExpression[1 + node.getArguments().size()];
+      args[i++] = string(makeWord(node.getMacro()));
+      for (PVariable argName : node.getArguments()) {
+        args[i++] = variableTranslator.translate(argName);
+      }
+      java.startAnonymousClass("CompiledMacro", args);
+
+      java.startMethod(RENDER_METHOD, "context");
+      java.writeStatement(declare(Type.DATA_CONTEXT, "dataContext", callOn(CONTEXT,
+          "getDataContext")));
+      java.writeStatement(callOn(CONTEXT, "pushExecutionContext", THIS_TEMPLATE));
+      // If !context.isRuntimeAutoEscaping(), enable runtime autoescaping for macro call.
+      String tempVariableName = generateTempVariable("doRuntimeAutoEscaping");
+      JavaExpression value =
+          JavaExpression.prefix(Type.BOOLEAN, "!", callOn(CONTEXT, "isRuntimeAutoEscaping"));
+      JavaExpression stmt = declare(Type.BOOLEAN, tempVariableName, value);
+      java.writeStatement(stmt);
+
+      JavaExpression doRuntimeAutoEscaping = symbol(Type.BOOLEAN, tempVariableName);
+      java.startIfBlock(doRuntimeAutoEscaping.cast(Type.BOOLEAN));
+      java.writeStatement(callOn(CONTEXT, "startRuntimeAutoEscaping"));
+      java.endIfBlock();
+
+      node.getCommand().apply(TemplateTranslator.this);
+
+      java.startIfBlock(doRuntimeAutoEscaping.cast(Type.BOOLEAN));
+      java.writeStatement(callOn(CONTEXT, "stopRuntimeAutoEscaping"));
+      java.endIfBlock();
+      java.writeStatement(callOn(CONTEXT, "popExecutionContext"));
+      java.endMethod();
+      java.endAnonymousClass();
+      java.endField();
+    }
+  }
+
+  private String makeWord(LinkedList<TWord> words) {
+    if (words.size() == 1) {
+      return words.getFirst().getText();
+    }
+    StringBuilder result = new StringBuilder();
+    for (TWord word : words) {
+      if (result.length() > 0) {
+        result.append('.');
+      }
+      result.append(word.getText());
+    }
+    return result.toString();
+  }
+
+  /**
+   * &lt;?cs call:someMacro(x,y) command. Call a macro.
+   */
+  @Override
+  public void caseACallCommand(ACallCommand node) {
+    capturePosition(node.getPosition());
+
+    String name = makeWord(node.getMacro());
+
+    java.startScopedBlock();
+    java.writeComment("call:" + name);
+
+    // Lookup macro.
+    // The expression used for the macro will either be the name of the
+    // static Macro object representing the macro (if we can statically
+    // determine it), or will be a temporary Macro variable (named
+    // 'macroCall###') that gets the result of findMacro at evaluation time.
+    JavaExpression macroCalled;
+    MacroInfo macroInfo = macroMap.get(name);
+    if (macroInfo == null) {
+      // We never saw the definition of the macro. Assume it might come in an
+      // included file and look it up at render time.
+      String macroCall = generateTempVariable("macroCall");
+      java
+          .writeStatement(declare(Type.MACRO, macroCall, callOn(CONTEXT, "findMacro", string(name))));
+
+      macroCalled = macro(macroCall);
+    } else {
+      macroCalled = macroInfo.symbol;
+    }
+
+    int numArgs = node.getArguments().size();
+    if (numArgs > 0) {
+
+      // TODO: Add check that number of arguments passed in equals the
+      // number expected by the macro. This should be done at translation
+      // time in a future CL.
+
+      JavaExpression[] argValues = new JavaExpression[numArgs];
+      JavaExpression[] argStatus = new JavaExpression[numArgs];
+
+      // Read the values first in case the new local variables shares the same
+      // name as a variable (or variable expansion) being passed in to the macro.
+      int i = 0;
+      for (PExpression argNode : node.getArguments()) {
+        JavaExpression value = expressionTranslator.translateUntyped(argNode);
+        if (value.getType() != Type.VAR_NAME) {
+          value = value.cast(Type.STRING);
+        }
+        String valueName = generateTempVariable("argValue");
+        java.writeStatement(declare(Type.STRING, valueName, value));
+        argValues[i] = JavaExpression.symbol(value.getType(), valueName);
+        if (propagateEscapeStatus) {
+          argStatus[i] = escapingEvaluator.computeEscaping(argNode, propagateEscapeStatus);
+        } else {
+          argStatus[i] = JavaExpression.symbol("EscapeMode.ESCAPE_NONE");
+        }
+
+        i++;
+      }
+
+      // Push a new local variable scope for this macro execution.
+      java.writeStatement(callOn(DATA_CONTEXT, "pushVariableScope"));
+
+      // Create the local variables for each argument.
+      for (i = 0; i < argValues.length; i++) {
+        JavaExpression value = argValues[i];
+        JavaExpression tempVar = callOn(macroCalled, "getArgumentName", integer(i));
+        String methodName;
+        if (value.getType() == Type.VAR_NAME) {
+          methodName = "createLocalVariableByPath";
+          java.writeStatement(callOn(DATA_CONTEXT, methodName, tempVar, value));
+        } else {
+          // Must be String as we cast it above.
+          methodName = "createLocalVariableByValue";
+          java.writeStatement(callOn(DATA_CONTEXT, methodName, tempVar, value, argStatus[i]));
+        }
+      }
+    }
+
+    // Render macro.
+    java.writeStatement(callOn(macroCalled, "render", CONTEXT));
+
+    if (numArgs > 0) {
+      // Release the variable scope used by the macro call
+      java.writeStatement(callOn(DATA_CONTEXT, "popVariableScope"));
+    }
+
+    java.endScopedBlock();
+  }
+
+  /**
+   * Walks the PPosition tree, which calls {@link #caseTCsOpen(TCsOpen)} below. This is simply to
+   * capture the position of the node in the original template file, to help developers diagnose
+   * errors.
+   */
+  private void capturePosition(PPosition position) {
+    position.apply(this);
+  }
+
+  /**
+   * Every time a &lt;cs token is found, grab the line and column and call
+   * context.setCurrentPosition() so this is captured for stack traces.
+   */
+  @Override
+  public void caseTCsOpen(TCsOpen node) {
+    int line = node.getLine();
+    int column = node.getPos();
+    java.writeStatement(callOn(CONTEXT, "setCurrentPosition", JavaExpression.integer(line),
+        JavaExpression.integer(column)));
+  }
+
+  private String generateTempVariable(String prefix) {
+    return prefix + tempVariable++;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/compiler/VariableTranslator.java b/src/com/google/clearsilver/jsilver/compiler/VariableTranslator.java
new file mode 100644
index 0000000..d555dfe
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/compiler/VariableTranslator.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.compiler;
+
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.StringExpression;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.Type;
+import static com.google.clearsilver.jsilver.compiler.JavaExpression.literal;
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.ADecNumberVariable;
+import com.google.clearsilver.jsilver.syntax.node.ADescendVariable;
+import com.google.clearsilver.jsilver.syntax.node.AExpandVariable;
+import com.google.clearsilver.jsilver.syntax.node.AHexNumberVariable;
+import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
+import com.google.clearsilver.jsilver.syntax.node.PVariable;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Translates a variable name (e.g. search.results.3.title) into the Java code for use as a key in
+ * looking up a variable (e.g. "search.results.3.title").
+ * 
+ * While it is possible to reuse an instance of this class repeatedly, it is not thread safe or
+ * reentrant. Evaluating an expression such as: <code>a.b[c.d]</code> would require two instances.
+ */
+public class VariableTranslator extends DepthFirstAdapter {
+
+  private List<JavaExpression> components;
+
+  private final ExpressionTranslator expressionTranslator;
+
+  public VariableTranslator(ExpressionTranslator expressionTranslator) {
+    this.expressionTranslator = expressionTranslator;
+  }
+
+  /**
+   * See class description.
+   * 
+   * @param csVariable Variable node in template's AST.
+   * @return Appropriate code (as JavaExpression).
+   */
+  public JavaExpression translate(PVariable csVariable) {
+    try {
+      assert components == null;
+      components = new ArrayList<JavaExpression>();
+      csVariable.apply(this);
+      components = joinComponentsWithDots(components);
+      components = combineAdjacentStrings(components);
+      return concatenate(components);
+    } finally {
+      components = null;
+    }
+  }
+
+  @Override
+  public void caseANameVariable(ANameVariable node) {
+    components.add(new StringExpression(node.getWord().getText()));
+  }
+
+  @Override
+  public void caseADecNumberVariable(ADecNumberVariable node) {
+    components.add(new StringExpression(node.getDecNumber().getText()));
+  }
+
+  @Override
+  public void caseAHexNumberVariable(AHexNumberVariable node) {
+    components.add(new StringExpression(node.getHexNumber().getText()));
+  }
+
+  @Override
+  public void caseADescendVariable(ADescendVariable node) {
+    node.getParent().apply(this);
+    node.getChild().apply(this);
+  }
+
+  @Override
+  public void caseAExpandVariable(AExpandVariable node) {
+    node.getParent().apply(this);
+    components.add(expressionTranslator.translateToString(node.getChild()));
+  }
+
+  /**
+   * Inserts dots between each component in the path.
+   * 
+   * e.g. from: "a", "b", something, "c" to: "a", ".", "b", ".", something, ".", "c"
+   */
+  private List<JavaExpression> joinComponentsWithDots(List<JavaExpression> in) {
+    List<JavaExpression> out = new ArrayList<JavaExpression>(in.size() * 2);
+    for (JavaExpression component : in) {
+      if (!out.isEmpty()) {
+        out.add(DOT);
+      }
+      out.add(component);
+    }
+    return out;
+  }
+
+  private static final JavaExpression DOT = new StringExpression(".");
+
+  /**
+   * Combines adjacent strings.
+   * 
+   * e.g. from: "a", ".", "b", ".", something, ".", "c" to : "a.b.", something, ".c"
+   */
+  private List<JavaExpression> combineAdjacentStrings(List<JavaExpression> in) {
+    assert !in.isEmpty();
+    List<JavaExpression> out = new ArrayList<JavaExpression>(in.size());
+    JavaExpression last = null;
+    for (JavaExpression current : in) {
+      if (last == null) {
+        last = current;
+        continue;
+      }
+      if (current instanceof StringExpression && last instanceof StringExpression) {
+        // Last and current are both strings - combine them.
+        StringExpression currentString = (StringExpression) current;
+        StringExpression lastString = (StringExpression) last;
+        last = new StringExpression(lastString.getValue() + currentString.getValue());
+      } else {
+        out.add(last);
+        last = current;
+      }
+    }
+    out.add(last);
+    return out;
+  }
+
+  /**
+   * Concatenate a list of JavaExpressions into a single string.
+   * 
+   * e.g. from: "a", "b", stuff to : "a" + "b" + stuff
+   */
+  private JavaExpression concatenate(List<JavaExpression> expressions) {
+    StringWriter buffer = new StringWriter();
+    PrintWriter out = new PrintWriter(buffer);
+    boolean seenFirst = false;
+    for (JavaExpression expression : expressions) {
+      if (seenFirst) {
+        out.print(" + ");
+      }
+      seenFirst = true;
+      expression.write(out);
+    }
+    return literal(Type.VAR_NAME, buffer.toString());
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/data/AbstractData.java b/src/com/google/clearsilver/jsilver/data/AbstractData.java
new file mode 100644
index 0000000..b102967
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/AbstractData.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+
+import java.io.IOException;
+
+/**
+ * This class is meant to hold implementation common to different instances of Data interface.
+ */
+public abstract class AbstractData implements Data {
+
+  protected EscapeMode escapeMode = EscapeMode.ESCAPE_NONE;
+
+  public int getIntValue() {
+    // If we ever use the integer value of a node to create the string
+    // representation we must ensure that an empty node is not mistaken
+    // for a node with the integer value '0'.
+    return TypeConverter.asNumber(getValue());
+  }
+
+  public boolean getBooleanValue() {
+    // If we ever use the boolean value of a node to create the string
+    // representation we must ensure that an empty node is not mistaken
+    // for a node with the boolean value 'false'.
+    return TypeConverter.asBoolean(getValue());
+  }
+
+  // ******************* Convenience methods *******************
+
+  /**
+   * Retrieves the value at the specified path in this HDF node's subtree.
+   * 
+   * Use {@link #getValue(String)} in preference to ensure ClearSilver compatibility.
+   */
+  public String getValue(String path, String defaultValue) {
+    Data child = getChild(path);
+    if (child == null) {
+      return defaultValue;
+    } else {
+      String result = child.getValue();
+      return result == null ? defaultValue : result;
+    }
+  }
+
+  /**
+   * Retrieves the integer value at the specified path in this HDF node's subtree. If the value does
+   * not exist, or cannot be converted to an integer, default_value will be returned.
+   * 
+   * Use {@link #getValue(String)} in preference to ensure ClearSilver compatibility.
+   */
+  public int getIntValue(String path, int defaultValue) {
+    Data child = getChild(path);
+    if (child == null) {
+      return defaultValue;
+    } else {
+      String result = child.getValue();
+      try {
+        return result == null ? defaultValue : TypeConverter.parseNumber(result);
+      } catch (NumberFormatException e) {
+        return defaultValue;
+      }
+    }
+  }
+
+  /**
+   * Retrieves the value at the specified path in this HDF node's subtree. If not found, returns
+   * null.
+   */
+  public String getValue(String path) {
+    return getValue(path, null);
+  }
+
+  /**
+   * Retrieves the value at the specified path in this HDF node's subtree. If not found or invalid,
+   * returns 0.
+   */
+  public int getIntValue(String path) {
+    return TypeConverter.asNumber(getChild(path));
+  }
+
+  /**
+   * Retrieves the value at the specified path in this HDF node's subtree. If not found or invalid,
+   * returns false.
+   */
+  public boolean getBooleanValue(String path) {
+    return TypeConverter.asBoolean(getChild(path));
+  }
+
+  /**
+   * Sets the value at the specified path in this HDF node's subtree.
+   */
+  public void setValue(String path, String value) {
+    Data child = createChild(path);
+    child.setValue(value);
+  }
+
+  // ******************* String representation *******************
+
+  @Override
+  public String toString() {
+    StringBuilder stringBuilder = new StringBuilder();
+    toString(stringBuilder, 0);
+    return stringBuilder.toString();
+  }
+
+  public void toString(StringBuilder out, int indent) {
+    try {
+      write(out, indent);
+    } catch (IOException ioe) {
+      throw new RuntimeException(ioe); // COV_NF_LINE
+    }
+  }
+
+  @Override
+  public void optimize() {
+  // Do nothing.
+  }
+
+  @Override
+  public void setEscapeMode(EscapeMode mode) {
+    this.escapeMode = mode;
+  }
+
+  @Override
+  public EscapeMode getEscapeMode() {
+    return escapeMode;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/ChainedData.java b/src/com/google/clearsilver/jsilver/data/ChainedData.java
new file mode 100644
index 0000000..568b6a1
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/ChainedData.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * Implementation of Data that allows for multiple underlying Data objects and checks each one in
+ * order for a value before giving up. Behaves like local HDF and global HDF in the JNI
+ * implementation of Clearsilver. This is only meant to be a root Data object and hardcodes that
+ * fact.
+ * <p>
+ * Note: If you have elements foo.1, foo.2, foo.3 in first Data object and foo.4, foo.5, foo.6 in
+ * second Data object, then fetching children of foo will return only foo.1 foo.2 foo.3 from first
+ * Data object.
+ */
+public class ChainedData extends DelegatedData {
+  public static final Logger logger = Logger.getLogger(ChainedData.class.getName());
+
+  // This mode allows developers to locate occurrences where they set the same HDF
+  // variable in multiple Data objects in the chain, which usually indicates
+  // bad planning or misuse.
+  public static final boolean DEBUG_MULTIPLE_ASSIGNMENTS = false;
+
+  Data[] dataList;
+
+  /**
+   * Optmization for case of single item.
+   * 
+   * @param data a single data object to wrap.
+   */
+  public ChainedData(Data data) {
+    super(data);
+    this.dataList = new Data[] {data};
+  }
+
+  public ChainedData(Data... dataList) {
+    super(getFirstData(dataList));
+    this.dataList = dataList;
+  }
+
+  public ChainedData(List<Data> dataList) {
+    super(getFirstData(dataList));
+    this.dataList = dataList.toArray(new Data[dataList.size()]);
+  }
+
+  @Override
+  protected DelegatedData newInstance(Data newDelegate) {
+    return newDelegate == null ? null : new ChainedData(newDelegate);
+  }
+
+  private static Data getFirstData(Data[] dataList) {
+    if (dataList.length == 0) {
+      throw new IllegalArgumentException("Must pass in at least one Data object to ChainedData.");
+    }
+    Data first = dataList[0];
+    if (first == null) {
+      throw new IllegalArgumentException("ChainedData does not accept null Data objects.");
+    }
+    return first;
+  }
+
+  private static Data getFirstData(List<Data> dataList) {
+    if (dataList.size() == 0) {
+      throw new IllegalArgumentException("Must pass in at least one Data object to ChainedData.");
+    }
+    Data first = dataList.get(0);
+    if (first == null) {
+      throw new IllegalArgumentException("ChainedData does not accept null Data objects.");
+    }
+    return first;
+  }
+
+  @Override
+  public Data getChild(String path) {
+    ArrayList<Data> children = null;
+    Data first = null;
+    for (Data d : dataList) {
+      Data child = d.getChild(path);
+      if (child != null) {
+        if (!DEBUG_MULTIPLE_ASSIGNMENTS) {
+          // If not in debug mode just return the first match. This assumes we are using the new
+          // style of VariableLocator that does not iteratively ask for each HDF path element
+          // separately.
+          return child;
+        }
+        if (first == null) {
+          // First match found
+          first = child;
+        } else if (children == null) {
+          // Second match found
+          children = new ArrayList<Data>(dataList.length);
+          children.add(first);
+          children.add(child);
+        } else {
+          // Third or more match found
+          children.add(child);
+        }
+      }
+    }
+    if (children == null) {
+      // 0 or 1 matches. Return first which is null or Data.
+      return first;
+    } else {
+      // Multiple matches. Pass back the first item found. This is only hit when
+      // DEBUG_MULTIPLE_ASSIGNMENTS is true.
+      logger.info("Found " + children.size() + " matches for path " + path);
+      return first;
+    }
+  }
+
+  @Override
+  public Data createChild(String path) {
+    Data child = getChild(path);
+    if (child != null) {
+      return child;
+    } else {
+      // We don't call super because we don't want to wrap the result in DelegatedData.
+      return dataList[0].createChild(path);
+    }
+  }
+
+  @Override
+  public String getValue(String path, String defaultValue) {
+    Data child = getChild(path);
+    if (child != null && child.getValue() != null) {
+      return child.getValue();
+    } else {
+      return defaultValue;
+    }
+  }
+
+  @Override
+  public int getIntValue(String path, int defaultValue) {
+    Data child = getChild(path);
+    if (child != null) {
+      String value = child.getValue();
+      try {
+        return value == null ? defaultValue : TypeConverter.parseNumber(value);
+      } catch (NumberFormatException e) {
+        return defaultValue;
+      }
+    } else {
+      return defaultValue;
+    }
+  }
+
+  @Override
+  public String getValue(String path) {
+    Data child = getChild(path);
+    if (child != null) {
+      return child.getValue();
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  public int getIntValue(String path) {
+    Data child = getChild(path);
+    if (child != null) {
+      return child.getIntValue();
+    } else {
+      return 0;
+    }
+  }
+
+  @Override
+  public boolean getBooleanValue(String path) {
+    Data child = getChild(path);
+    if (child != null) {
+      return child.getBooleanValue();
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public void toString(StringBuilder out, int indent) {
+    for (Data d : dataList) {
+      d.toString(out, indent);
+    }
+  }
+
+  @Override
+  public void write(Appendable out, int indent) throws IOException {
+    for (Data d : dataList) {
+      d.write(out, indent);
+    }
+  }
+
+  @Override
+  public void optimize() {
+    for (Data d : dataList) {
+      d.optimize();
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/Data.java b/src/com/google/clearsilver/jsilver/data/Data.java
new file mode 100644
index 0000000..769a436
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/Data.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Represents a hierarchical data set of primitives.
+ * 
+ * This is the JSilver equivalent to ClearSilver's HDF object.
+ */
+public interface Data {
+
+  // ******************* Node data *******************
+
+  /**
+   * Returns the name of this HDF node. The root node has no name, so calling this on the root node
+   * will return null.
+   */
+  String getName();
+
+  /**
+   * Returns the value of this HDF node, or null if this node has no value. Every node in the tree
+   * can have a value, a child, and a next peer.
+   */
+  String getValue();
+
+  /**
+   * Returns the integer value of this HDF node, or 0 if this node has no value.
+   * 
+   * Note: The fact that this method returns a primitive type, rather than an Integer means that its
+   * value cannot be used to determine whether the data node exists or not. Note also that, when
+   * implementing a Data object that caches these values, care must be taken to ensure that a node
+   * with an integer value of '0' is not mistaken for a non-existent node.
+   */
+  int getIntValue();
+
+  /**
+   * Returns the boolean value of this HDF node, or false if this node has no value.
+   * 
+   * Note: The fact that this method returns a primitive type, rather than a Boolean means that its
+   * value cannot be used to determine whether the data node exists or not. Note also that, when
+   * implementing a Data object that caches these values, care must be taken to ensure that a node
+   * with a boolean value of 'false' is not mistaken for a non-existent node.
+   */
+  boolean getBooleanValue();
+
+  /**
+   * Set the value of this node. Any symlink that may have been set for this node will be replaced.
+   */
+  void setValue(String value);
+
+  /**
+   * Returns the full path to this node via its parent links.
+   */
+  String getFullPath();
+
+  // ******************* Attributes *******************
+
+  /**
+   * Sets an attribute key and value on the current node, replacing any existing value.
+   * 
+   * @param key the name of the attribute to add/modify.
+   * @param value the value to assign it. Value of {@code null} will clear the attribute.
+   */
+  void setAttribute(String key, String value);
+
+  /**
+   * Returns the value of the node attribute with the given name, or {@code null} if there is no
+   * value.
+   */
+  String getAttribute(String key);
+
+  /**
+   * Returns {@code true} if the node contains an attribute with the given name, {@code false}
+   * otherwise.
+   */
+  boolean hasAttribute(String key);
+
+  /**
+   * Returns the number of attributes on this node.
+   */
+  int getAttributeCount();
+
+  /**
+   * Returns an iterable collection of attribute name/value pairs.
+   * 
+   * @return an object that can be iterated over to get all the attribute name/value pairs. Should
+   *         return empty iterator if there are no attributes.
+   */
+  Iterable<Map.Entry<String, String>> getAttributes();
+
+  // ******************* Children *******************
+
+  /**
+   * Return the root of the tree where the current node lies. If the current node is the root,
+   * return this.
+   */
+  Data getRoot();
+
+  /**
+   * Get the parent node.
+   */
+  Data getParent();
+
+  /**
+   * Is this the first of its siblings?
+   */
+  boolean isFirstSibling();
+
+  /**
+   * Is this the last of its siblings?
+   */
+  boolean isLastSibling();
+
+  /**
+   * Retrieves the node representing the next sibling of this Data node, if any.
+   * 
+   * @return the next sibling Data object or {@code null} if this is the last sibling.
+   */
+  Data getNextSibling();
+
+  /**
+   * Returns number of child nodes.
+   */
+  int getChildCount();
+
+  /**
+   * Returns children of this node.
+   */
+  Iterable<? extends Data> getChildren();
+
+  /**
+   * Retrieves the object that is the root of the subtree at hdfpath, returning null if the subtree
+   * doesn't exist
+   */
+  Data getChild(String path);
+
+  /**
+   * Retrieves the HDF object that is the root of the subtree at hdfpath, create the subtree if it
+   * doesn't exist
+   */
+  Data createChild(String path);
+
+  /**
+   * Remove the specified subtree.
+   */
+  void removeTree(String path);
+
+  // ******************* Symbolic links *******************
+
+  /**
+   * Set the source node to be a symbolic link to the destination.
+   */
+  void setSymlink(String sourcePath, String destinationPath);
+
+  /**
+   * Set the source node to be a symbolic link to the destination.
+   */
+  void setSymlink(String sourcePath, Data destination);
+
+  /**
+   * Set this node to be a symbolic link to another node.
+   */
+  void setSymlink(Data symLink);
+
+  /**
+   * Retrieve the symbolic link this node points to. Will return reference to self if not a symlink.
+   */
+  Data getSymlink();
+
+  // **************************** Copy **************************
+
+  /**
+   * Does a deep copy of the attributes and values from one node to another.
+   * 
+   * @param toPath destination path for the deep copy.
+   * @param from Data object that should be copied over.
+   */
+  void copy(String toPath, Data from);
+
+  /**
+   * Does a deep copy the attributes and values from one node to another
+   * 
+   * @param from Data object whose value should be copied over.
+   */
+  void copy(Data from);
+
+  // ******************* Convenience methods *******************
+
+  /**
+   * Retrieves the value at the specified path in this HDF node's subtree.
+   */
+  String getValue(String path, String defaultValue);
+
+  /**
+   * Retrieves the integer value at the specified path in this HDF node's subtree. If the value does
+   * not exist, or cannot be converted to an integer, default_value will be returned.
+   */
+  int getIntValue(String path, int defaultValue);
+
+  /**
+   * Retrieves the value at the specified path in this HDF node's subtree. If not found, returns
+   * null.
+   */
+  String getValue(String path);
+
+  /**
+   * Retrieves the value at the specified path in this HDF node's subtree. If not found or invalid,
+   * returns 0.
+   */
+  int getIntValue(String path);
+
+  /**
+   * Retrieves the value at the specified path in this HDF node's subtree. If not found or invalid,
+   * returns false.
+   */
+  boolean getBooleanValue(String path);
+
+  /**
+   * Sets the value at the specified path in this HDF node's subtree.
+   */
+  void setValue(String path, String value);
+
+  // ******************* String representation *******************
+
+  String toString();
+
+  void toString(StringBuilder out, int indent);
+
+  /**
+   * Write out the String representation of this HDF node.
+   */
+  void write(Appendable out, int indent) throws IOException;
+
+  /**
+   * Optimizes the Data structure for performance. This is a somewhat expensive operation that
+   * should improve CPU and/or memory usage for long-lived Data objects. For example, it may
+   * internalize all Strings to reduce redundant copies.
+   */
+  void optimize();
+
+  /**
+   * Indicates the escaping, if any that was applied to this HDF node.
+   * 
+   * @return EscapeMode that was applied to this node's value. {@code EscapeMode.ESCAPE_NONE} if the
+   *         value is not escaped. {@code EscapeMode.ESCAPE_IS_CONSTANT} if value is a string or
+   *         numeric literal.
+   * 
+   * @see #setEscapeMode
+   * @see EscapeMode
+   */
+  EscapeMode getEscapeMode();
+
+  /**
+   * Set the escaping that was applied to this HDF node. This method may be called by the template
+   * renderer, for instance, when a "set" command sets the node to a constant string. It may also be
+   * explicitly called if populating the HDF with pre-escaped or trusted values.
+   * 
+   * @see #getEscapeMode
+   */
+  void setEscapeMode(EscapeMode mode);
+}
diff --git a/src/com/google/clearsilver/jsilver/data/DataContext.java b/src/com/google/clearsilver/jsilver/data/DataContext.java
new file mode 100644
index 0000000..6d50935
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/DataContext.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+
+/**
+ * Manages the global Data object and local variable mappings during rendering.
+ */
+public interface DataContext {
+
+  /**
+   * Returns the main Data object this RenderingContext was defined with.
+   */
+  Data getRootData();
+
+  /**
+   * Creates a new Data object to hold local references, pushes it onto the variable map stack. This
+   * is used to hold macro parameters after a call command, and local variables defined for 'each'
+   * and 'with'.
+   */
+  void pushVariableScope();
+
+  /**
+   * Removes the most recent Data object added to the local variable map stack.
+   */
+  void popVariableScope();
+
+  /**
+   * Creates and sets a local variable in the current scope equal to the given value.
+   * 
+   * @param name the name of the local variable to fetch or create.
+   * @param value The String value to store at the local variable.
+   */
+  void createLocalVariableByValue(String name, String value);
+
+  /**
+   * Creates and sets a local variable in the current scope equal to the given value. Also set the
+   * EscapeMode that was applied to its value. This may be used by the template renderer to decide
+   * whether to autoescape the variable.
+   * 
+   * @param name the name of the local variable to fetch or create.
+   * @param value The String value to store at the local variable.
+   * @param mode EscapeMode that describes the escaping this variable has. {@code
+   *        EscapeMode.ESCAPE_NONE} if the variable was not escaped. {@code
+   *        EscapeMode.ESCAPE_IS_CONSTANT} if the variable was populated with a string or numeric
+   *        literal.
+   */
+  void createLocalVariableByValue(String name, String value, EscapeMode mode);
+
+  /**
+   * Creates and sets a local variable in the current scope equal to the given value.
+   * <p>
+   * This method is a helper method for supporting the first() and last() functions on loops without
+   * requiring loops create a full Data tree.
+   * 
+   * @param name the name of the local variable to fetch or create.
+   * @param value The String value to store at the local variable.
+   * @param isFirst What the local variable should return for
+   *        {@link com.google.clearsilver.jsilver.data.Data#isFirstSibling}
+   * @param isLast What the local variable should return for
+   *        {@link com.google.clearsilver.jsilver.data.Data#isLastSibling}
+   */
+  void createLocalVariableByValue(String name, String value, boolean isFirst, boolean isLast);
+
+  /**
+   * Creates a local variable that references a (possibly non-existent) Data node. When the Data
+   * object for this variable is requested, it will return the Data object at the path location or
+   * {@code null}, if it does not exist. If {@link #findVariable} is called with {@code create ==
+   * true}, then if no Data object exists at the path location, it will be created.
+   * 
+   * @param name the name of the local variable to fetch or create.
+   * @param path The path to the Data object
+   */
+  void createLocalVariableByPath(String name, String path);
+
+  /**
+   * Searches the variable map stack for the specified variable name. If not found, it then searches
+   * the root Data object. If not found then and create is {@code true}, then a new node is created
+   * with the given name in the root Data object and that node is returned.
+   * <p>
+   * Note: This only creates nodes in the root Data object, not in any local variable map. To do
+   * that, use the Data node returned by {@link #pushVariableScope()}.
+   * 
+   * @param name the name of the variable to find and/or create.
+   * @param create if {@link true} then a new node will be created if an existing Data node with the
+   *        given name does not exist.
+   * @return The first Data node in the variable map stack that matches the given name, or a Data
+   *         node in the root Data object matching the given name, or {@code null} if no such node
+   *         exists and {@code create} is {@code false}.
+   */
+  Data findVariable(String name, boolean create);
+
+  /**
+   * Searches the variable map stack for the specified variable name, and returns its
+   * {@link EscapeMode}.
+   * 
+   * @return If the variable is found, returns its {@link EscapeMode}, otherwise returns {@code
+   *         EscapeMode.ESCAPE_NONE}.
+   */
+  EscapeMode findVariableEscapeMode(String name);
+}
diff --git a/src/com/google/clearsilver/jsilver/data/DataFactory.java b/src/com/google/clearsilver/jsilver/data/DataFactory.java
new file mode 100644
index 0000000..8b94bfa
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/DataFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+
+import java.io.IOException;
+
+/**
+ * Loads data from resources.
+ */
+public interface DataFactory {
+
+  /**
+   * Create new Data instance, ready to be populated.
+   */
+  Data createData();
+
+  /**
+   * Loads data in Hierarchical Data Format (HDF) into an existing Data object.
+   */
+  void loadData(final String dataFileName, ResourceLoader resourceLoader, Data output)
+      throws JSilverBadSyntaxException, IOException;
+
+  /**
+   * Loads data in Hierarchical Data Format (HDF) into a new Data object.
+   */
+  Data loadData(String dataFileName, ResourceLoader resourceLoader) throws IOException;
+
+  /**
+   * Returns the Parser used by this factory to parse the HDF content.
+   * 
+   * @return
+   */
+  Parser getParser();
+}
diff --git a/src/com/google/clearsilver/jsilver/data/DefaultData.java b/src/com/google/clearsilver/jsilver/data/DefaultData.java
new file mode 100644
index 0000000..636f4f0
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/DefaultData.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+/**
+ * Default implementation of Data.
+ * 
+ * If you're not sure what Data implementation to use, just use this one - it will be a sensible
+ * option.
+ * 
+ * @see Data
+ * @see NestedMapData
+ */
+public class DefaultData extends NestedMapData {
+
+}
diff --git a/src/com/google/clearsilver/jsilver/data/DefaultDataContext.java b/src/com/google/clearsilver/jsilver/data/DefaultDataContext.java
new file mode 100644
index 0000000..54bf6c2
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/DefaultDataContext.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * This is the basic implementation of the DataContext. It stores the root Data node and a stack of
+ * Data objects that hold local variables. By definition, local variables are limited to single HDF
+ * names, with no '.' allowed. We use this to limit our search in the stack for the first occurence
+ * of the first chunk in the variable name.
+ */
+public class DefaultDataContext implements DataContext {
+
+  /**
+   * Root node of the Data structure used by the current context.
+   */
+  private final Data rootData;
+
+  /**
+   * Head of the linked list of local variables, starting with the newest variable created.
+   */
+  private LocalVariable head = null;
+
+  /**
+   * Indicates whether the renderer has pushed a new variable scope but no variable has been created
+   * yet.
+   */
+  private boolean newScope = false;
+
+  public DefaultDataContext(Data data) {
+    if (data == null) {
+      throw new IllegalArgumentException("rootData is null");
+    }
+    this.rootData = data;
+  }
+
+  @Override
+  public Data getRootData() {
+    return rootData;
+  }
+
+  /**
+   * Starts a new variable scope. It is illegal to call this twice in a row without declaring a
+   * local variable.
+   */
+  @Override
+  public void pushVariableScope() {
+    if (newScope) {
+      throw new IllegalStateException("PushVariableScope called twice with no "
+          + "variables declared in between.");
+    }
+    newScope = true;
+  }
+
+  /**
+   * Removes the current variable scope and references to the variables in it. It is illegal to call
+   * this more times than {@link #pushVariableScope} has been called.
+   */
+  @Override
+  public void popVariableScope() {
+    if (newScope) {
+      // We pushed but created no local variables.
+      newScope = false;
+    } else {
+      // Note, this will throw a NullPointerException if there is no scope to
+      // pop.
+      head = head.nextScope;
+    }
+  }
+
+  @Override
+  public void createLocalVariableByValue(String name, String value) {
+    createLocalVariableByValue(name, value, EscapeMode.ESCAPE_NONE);
+  }
+
+  @Override
+  public void createLocalVariableByValue(String name, String value, EscapeMode mode) {
+    LocalVariable local = createLocalVariable(name);
+    local.value = value;
+    local.isPath = false;
+    local.setEscapeMode(mode);
+  }
+
+  @Override
+  public void createLocalVariableByValue(String name, String value, boolean isFirst, boolean isLast) {
+    LocalVariable local = createLocalVariable(name);
+    local.value = value;
+    local.isPath = false;
+    local.isFirst = isFirst;
+    local.isLast = isLast;
+  }
+
+  @Override
+  public void createLocalVariableByPath(String name, String path) {
+    LocalVariable local = createLocalVariable(name);
+    local.value = path;
+    local.isPath = true;
+  }
+
+  private LocalVariable createLocalVariable(String name) {
+    if (head == null && !newScope) {
+      throw new IllegalStateException("Must call pushVariableScope before "
+          + "creating local variable.");
+    }
+    // First look for a local variable with the same name in the current scope
+    // and return that if it exists. If we don't do this then loops and each
+    // can cause the list of local variables to explode.
+    //
+    // We only look at the first local variable (head) if it is part of the
+    // current scope (we're not defining a new scope). Since each and loop
+    // variables are always in their own scope, there would only be one variable
+    // in the current scope if it was a reuse case. For macro calls (which are
+    // the only other way createLocalVariable is called multiple times in a
+    // single scope and thus head may not be the only local variable in the
+    // current scope) it is illegal to use the same argument name more than
+    // once. Therefore we don't need to worry about checking to see if the new
+    // local variable name matches beyond the first local variable in the
+    // current scope.
+
+    if (!newScope && head != null && name.equals(head.name)) {
+      // Clear out the fields that aren't set by the callers.
+      head.isFirst = true;
+      head.isLast = true;
+      head.node = null;
+      return head;
+    }
+
+    LocalVariable local = new LocalVariable();
+    local.name = name;
+    local.next = head;
+    if (newScope) {
+      local.nextScope = head;
+      newScope = false;
+    } else if (head != null) {
+      local.nextScope = head.nextScope;
+    } else {
+      local.nextScope = null;
+    }
+    head = local;
+    return local;
+  }
+
+  @Override
+  public Data findVariable(String name, boolean create) {
+    return findVariable(name, create, head);
+  }
+
+  @Override
+  public EscapeMode findVariableEscapeMode(String name) {
+    Data var = findVariable(name, false);
+    if (var == null) {
+      return EscapeMode.ESCAPE_NONE;
+    } else {
+      return var.getEscapeMode();
+    }
+  }
+
+  private Data findVariable(String name, boolean create, LocalVariable start) {
+    // When traversing the stack, we first look for the first chunk of the
+    // name. This is so we respect scope. If we are searching for 'foo.bar'
+    // and 'foo' is defined in many scopes, we should stop searching
+    // after the first time we find 'foo', even if that local variable does
+    // not have a child 'bar'.
+    String firstChunk = name;
+    int dot = name.indexOf('.');
+    if (dot != -1) {
+      firstChunk = name.substring(0, dot);
+    }
+
+    LocalVariable curr = start;
+
+    while (curr != null) {
+      if (curr.name.equals(firstChunk)) {
+        if (curr.isPath) {
+          // The local variable references another Data node, dereference it.
+          if (curr.node == null) {
+            // We haven't resolved the path yet. Do it now and cache it if
+            // not null. Note we begin looking for the dereferenced in the next
+            // scope.
+            curr.node = findVariable(curr.value, create, curr.nextScope);
+            if (curr.node == null) {
+              // Node does not exist. Any children won't either.
+              return null;
+            }
+          }
+          // We have a reference to the Data node directly. Use it.
+          if (dot == -1) {
+            // This is the node we're interested in.
+            return curr.node;
+          } else {
+            if (create) {
+              return curr.node.createChild(name.substring(dot + 1));
+            } else {
+              return curr.node.getChild(name.substring(dot + 1));
+            }
+          }
+        } else {
+          // This is a literal value local variable. It has no children, nor
+          // can it. We want to throw an error on creation of children.
+          if (dot == -1) {
+            return curr;
+          }
+          if (create) {
+            throw new IllegalStateException("Cannot create children of a "
+                + "local literal variable");
+          } else {
+            // No children.
+            return null;
+          }
+        }
+      }
+      curr = curr.next;
+    }
+    if (create) {
+      return rootData.createChild(name);
+    } else {
+      return rootData.getChild(name);
+    }
+  }
+
+  /**
+   * This class holds the name and value/path of a local variable. Objects of this type should only
+   * be exposed outside of this class for value-based local variables.
+   * <p>
+   * Fields are not private to avoid the performance overhead of hidden access methods used for
+   * outer classes to access private fields of inner classes.
+   */
+  private static class LocalVariable extends AbstractData {
+    // Pointer to next LocalVariable in the list.
+    LocalVariable next;
+    // Pointer to the first LocalVariable in the next scope down.
+    LocalVariable nextScope;
+
+    String name;
+    String value;
+    // True if value represents the path to another Data variable.
+    boolean isPath;
+    // Once the path resolves to a valid Data node, store it here to avoid
+    // refetching.
+    Data node = null;
+
+    // Used only for loop local variables
+    boolean isFirst = true;
+    boolean isLast = true;
+
+    public String getName() {
+      return name;
+    }
+
+    public String getValue() {
+      return value;
+    }
+
+    public void setValue(String value) {
+      this.value = value;
+    }
+
+    public String getFullPath() {
+      return name;
+    }
+
+    public void setAttribute(String key, String value) {
+      throw new UnsupportedOperationException("Not allowed on local variables.");
+    }
+
+    public String getAttribute(String key) {
+      return null;
+    }
+
+    public boolean hasAttribute(String key) {
+      return false;
+    }
+
+    public int getAttributeCount() {
+      return 0;
+    }
+
+    public Iterable<Map.Entry<String, String>> getAttributes() {
+      return null;
+    }
+
+    public Data getRoot() {
+      return null;
+    }
+
+    public Data getParent() {
+      return null;
+    }
+
+    public boolean isFirstSibling() {
+      return isFirst;
+    }
+
+    public boolean isLastSibling() {
+      return isLast;
+    }
+
+    public Data getNextSibling() {
+      throw new UnsupportedOperationException("Not allowed on local variables.");
+    }
+
+    public int getChildCount() {
+      return 0;
+    }
+
+    public Iterable<? extends Data> getChildren() {
+      return null;
+    }
+
+    public Data getChild(String path) {
+      return null;
+    }
+
+    public Data createChild(String path) {
+      throw new UnsupportedOperationException("Not allowed on local variables.");
+    }
+
+    public void removeTree(String path) {
+      throw new UnsupportedOperationException("Not allowed on local variables.");
+    }
+
+    public void setSymlink(String sourcePath, String destinationPath) {
+      throw new UnsupportedOperationException("Not allowed on local variables.");
+    }
+
+    public void setSymlink(String sourcePath, Data destination) {
+      throw new UnsupportedOperationException("Not allowed on local variables.");
+    }
+
+    public void setSymlink(Data symLink) {
+      throw new UnsupportedOperationException("Not allowed on local variables.");
+    }
+
+    public Data getSymlink() {
+      return this;
+    }
+
+    public void copy(String toPath, Data from) {
+      throw new UnsupportedOperationException("Not allowed on local variables.");
+    }
+
+    public void copy(Data from) {
+      throw new UnsupportedOperationException("Not allowed on local variables.");
+    }
+
+    public String getValue(String path, String defaultValue) {
+      throw new UnsupportedOperationException("Not allowed on local variables.");
+    }
+
+    public void write(Appendable out, int indent) throws IOException {
+      for (int i = 0; i < indent; i++) {
+        out.append("  ");
+      }
+      out.append(getName()).append(" = ").append(getValue());
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/DefaultHdfParser.java b/src/com/google/clearsilver/jsilver/data/DefaultHdfParser.java
new file mode 100644
index 0000000..b34f30f
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/DefaultHdfParser.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parses data in HierachicalDataFormat (HDF), generating callbacks for data encountered in the
+ * stream.
+ */
+public class DefaultHdfParser implements Parser {
+
+  private int initialContextSize = 10;
+
+  public void parse(Reader reader, Data output, ErrorHandler errorHandler,
+      ResourceLoader resourceLoader, String dataFileName, boolean ignoreAttributes)
+      throws IOException {
+    LineNumberReader lineReader = new LineNumberReader(reader);
+    // Although a linked list could be used here, we iterate a lot and the
+    // size will rarely get > 10 deep. In this case ArrayList is faster than
+    // LinkedList.
+    List<String> context = new ArrayList<String>(initialContextSize);
+    String line;
+    while ((line = lineReader.readLine()) != null) {
+      parseLine(line, output, context, lineReader, dataFileName, errorHandler);
+    }
+  }
+
+  private void parseLine(String line, Data output, List<String> context,
+      LineNumberReader lineReader, String dataFileName, ErrorHandler errorHandler)
+      throws IOException {
+    line = stripComment(line);
+
+    Split split;
+    if ((split = split(line, "=")) != null) {
+      // some.thing = Hello
+      output.setValue(createFullPath(context, split.left), split.right);
+    } else if ((split = split(line, "<<")) != null) {
+      // some.thing << EOM
+      // Blah blah
+      // Blah blah
+      // EOM
+      output.setValue(createFullPath(context, split.left), readToToken(lineReader, split.right));
+    } else if ((split = split(line, "{")) != null) {
+      // some.thing {
+      // ...
+      context.add(split.left);
+    } else if (split(line, "}") != null) {
+      // ...
+      // }
+      context.remove(context.size() - 1);
+    } else if ((split = split(line, ":")) != null) {
+      // some.tree : another.tree
+      output.setSymlink(createFullPath(context, split.left), split.right);
+    } else if (line.trim().length() != 0) {
+      // Anything else
+      if (errorHandler != null) {
+        errorHandler.error(lineReader.getLineNumber(), line, dataFileName, "Bad HDF syntax");
+      }
+    }
+  }
+
+  private String stripComment(String line) {
+    int commentPosition = line.indexOf('#');
+    int equalsPosition = line.indexOf('=');
+    if (commentPosition > -1 && (equalsPosition == -1 || commentPosition < equalsPosition)) {
+      return line.substring(0, commentPosition);
+    } else {
+      return line;
+    }
+  }
+
+  /**
+   * Reads lines from a reader until a line is encountered that matches token (or end of stream).
+   */
+  private String readToToken(LineNumberReader reader, String token) throws IOException {
+    StringBuilder result = new StringBuilder();
+    String line;
+    while ((line = reader.readLine()) != null && !line.trim().equals(token)) {
+      result.append(line).append('\n');
+    }
+    return result.toString();
+  }
+
+  /**
+   * Creates the full path, based on the current context.
+   */
+  private String createFullPath(List<String> context, String subPath) {
+    StringBuilder result = new StringBuilder();
+    for (String contextItem : context) {
+      result.append(contextItem).append('.');
+    }
+    result.append(subPath);
+    return result.toString();
+  }
+
+  /**
+   * Split a line in two, based on a delimiter. If the delimiter is not found, null is returned.
+   */
+  private Split split(String line, String delimiter) {
+    int position = line.indexOf(delimiter);
+    if (position > -1) {
+      Split result = new Split();
+      result.left = line.substring(0, position).trim();
+      result.right = line.substring(position + delimiter.length()).trim();
+      return result;
+    } else {
+      return null;
+    }
+  }
+
+  private static class Split {
+    String left;
+    String right;
+  }
+
+  /**
+   * Returns a factory object that constructs DefaultHdfParser objects.
+   */
+  public static ParserFactory newFactory() {
+    return new ParserFactory() {
+      public Parser newInstance() {
+        return new DefaultHdfParser();
+      }
+    };
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/data/DelegatedData.java b/src/com/google/clearsilver/jsilver/data/DelegatedData.java
new file mode 100644
index 0000000..06c08cd
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/DelegatedData.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Class that wraps a Data object and exports the same interface. Useful for extending the
+ * capabilities of an existing implementation.
+ */
+public class DelegatedData implements Data {
+
+  private final Data delegate;
+
+  public DelegatedData(Data delegate) {
+    if (delegate == null) {
+      throw new NullPointerException("Delegate Data must not be null.");
+    }
+    this.delegate = delegate;
+  }
+
+  /**
+   * Subclasses will want to override this method to return a Data object of their specific type.
+   * 
+   * @param newDelegate the Data object to wrap with a new delegator
+   * @return a DelegateData type or subclass.
+   */
+  protected DelegatedData newInstance(Data newDelegate) {
+    return newDelegate == null ? null : new DelegatedData(newDelegate);
+  }
+
+  protected Data getDelegate() {
+    return delegate;
+  }
+
+  protected static Data unwrap(Data data) {
+    if (data instanceof DelegatedData) {
+      data = ((DelegatedData) data).getDelegate();
+    }
+    return data;
+  }
+
+  @Override
+  public String getName() {
+    return getDelegate().getName();
+  }
+
+  @Override
+  public String getValue() {
+    return getDelegate().getValue();
+  }
+
+  @Override
+  public int getIntValue() {
+    return getDelegate().getIntValue();
+  }
+
+  @Override
+  public boolean getBooleanValue() {
+    return getDelegate().getBooleanValue();
+  }
+
+  @Override
+  public void setValue(String value) {
+    getDelegate().setValue(value);
+  }
+
+  @Override
+  public String getFullPath() {
+    return getDelegate().getFullPath();
+  }
+
+  @Override
+  public void setAttribute(String key, String value) {
+    getDelegate().setAttribute(key, value);
+  }
+
+  @Override
+  public String getAttribute(String key) {
+    return getDelegate().getAttribute(key);
+  }
+
+  @Override
+  public boolean hasAttribute(String key) {
+    return getDelegate().hasAttribute(key);
+  }
+
+  @Override
+  public int getAttributeCount() {
+    return getDelegate().getAttributeCount();
+  }
+
+  @Override
+  public Iterable<Map.Entry<String, String>> getAttributes() {
+    return getDelegate().getAttributes();
+  }
+
+  @Override
+  public Data getRoot() {
+    return newInstance(getDelegate().getRoot());
+  }
+
+  @Override
+  public Data getParent() {
+    return newInstance(getDelegate().getParent());
+  }
+
+  @Override
+  public boolean isFirstSibling() {
+    return getDelegate().isFirstSibling();
+  }
+
+  @Override
+  public boolean isLastSibling() {
+    return getDelegate().isLastSibling();
+  }
+
+  @Override
+  public Data getNextSibling() {
+    return newInstance(getDelegate().getNextSibling());
+  }
+
+  @Override
+  public int getChildCount() {
+    return getDelegate().getChildCount();
+  }
+
+  /**
+   * Wrapping implementation of iterator that makes sure any Data object returned by the underlying
+   * iterator is wrapped with the right DelegatedData type.
+   */
+  protected class DelegatedIterator implements Iterator<DelegatedData> {
+    private final Iterator<? extends Data> iterator;
+
+    DelegatedIterator(Iterator<? extends Data> iterator) {
+      this.iterator = iterator;
+    }
+
+    public boolean hasNext() {
+      return iterator.hasNext();
+    }
+
+    public DelegatedData next() {
+      return newInstance(iterator.next());
+    }
+
+    public void remove() {
+      iterator.remove();
+    }
+  }
+
+  /**
+   * Subclasses can override this method to return specialized child iterators. For example, if they
+   * don't want to support the remove() operation.
+   * 
+   * @return Iterator of children of delegate Data object that returns wrapped Data nodes.
+   */
+  protected Iterator<DelegatedData> newChildIterator() {
+    return new DelegatedIterator(getDelegate().getChildren().iterator());
+  }
+
+  /**
+   * Single Iterable object for each node. All it does is return a DelegatedIterator when asked for
+   * iterator.
+   */
+  private final Iterable<DelegatedData> delegatedIterable = new Iterable<DelegatedData>() {
+    public Iterator<DelegatedData> iterator() {
+      return newChildIterator();
+    }
+  };
+
+  @Override
+  public Iterable<? extends Data> getChildren() {
+    return delegatedIterable;
+  }
+
+  @Override
+  public Data getChild(String path) {
+    return newInstance(getDelegate().getChild(path));
+  }
+
+  @Override
+  public Data createChild(String path) {
+    return newInstance(getDelegate().createChild(path));
+  }
+
+  @Override
+  public void removeTree(String path) {
+    getDelegate().removeTree(path);
+  }
+
+  @Override
+  public void setSymlink(String sourcePath, String destinationPath) {
+    getDelegate().setSymlink(sourcePath, destinationPath);
+  }
+
+  @Override
+  public void setSymlink(String sourcePath, Data destination) {
+    destination = unwrap(destination);
+    getDelegate().setSymlink(sourcePath, destination);
+  }
+
+  @Override
+  public void setSymlink(Data symLink) {
+    symLink = unwrap(symLink);
+    getDelegate().setSymlink(symLink);
+  }
+
+  @Override
+  public Data getSymlink() {
+    return newInstance(getDelegate().getSymlink());
+  }
+
+  @Override
+  public void copy(String toPath, Data from) {
+    from = unwrap(from);
+    getDelegate().copy(toPath, from);
+  }
+
+  @Override
+  public void copy(Data from) {
+    from = unwrap(from);
+    getDelegate().copy(from);
+  }
+
+  @Override
+  public String getValue(String path, String defaultValue) {
+    return getDelegate().getValue(path, defaultValue);
+  }
+
+  @Override
+  public int getIntValue(String path, int defaultValue) {
+    return getDelegate().getIntValue(path, defaultValue);
+  }
+
+  @Override
+  public String getValue(String path) {
+    return getDelegate().getValue(path);
+  }
+
+  @Override
+  public int getIntValue(String path) {
+    return getDelegate().getIntValue(path);
+  }
+
+  @Override
+  public boolean getBooleanValue(String path) {
+    return getDelegate().getBooleanValue(path);
+  }
+
+  @Override
+  public void setValue(String path, String value) {
+    getDelegate().setValue(path, value);
+  }
+
+  @Override
+  public String toString() {
+    return getDelegate().toString();
+  }
+
+  @Override
+  public void toString(StringBuilder out, int indent) {
+    getDelegate().toString(out, indent);
+  }
+
+  @Override
+  public void write(Appendable out, int indent) throws IOException {
+    getDelegate().write(out, indent);
+  }
+
+  @Override
+  public void optimize() {
+    getDelegate().optimize();
+  }
+
+  @Override
+  public void setEscapeMode(EscapeMode mode) {
+    getDelegate().setEscapeMode(mode);
+  }
+
+  @Override
+  public EscapeMode getEscapeMode() {
+    return getDelegate().getEscapeMode();
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/HDFDataFactory.java b/src/com/google/clearsilver/jsilver/data/HDFDataFactory.java
new file mode 100644
index 0000000..e3e9bf4
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/HDFDataFactory.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Loads data in Hierarchical Data Format (HDF) into Data objects.
+ */
+public class HDFDataFactory implements DataFactory {
+  private final Parser hdfParser;
+  private final boolean ignoreAttributes;
+
+  public HDFDataFactory(boolean ignoreAttributes) {
+    this(ignoreAttributes, new NoOpStringInternStrategy());
+  }
+
+  public HDFDataFactory(boolean ignoreAttributes, StringInternStrategy stringInternStrategy) {
+    this(NewHdfParser.newFactory(stringInternStrategy), ignoreAttributes);
+  }
+
+  public HDFDataFactory(ParserFactory hdfParserFactory, boolean ignoreAttributes) {
+    this.ignoreAttributes = ignoreAttributes;
+    this.hdfParser = hdfParserFactory.newInstance();
+  }
+
+  @Override
+  public Data createData() {
+    return new DefaultData();
+  }
+
+  @Override
+  public void loadData(final String dataFileName, ResourceLoader resourceLoader, Data output)
+      throws JSilverBadSyntaxException, IOException {
+    Reader reader = resourceLoader.open(dataFileName);
+    if (reader == null) {
+      throw new FileNotFoundException(dataFileName);
+    }
+    try {
+      hdfParser.parse(reader, output, new Parser.ErrorHandler() {
+        public void error(int line, String lineContent, String fileName, String errorMessage) {
+          throw new JSilverBadSyntaxException("HDF parsing error : '" + errorMessage + "'",
+              lineContent, fileName, line, JSilverBadSyntaxException.UNKNOWN_POSITION, null);
+        }
+      }, resourceLoader, dataFileName, ignoreAttributes);
+    } finally {
+      resourceLoader.close(reader);
+    }
+  }
+
+  @Override
+  public Data loadData(String dataFileName, ResourceLoader resourceLoader) throws IOException {
+    Data result = createData();
+    loadData(dataFileName, resourceLoader, result);
+    return result;
+  }
+
+  @Override
+  public Parser getParser() {
+    return hdfParser;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/LocalAndGlobalData.java b/src/com/google/clearsilver/jsilver/data/LocalAndGlobalData.java
new file mode 100644
index 0000000..8882b87
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/LocalAndGlobalData.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+/**
+ * This is a special implementation of ChainedData to be used for holding the local and global Data
+ * objects (like local and global HDFs in Clearsilver). It prevents writes and modifications to the
+ * global Data object and applies them all to the local data object.
+ */
+public class LocalAndGlobalData extends ChainedData {
+
+  private final Data local;
+
+  /**
+   * Creates a Data object that encapsulates both request-scoped local HDF and an application
+   * global-scoped HDF that can be read from the template renderer. Part of the backwards
+   * compatibility with JNI Clearsilver and its globalHdf support.
+   * 
+   * @param local the request-specific HDF data that takes priority.
+   * @param global application global HDF data that should be read but not written to from the
+   *        template renderer.
+   */
+  public LocalAndGlobalData(Data local, Data global) {
+    this(local, global, false);
+  }
+
+  /**
+   * Creates a Data object that encapsulates both request-scoped local HDF and an application
+   * global-scoped HDF that can be read from the template renderer. Part of the backwards
+   * compatibility with JNI Clearsilver and its globalHdf support. We wrap the global HDF in an
+   * UnmodifiableData delegate
+   * 
+   * @param local the request-specific HDF data that takes priority.
+   * @param global application global HDF data that should be read but not written to from the
+   *        template renderer.
+   * @param allowGlobalDataModification if {@code true} then enable legacy mode where we do not wrap
+   *        the global Data with an Unmodifiable wrapper. Should not be set to {@code true} unless
+   *        incompatibilities or performance issues found. Note, that setting to {@code true} could
+   *        introduce bugs in templates that acquire local references to the global data structure
+   *        and then try to modify them, which is not the intended behavior.
+   */
+  public LocalAndGlobalData(Data local, Data global, boolean allowGlobalDataModification) {
+    super(local, prepareGlobal(global, allowGlobalDataModification));
+    this.local = local;
+  }
+
+  private static Data prepareGlobal(Data global, boolean allowGlobalDataModification) {
+    if (allowGlobalDataModification) {
+      return global;
+    } else {
+      return new UnmodifiableData(global);
+    }
+  }
+
+  @Override
+  public Data createChild(String path) {
+    // We never want to modify the global Data object.
+    return local.createChild(path);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/NativeStringInternStrategy.java b/src/com/google/clearsilver/jsilver/data/NativeStringInternStrategy.java
new file mode 100644
index 0000000..d7d2951
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/NativeStringInternStrategy.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+/**
+ * Implementation of {@link StringInternStrategy} using Java String Pool and {@link String#intern()}
+ * method.
+ */
+public class NativeStringInternStrategy implements StringInternStrategy {
+
+  @Override
+  public String intern(String value) {
+    return value.intern();
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/NestedMapData.java b/src/com/google/clearsilver/jsilver/data/NestedMapData.java
new file mode 100644
index 0000000..617e73b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/NestedMapData.java
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * Represents a hierarchical data set of primitives.
+ * 
+ * This is the JSilver equivalent to ClearSilver's HDF object.
+ * 
+ * This class has no synchronization logic. Follow the same thread-safety semantics as you would for
+ * a java.util.ArrayList (i.e. concurrent reads allowed, but ensure you have exclusive access when
+ * modifying - should not read whilst another thread writes).
+ */
+public class NestedMapData extends AbstractData {
+
+  /**
+   * Number of children a node can have before we bother allocating a HashMap. We currently allocate
+   * the HashMap on the 5th child.
+   */
+  private static final int CHILD_MAP_THRESHOLD = 4;
+
+  private String name;
+  private NestedMapData parent;
+  private final NestedMapData root;
+
+  // Lazily intitialized after CHILD_MAP_THRESHOLD is hit.
+  private Map<String, NestedMapData> children = null;
+  // Number of children
+  private int childCount = 0;
+  // First child (first sibling of children)
+  private NestedMapData firstChild = null;
+  // Last child (last sibling of children)
+  private NestedMapData lastChild = null;
+
+  /**
+   * Single object returned by getChildren(). Constructs ChildrenIterator objects.
+   */
+  private Iterable<NestedMapData> iterableChildren = null;
+
+  // Holds the attributes for this HDF node.
+  private Map<String, String> attributeList = null;
+
+  private String value = null;
+  private NestedMapData symLink = this;
+
+  // Doubly linked list of siblings.
+  private NestedMapData prevSibling = null;
+  private NestedMapData nextSibling = null;
+
+  public NestedMapData() {
+    name = null;
+    parent = null;
+    root = this;
+  }
+
+  protected NestedMapData(String name, NestedMapData parent, NestedMapData root) {
+    this.name = name;
+    this.parent = parent;
+    this.root = root;
+  }
+
+  // ******************* Node creation and removal *******************
+  // Must be kept in sync.
+
+  /**
+   * Creates a child of this node.
+   * 
+   * @param chunk name to give the new child node.
+   * @return the NestedMapData object corresponding to the new node.
+   */
+  protected NestedMapData createChildNode(String chunk) {
+    NestedMapData sym = followSymLinkToTheBitterEnd();
+    NestedMapData data = new NestedMapData(chunk, sym, sym.root);
+
+    // If the parent node's child count is 5 or more and it does not have a
+    // children Hashmap, initialize it now.
+    if (sym.children == null && sym.childCount >= CHILD_MAP_THRESHOLD) {
+      sym.children = new HashMap<String, NestedMapData>();
+      // Copy in existing children.
+      NestedMapData curr = sym.firstChild;
+      while (curr != null) {
+        sym.children.put(curr.getName(), curr);
+        curr = curr.nextSibling;
+      }
+    }
+    // If the parent node has a children map, add the new child node to it.
+    if (sym.children != null) {
+      sym.children.put(chunk, data);
+    }
+
+    data.prevSibling = sym.lastChild;
+    if (sym.lastChild != null) {
+      // Update previous lastChild to point to new child.
+      sym.lastChild.nextSibling = data;
+    } else {
+      // There were no previous children so this is the first.
+      sym.firstChild = data;
+    }
+    sym.lastChild = data;
+
+    sym.childCount++;
+
+    return data;
+  }
+
+  private void severNode() {
+    if (parent == null) {
+      return;
+    }
+    if (parent.children != null) {
+      parent.children.remove(name);
+    }
+    if (prevSibling != null) {
+      prevSibling.nextSibling = nextSibling;
+    } else {
+      parent.firstChild = nextSibling;
+    }
+    if (nextSibling != null) {
+      nextSibling.prevSibling = prevSibling;
+    } else {
+      parent.lastChild = prevSibling;
+    }
+    parent.childCount--;
+
+    // Need to cleal the parent pointer or else if someone has a direct reference to this node
+    // they will get very strange results.
+    parent = null;
+  }
+
+  // ******************* Node data *******************
+
+  /**
+   * Returns the name of this HDF node. The root node has no name, so calling this on the root node
+   * will return null.
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Recursive method that builds the full path to this node via its parent links into the given
+   * StringBuilder.
+   */
+  private void getPathName(StringBuilder sb) {
+    if (parent != null && parent != root) {
+      parent.getPathName(sb);
+      sb.append(".");
+    }
+    String name = getName();
+    if (name != null) {
+      sb.append(name);
+    }
+  }
+
+  /**
+   * Returns the full path to this node via its parent links.
+   */
+  public String getFullPath() {
+    StringBuilder sb = new StringBuilder();
+    getPathName(sb);
+    return sb.toString();
+  }
+
+  /**
+   * Returns the value of this HDF node, or null if this node has no value. Every node in the tree
+   * can have a value, a child, and a next peer.
+   */
+  public String getValue() {
+    return followSymLinkToTheBitterEnd().value;
+  }
+
+  /**
+   * Set the value of this node. Any symlink that may have been set for this node will be replaced.
+   */
+  public void setValue(String value) {
+    // Clearsilver behaviour is to replace any symlink that may already exist
+    // here with the new value, rather than following the symlink.
+    this.symLink = this;
+    this.value = value;
+  }
+
+  // ******************* Attributes *******************
+
+  // We don't expect attributes to be heavily used. They are not used for template parsing.
+
+  public void setAttribute(String key, String value) {
+    if (key == null) {
+      throw new NullPointerException("Attribute name cannot be null.");
+    }
+    if (attributeList == null) {
+      // Do we need to worry about synchronization?
+      attributeList = new HashMap<String, String>();
+    }
+    if (value == null) {
+      attributeList.remove(key);
+    } else {
+      attributeList.put(key, value);
+    }
+  }
+
+  public String getAttribute(String key) {
+    return attributeList == null ? null : attributeList.get(key);
+  }
+
+  public boolean hasAttribute(String key) {
+    return attributeList != null && attributeList.containsKey(key);
+  }
+
+  public int getAttributeCount() {
+    return attributeList == null ? 0 : attributeList.size();
+  }
+
+  public Iterable<Map.Entry<String, String>> getAttributes() {
+    if (attributeList == null) {
+      return Collections.emptySet();
+    }
+    return attributeList.entrySet();
+  }
+
+  // ******************* Children *******************
+
+  /**
+   * Return the root of the tree where the current node lies. If the current node is the root,
+   * return this.
+   */
+  public Data getRoot() {
+    return root;
+  }
+
+  /**
+   * Get the parent node.
+   */
+  public Data getParent() {
+    return parent;
+  }
+
+  /**
+   * Is this the first of its siblings?
+   */
+  @Override
+  public boolean isFirstSibling() {
+    return prevSibling == null;
+  }
+
+  /**
+   * Is this the last of its siblings?
+   */
+  @Override
+  public boolean isLastSibling() {
+    return nextSibling == null;
+  }
+
+  public Data getNextSibling() {
+    return nextSibling;
+  }
+
+  /**
+   * Returns number of child nodes.
+   */
+  @Override
+  public int getChildCount() {
+    return followSymLinkToTheBitterEnd().childCount;
+  }
+
+  /**
+   * Returns children of this node.
+   */
+  @Override
+  public Iterable<? extends Data> getChildren() {
+    if (iterableChildren == null) {
+      iterableChildren = new IterableChildren();
+    }
+    return iterableChildren;
+  }
+
+  /**
+   * Retrieves the object that is the root of the subtree at hdfpath, returning null if the subtree
+   * doesn't exist
+   */
+  public NestedMapData getChild(String path) {
+    NestedMapData current = this;
+    for (int lastDot = 0, nextDot = 0; nextDot != -1 && current != null; lastDot = nextDot + 1) {
+      nextDot = path.indexOf('.', lastDot);
+      String chunk = nextDot == -1 ? path.substring(lastDot) : path.substring(lastDot, nextDot);
+      current = current.followSymLinkToTheBitterEnd().getChildNode(chunk);
+    }
+    return current;
+  }
+
+  /**
+   * Retrieves the HDF object that is the root of the subtree at hdfpath, create the subtree if it
+   * doesn't exist
+   */
+  public NestedMapData createChild(String path) {
+    NestedMapData current = this;
+    for (int lastDot = 0, nextDot = 0; nextDot != -1; lastDot = nextDot + 1) {
+      nextDot = path.indexOf('.', lastDot);
+      String chunk = nextDot == -1 ? path.substring(lastDot) : path.substring(lastDot, nextDot);
+      NestedMapData currentSymLink = current.followSymLinkToTheBitterEnd();
+      current = currentSymLink.getChildNode(chunk);
+      if (current == null) {
+        current = currentSymLink.createChildNode(chunk);
+      }
+    }
+    return current;
+  }
+
+  /**
+   * Non-recursive method that only returns a Data node if this node has a child whose name matches
+   * the specified name.
+   * 
+   * @param name String containing the name of the child to look for.
+   * @return a Data node that is the child of this node and named 'name', otherwise {@code null}.
+   */
+  private NestedMapData getChildNode(String name) {
+    NestedMapData sym = followSymLinkToTheBitterEnd();
+    if (sym.getChildCount() == 0) {
+      // No children. Just return null.
+      return null;
+    }
+    if (sym.children != null) {
+      // children map exists. Look it up there.
+      return sym.children.get(name);
+    }
+    // Iterate through linked list of children and look for a name match.
+    NestedMapData curr = sym.firstChild;
+    while (curr != null) {
+      if (curr.getName().equals(name)) {
+        return curr;
+      }
+      curr = curr.nextSibling;
+    }
+    return null;
+  }
+
+  /**
+   * Remove the specified subtree.
+   */
+  public void removeTree(String path) {
+    NestedMapData removed = getChild(path);
+    if (removed != null) {
+      removed.severNode();
+    }
+  }
+
+  private NestedMapData followSymLinkToTheBitterEnd() {
+    NestedMapData current;
+    for (current = this; current.symLink != current; current = current.symLink);
+    return current;
+  }
+
+  // ******************* Symbolic links *******************
+
+  /**
+   * Set the source node to be a symbolic link to the destination.
+   */
+  public void setSymlink(String sourcePath, String destinationPath) {
+    setSymlink(sourcePath, createChild(destinationPath));
+  }
+
+  /**
+   * Set the source node to be a symbolic link to the destination.
+   */
+  public void setSymlink(String sourcePath, Data destination) {
+    createChild(sourcePath).setSymlink(destination);
+  }
+
+  /**
+   * Set this node to be a symbolic link to another node.
+   */
+  public void setSymlink(Data symLink) {
+    if (symLink instanceof NestedMapData) {
+      this.symLink = (NestedMapData) symLink;
+    } else {
+      String errorMessage =
+          "Cannot set symlink of incompatible Data type: " + symLink.getClass().getName();
+      if (symLink instanceof ChainedData) {
+        errorMessage +=
+            "\nOther type is ChainedData indicating there are "
+                + "multiple valid Data nodes for the path: " + symLink.getFullPath();
+      }
+      throw new IllegalArgumentException(errorMessage);
+    }
+  }
+
+  /**
+   * Retrieve the symbolic link this node points to. Will return reference to self if not a symlink.
+   */
+  public Data getSymlink() {
+    return symLink;
+  }
+
+  // ************************ Copy *************************
+
+  public void copy(String toPath, Data from) {
+    if (toPath == null) {
+      throw new NullPointerException("Invalid copy destination path");
+    }
+    if (from == null) {
+      // Is this a nop or should we throw an error?
+      return;
+    }
+    Data to = createChild(toPath);
+    to.copy(from);
+  }
+
+  public void copy(Data from) {
+    if (from == null) {
+      // Is this a nop or should we throw an error?
+      return;
+    }
+    // Clear any existing symlink.
+    this.symLink = this;
+
+    // Clear any existing attributes and copy the ones from the source.
+    if (this.attributeList != null) {
+      this.attributeList.clear();
+    }
+    for (Map.Entry<String, String> attribute : from.getAttributes()) {
+      setAttribute(attribute.getKey(), attribute.getValue());
+    }
+
+    // If the source node was a symlink, just copy the link over and we're done.
+    if (from.getSymlink() != from) {
+      setSymlink(from.getSymlink());
+      return;
+    }
+
+    // Copy over the node's value.
+    setValue(from.getValue());
+
+    // For each source child, create a new child with the same name and recurse.
+    for (Data fromChild : from.getChildren()) {
+      Data toChild = createChild(fromChild.getName());
+      toChild.copy(fromChild);
+    }
+  }
+
+  /**
+   * Write out the String representation of this HDF node.
+   */
+  public void write(Appendable out, int indent) throws IOException {
+    if (symLink != this) {
+      indent(out, indent);
+      writeNameAttrs(out);
+      out.append(" : ").append(symLink.getFullPath()).append('\n');
+      return;
+    }
+    if (getValue() != null) {
+      indent(out, indent);
+      writeNameAttrs(out);
+      if (getValue().contains("\n")) {
+        writeMultiline(out);
+      } else {
+        out.append(" = ").append(getValue()).append('\n');
+      }
+    }
+    if (getChildCount() > 0) {
+      int childIndent = indent;
+      if (this != root) {
+        indent(out, indent);
+        writeNameAttrs(out);
+        out.append(" {\n");
+        childIndent++;
+      }
+      for (Data child : getChildren()) {
+        child.write(out, childIndent);
+      }
+      if (this != root) {
+        indent(out, indent);
+        out.append("}\n");
+      }
+    }
+  }
+
+  /**
+   * Here we optimize the structure for long-term use. We call intern() on all Strings to reduce the
+   * copies of the same string that appear
+   */
+  @Override
+  public void optimize() {
+    name = name == null ? null : name.intern();
+    value = value == null ? null : value.intern();
+    if (attributeList != null) {
+      Map<String, String> newList = new HashMap<String, String>(attributeList.size());
+      for (Map.Entry<String, String> entry : attributeList.entrySet()) {
+        String key = entry.getKey();
+        String value = entry.getValue();
+        key = key == null ? null : key.intern();
+        value = value == null ? null : value.intern();
+        newList.put(key, value);
+      }
+      attributeList = newList;
+    }
+    for (NestedMapData child = firstChild; child != null; child = child.nextSibling) {
+      child.optimize();
+    }
+  }
+
+  private void writeMultiline(Appendable out) throws IOException {
+    String marker = "EOM";
+    while (getValue().contains(marker)) {
+      marker += System.nanoTime() % 10;
+    }
+    out.append(" << ").append(marker).append('\n').append(getValue());
+    if (!getValue().endsWith("\n")) {
+      out.append('\n');
+    }
+    out.append(marker).append('\n');
+  }
+
+  private void indent(Appendable out, int indent) throws IOException {
+    for (int i = 0; i < indent; i++) {
+      out.append("  ");
+    }
+  }
+
+  private void writeNameAttrs(Appendable out) throws IOException {
+    // Output name
+    out.append(getName());
+    if (attributeList != null && !attributeList.isEmpty()) {
+      // Output parameters.
+      out.append(" [");
+      boolean first = true;
+      for (Map.Entry<String, String> attr : attributeList.entrySet()) {
+        if (first) {
+          first = false;
+        } else {
+          out.append(", ");
+        }
+        out.append(attr.getKey());
+        if (attr.getValue().equals("1")) {
+          continue;
+        }
+        out.append(" = \"");
+        writeAttributeValue(out, attr.getValue());
+        out.append('"');
+      }
+      out.append(']');
+    }
+  }
+
+  // Visible for testing
+  static void writeAttributeValue(Appendable out, String value) throws IOException {
+    for (int i = 0; i < value.length(); i++) {
+      char c = value.charAt(i);
+      switch (c) {
+        case '"':
+          out.append("\\\"");
+          break;
+        case '\n':
+          out.append("\\n");
+          break;
+        case '\t':
+          out.append("\\t");
+          break;
+        case '\\':
+          out.append("\\\\");
+          break;
+        case '\r':
+          out.append("\\r");
+          break;
+        default:
+          out.append(c);
+      }
+    }
+  }
+
+  /**
+   * A single instance of this is created per NestedMapData object. Its single method returns an
+   * iterator over the children of this node.
+   * <p>
+   * Note: This returns an iterator that starts with the first child at the time of iterator() being
+   * called, not when this Iterable object was handed to the code. This might result in slightly
+   * unexpected behavior if the children list is modified between when getChildren() is called and
+   * iterator is called on the returned object but this should not be an issue in practice as
+   * iterator is usually called immediately after getChildren().
+   * 
+   */
+  private class IterableChildren implements Iterable<NestedMapData> {
+    public Iterator<NestedMapData> iterator() {
+      return new ChildrenIterator(followSymLinkToTheBitterEnd().firstChild);
+    }
+  }
+
+  /**
+   * Iterator implementation for children. We do not check for concurrent modification like in other
+   * Collection iterators.
+   */
+  private static class ChildrenIterator implements Iterator<NestedMapData> {
+    NestedMapData current;
+    NestedMapData next;
+
+    ChildrenIterator(NestedMapData first) {
+      next = first;
+      current = null;
+    }
+
+    public boolean hasNext() {
+      return next != null;
+    }
+
+    public NestedMapData next() {
+      if (next == null) {
+        throw new NoSuchElementException();
+      }
+      current = next;
+      next = next.nextSibling;
+      return current;
+    }
+
+    public void remove() {
+      if (current == null) {
+        throw new NoSuchElementException();
+      }
+      current.severNode();
+      current = null;
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/NewHdfParser.java b/src/com/google/clearsilver/jsilver/data/NewHdfParser.java
new file mode 100644
index 0000000..e9970aa
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/NewHdfParser.java
@@ -0,0 +1,702 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Stack;
+
+/**
+ * Parser for HDF based on the following grammar by Brandon Long.
+ * 
+ * COMMAND := (INCLUDE | COMMENT | HDF_SET | HDF_DESCEND | HDF_ASCEND ) INCLUDE := #include
+ * "FILENAME" EOL COMMENT := # .* EOL HDF_DESCEND := HDF_NAME_ATTRS { EOL HDF_ASCEND := } EOL
+ * HDF_SET := (HDF_ASSIGN | HDF_MULTILINE_ASSIGN | HDF_COPY | HDF_LINK) HDF_ASSIGN := HDF_NAME_ATTRS
+ * = .* EOL HDF_MULTILINE_ASSIGN := HDF_NAME_ATTRS << EOM_MARKER EOL (.* EOL)* EOM_MARKER EOL
+ * HDF_COPY := HDF_NAME_ATTRS := HDF_NAME EOL HDF_LINK := HDF_NAME_ATTRS : HDF_NAME EOL
+ * HDF_NAME_ATTRS := (HDF_NAME | HDF_NAME [HDF_ATTRS]) HDF_ATTRS := (HDF_ATTR | HDF_ATTR, HDF_ATTRS)
+ * HDF_ATTR := (HDF_ATTR_KEY | HDF_ATTR_KEY = [^\s,\]]+ | HDF_ATTR_KEY = DQUOTED_STRING)
+ * HDF_ATTR_KEY := [0-9a-zA-Z]+ DQUOTED_STRING := "([^\\"]|\\[ntr]|\\.)*" HDF_NAME := (HDF_SUB_NAME
+ * | HDF_SUB_NAME\.HDF_NAME) HDF_SUB_NAME := [0-9a-zA-Z_]+ EOM_MARKER := \S.*\S EOL := \n
+ */
+public class NewHdfParser implements Parser {
+
+  private final StringInternStrategy internStrategy;
+
+  /**
+   * Special exception used to detect when we unexpectedly run out of characters on the line.
+   */
+  private static class OutOfCharsException extends Exception {}
+
+  /**
+   * Object used to hold the name and attributes of an HDF node before we are ready to commit it to
+   * the Data object.
+   */
+  private static class HdfNameAttrs {
+    String name;
+    ArrayList<String> attrs = null;
+    int endOfSequence;
+
+    void reset(String newname) {
+      // TODO: think about moving interning here instead of parser code
+      this.name = newname;
+      if (attrs != null) {
+        attrs.clear();
+      }
+      endOfSequence = 0;
+    }
+
+    void addAttribute(String key, String value) {
+      if (attrs == null) {
+        attrs = new ArrayList<String>(10);
+      }
+      attrs.ensureCapacity(attrs.size() + 2);
+      // TODO: think about moving interning here instead of parser code
+      attrs.add(key);
+      attrs.add(value);
+    }
+
+    Data toData(Data data) {
+      Data child = data.createChild(name);
+      if (attrs != null) {
+        Iterator<String> it = attrs.iterator();
+        while (it.hasNext()) {
+          String key = it.next();
+          String value = it.next();
+          child.setAttribute(key, value);
+        }
+      }
+      return child;
+    }
+  }
+
+  static final String UNNAMED_INPUT = "[UNNAMED_INPUT]";
+
+  /**
+   * State information that we pass through the parse methods. Allows parser to be reentrant as all
+   * the state is passed through method calls.
+   */
+  static class ParseState {
+    final Stack<Data> context = new Stack<Data>();
+    final Data output;
+    final LineNumberReader lineReader;
+    final ErrorHandler errorHandler;
+    final ResourceLoader resourceLoader;
+    final NewHdfParser hdfParser;
+    final boolean ignoreAttributes;
+    final HdfNameAttrs hdfNameAttrs;
+    final UniqueStack<String> includeStack;
+    final String parsedFileName;
+
+    String line;
+    Data currentNode;
+
+    private ParseState(Data output, LineNumberReader lineReader, ErrorHandler errorHandler,
+        ResourceLoader resourceLoader, NewHdfParser hdfParser, String parsedFileName,
+        boolean ignoreAttributes, HdfNameAttrs hdfNameAttrs, UniqueStack<String> includeStack) {
+      this.lineReader = lineReader;
+      this.errorHandler = errorHandler;
+      this.output = output;
+      currentNode = output;
+      this.resourceLoader = resourceLoader;
+      this.hdfParser = hdfParser;
+      this.parsedFileName = parsedFileName;
+      this.ignoreAttributes = ignoreAttributes;
+      this.hdfNameAttrs = hdfNameAttrs;
+      this.includeStack = includeStack;
+    }
+
+    public static ParseState createNewParseState(Data output, Reader reader,
+        ErrorHandler errorHandler, ResourceLoader resourceLoader, NewHdfParser hdfParser,
+        String parsedFileName, boolean ignoreAttributes) {
+
+      if (parsedFileName == null) {
+        parsedFileName = UNNAMED_INPUT;
+      }
+      UniqueStack<String> includeStack = new UniqueStack<String>();
+      includeStack.push(parsedFileName);
+
+      return new ParseState(output, new LineNumberReader(reader), errorHandler, resourceLoader,
+          hdfParser, parsedFileName, ignoreAttributes, new HdfNameAttrs(), includeStack);
+    }
+
+    public static ParseState createParseStateForIncludedFile(ParseState originalState,
+        String includeFileName, Reader includeFileReader) {
+      return new ParseState(originalState.output, new LineNumberReader(includeFileReader),
+          originalState.errorHandler, originalState.resourceLoader, originalState.hdfParser,
+          originalState.parsedFileName, originalState.ignoreAttributes, new HdfNameAttrs(),
+          originalState.includeStack);
+    }
+  }
+
+
+  /**
+   * Constructor for {@link NewHdfParser}.
+   * 
+   * @param internPool - {@link StringInternStrategy} instance used to optimize the HDF parsing.
+   */
+  public NewHdfParser(StringInternStrategy internPool) {
+    this.internStrategy = internPool;
+  }
+
+  private static class NewHdfParserFactory implements ParserFactory {
+    private final StringInternStrategy stringInternStrategy;
+
+    public NewHdfParserFactory(StringInternStrategy stringInternStrategy) {
+      this.stringInternStrategy = stringInternStrategy;
+    }
+
+    @Override
+    public Parser newInstance() {
+      return new NewHdfParser(stringInternStrategy);
+    }
+  }
+
+  /**
+   * Creates a {@link ParserFactory} instance.
+   * 
+   * <p>
+   * Provided {@code stringInternStrategy} instance will be used by shared all {@link Parser}
+   * objects created by the factory and used to optimize the HDF parsing process by reusing the
+   * String for keys and values.
+   * 
+   * @param stringInternStrategy - {@link StringInternStrategy} instance used to optimize the HDF
+   *        parsing.
+   * @return an instance of {@link ParserFactory} implementation.
+   */
+  public static ParserFactory newFactory(StringInternStrategy stringInternStrategy) {
+    return new NewHdfParserFactory(stringInternStrategy);
+  }
+
+  public void parse(Reader reader, Data output, Parser.ErrorHandler errorHandler,
+      ResourceLoader resourceLoader, String dataFileName, boolean ignoreAttributes)
+      throws IOException {
+
+    parse(ParseState.createNewParseState(output, reader, errorHandler, resourceLoader, this,
+        dataFileName, ignoreAttributes));
+  }
+
+  private void parse(ParseState state) throws IOException {
+    while ((state.line = state.lineReader.readLine()) != null) {
+      String seq = stripWhitespace(state.line);
+      try {
+        parseCommand(seq, state);
+      } catch (OutOfCharsException e) {
+        reportError(state, "End of line was prematurely reached. Parse error.");
+      }
+    }
+  }
+
+  private static final String INCLUDE_WS = "#include ";
+
+  private void parseCommand(String seq, ParseState state) throws IOException, OutOfCharsException {
+    if (seq.length() == 0) {
+      // Empty line.
+      return;
+    }
+    if (charAt(seq, 0) == '#') {
+      // If there isn't a match on include then this is a comment and we do nothing.
+      if (matches(seq, 0, INCLUDE_WS)) {
+        // This is an include command
+        int start = skipLeadingWhitespace(seq, INCLUDE_WS.length());
+        parseInclude(seq, start, state);
+      }
+      return;
+    } else if (charAt(seq, 0) == '}') {
+      if (skipLeadingWhitespace(seq, 1) != seq.length()) {
+        reportError(state, "Extra chars after '}'");
+        return;
+      }
+      handleAscend(state);
+    } else {
+      parseHdfElement(seq, state);
+    }
+  }
+
+  private void parseInclude(String seq, int start, ParseState state) throws IOException,
+      OutOfCharsException {
+    int end = seq.length();
+    if (charAt(seq, start) == '"') {
+      if (charAt(seq, end - 1) == '"') {
+        start++;
+        end--;
+      } else {
+        reportError(state, "Missing '\"' at end of include");
+        return;
+      }
+    }
+    handleInclude(seq.substring(start, end), state);
+  }
+
+  private static final int NO_MATCH = -1;
+
+  private void parseHdfElement(String seq, ParseState state) throws IOException,
+      OutOfCharsException {
+    // Re-use a single element to avoid repeated allocations/trashing (serious
+    // performance impact, 5% of real service performance)
+    HdfNameAttrs element = state.hdfNameAttrs;
+    if (!parseHdfNameAttrs(element, seq, 0, state)) {
+      return;
+    }
+    int index = skipLeadingWhitespace(seq, element.endOfSequence);
+    switch (charAt(seq, index)) {
+      case '{':
+        // Descend
+        if (index + 1 != seq.length()) {
+          reportError(state, "No characters expected after '{'");
+          return;
+        }
+        handleDescend(state, element);
+        return;
+      case '=':
+        // Assignment
+        index = skipLeadingWhitespace(seq, index + 1);
+        String value = internStrategy.intern(seq.substring(index, seq.length()));
+        handleAssign(state, element, value);
+        return;
+      case ':':
+        if (charAt(seq, index + 1) == '=') {
+          // Copy
+          index = skipLeadingWhitespace(seq, index + 2);
+          String src = parseHdfName(seq, index);
+          if (src == null) {
+            reportError(state, "Invalid HDF name");
+            return;
+          }
+          if (index + src.length() != seq.length()) {
+            reportError(state, "No characters expected after '{'");
+            return;
+          }
+          handleCopy(state, element, src);
+        } else {
+          // Link
+          index = skipLeadingWhitespace(seq, index + 1);
+          String src = parseHdfName(seq, index);
+          if (src == null) {
+            reportError(state, "Invalid HDF name");
+            return;
+          }
+          if (index + src.length() != seq.length()) {
+            reportError(state, "No characters expected after '{'");
+            return;
+          }
+          handleLink(state, element, src);
+        }
+        return;
+      case '<':
+        if (charAt(seq, index + 1) != '<') {
+          reportError(state, "Expected '<<'");
+        }
+        index = skipLeadingWhitespace(seq, index + 2);
+        String eomMarker = seq.substring(index, seq.length());
+        // TODO: think about moving interning to handleAssign()
+        String multilineValue = internStrategy.intern(parseMultilineValue(state, eomMarker));
+        if (multilineValue == null) {
+          return;
+        }
+        handleAssign(state, element, multilineValue);
+        return;
+      default:
+        reportError(state, "No valid operator");
+        return;
+    }
+  }
+
+  /**
+   * This method parses out an HDF element name and any optional attributes into a caller-supplied
+   * HdfNameAttrs object. It returns a {@code boolean} with whether it succeeded to parse.
+   */
+  private boolean parseHdfNameAttrs(HdfNameAttrs destination, String seq, int index,
+      ParseState state) throws OutOfCharsException {
+    String hdfName = parseHdfName(seq, index);
+    if (hdfName == null) {
+      reportError(state, "Invalid HDF name");
+      return false;
+    }
+    destination.reset(hdfName);
+    index = skipLeadingWhitespace(seq, index + hdfName.length());
+    int end = parseAttributes(seq, index, state, destination);
+    if (end == NO_MATCH) {
+      // Error already reported below.
+      return false;
+    } else {
+      destination.endOfSequence = end;
+      return true;
+    }
+  }
+
+  /**
+   * Parses a valid hdf path name.
+   */
+  private String parseHdfName(String seq, int index) throws OutOfCharsException {
+    int end = index;
+    while (end < seq.length() && isHdfNameChar(charAt(seq, end))) {
+      end++;
+    }
+    if (end == index) {
+      return null;
+    }
+    return internStrategy.intern(seq.substring(index, end));
+  }
+
+  /**
+   * Looks for optional attributes and adds them to the HdfNameAttrs object passed into the method.
+   */
+  private int parseAttributes(String seq, int index, ParseState state, HdfNameAttrs element)
+      throws OutOfCharsException {
+    if (charAt(seq, index) != '[') {
+      // No attributes to parse
+      return index;
+    }
+    index = skipLeadingWhitespace(seq, index + 1);
+
+    // If we don't care about attributes, just skip over them.
+    if (state.ignoreAttributes) {
+      while (charAt(seq, index) != ']') {
+        index++;
+      }
+      return index + 1;
+    }
+
+    boolean first = true;
+    do {
+      if (first) {
+        first = false;
+      } else if (charAt(seq, index) == ',') {
+        index = skipLeadingWhitespace(seq, index + 1);
+      } else {
+        reportError(state, "Error parsing attribute list");
+      }
+      index = parseAttribute(seq, index, state, element);
+      if (index == NO_MATCH) {
+        // reportError called by parseAttribute already.
+        return NO_MATCH;
+      }
+      index = skipLeadingWhitespace(seq, index);
+    } while (charAt(seq, index) != ']');
+    return index + 1;
+  }
+
+  private static final String DEFAULT_ATTR_VALUE = "1";
+
+  /**
+   * Parse out a single HDF attribute. If there is no explicit value, use default value of "1" like
+   * in C clearsilver. Returns NO_MATCH if it fails to parse an attribute.
+   */
+  private int parseAttribute(String seq, int index, ParseState state, HdfNameAttrs element)
+      throws OutOfCharsException {
+    int end = parseAttributeKey(seq, index);
+    if (index == end) {
+      reportError(state, "No valid attribute key");
+      return NO_MATCH;
+    }
+    String attrKey = internStrategy.intern(seq.substring(index, end));
+    index = skipLeadingWhitespace(seq, end);
+    if (charAt(seq, index) != '=') {
+      // No value for this attribute key. Use default value of "1"
+      element.addAttribute(attrKey, DEFAULT_ATTR_VALUE);
+      return index;
+    }
+    // We need to parse out the attribute value.
+    index = skipLeadingWhitespace(seq, index + 1);
+    if (charAt(seq, index) == '"') {
+      index++;
+      StringBuilder sb = new StringBuilder();
+      end = parseQuotedAttributeValue(seq, index, sb);
+      if (end == NO_MATCH) {
+        reportError(state, "Unable to parse quoted attribute value");
+        return NO_MATCH;
+      }
+      String attrValue = internStrategy.intern(sb.toString());
+      element.addAttribute(attrKey, attrValue);
+      end++;
+    } else {
+      // Simple attribute that has no whitespace.
+      String attrValue = parseAttributeValue(seq, index, state);
+      if (attrValue == null || attrValue.length() == 0) {
+        reportError(state, "No attribute for key " + attrKey);
+        return NO_MATCH;
+      }
+
+      attrValue = internStrategy.intern(attrValue);
+      element.addAttribute(attrKey, attrValue);
+      end = index + attrValue.length();
+    }
+    return end;
+  }
+
+  /**
+   * Returns the range in the sequence starting at start that corresponds to a valid attribute key.
+   */
+  private int parseAttributeKey(String seq, int index) throws OutOfCharsException {
+    while (isAlphaNumericChar(charAt(seq, index))) {
+      index++;
+    }
+    return index;
+  }
+
+  /**
+   * Parses a quoted attribute value. Unescapes octal characters and \n, \r, \t, \", etc.
+   */
+  private int parseQuotedAttributeValue(String seq, int index, StringBuilder sb)
+      throws OutOfCharsException {
+    char c;
+    while ((c = charAt(seq, index)) != '"') {
+      if (c == '\\') {
+        // Escaped character. Look for 1 to 3 digits in a row as octal or n,t,r.
+        index++;
+        char next = charAt(seq, index);
+        if (isNumericChar(next)) {
+          // Parse the next 1 to 3 characters if they are digits. Treat it as an octal code.
+          int val = next - '0';
+          if (isNumericChar(charAt(seq, index + 1))) {
+            index++;
+            val = val * 8 + (charAt(seq, index) - '0');
+            if (isNumericChar(charAt(seq, index + 1))) {
+              index++;
+              val = val * 8 + (charAt(seq, index) - '0');
+            }
+          }
+          c = (char) val;
+        } else if (next == 'n') {
+          c = '\n';
+        } else if (next == 't') {
+          c = '\t';
+        } else if (next == 'r') {
+          c = '\r';
+        } else {
+          // Regular escaped char like " or /
+          c = next;
+        }
+      }
+      sb.append(c);
+      index++;
+    }
+    return index;
+  }
+
+  /**
+   * Parses a simple attribute value that cannot have any whitespace or specific punctuation
+   * reserved by the HDF grammar.
+   */
+  private String parseAttributeValue(String seq, int index, ParseState state)
+      throws OutOfCharsException {
+    int end = index;
+    char c = charAt(seq, end);
+    while (c != ',' && c != ']' && c != '"' && !Character.isWhitespace(c)) {
+      end++;
+      c = charAt(seq, end);
+    }
+    return seq.substring(index, end);
+  }
+
+  private String parseMultilineValue(ParseState state, String eomMarker) throws IOException {
+    StringBuilder sb = new StringBuilder(256);
+    String line;
+    while ((line = state.lineReader.readLine()) != null) {
+      if (line.startsWith(eomMarker)
+          && skipLeadingWhitespace(line, eomMarker.length()) == line.length()) {
+        return sb.toString();
+      } else {
+        sb.append(line).append('\n');
+      }
+    }
+    reportError(state, "EOM " + eomMarker + " never found");
+    return null;
+  }
+
+  // //////////////////////////////////////////////////////////////////////////
+  //
+  // Handlers
+
+  private void handleDescend(ParseState state, HdfNameAttrs element) {
+    Data child = handleNodeCreation(state.currentNode, element);
+    state.context.push(state.currentNode);
+    state.currentNode = child;
+  }
+
+  private Data handleNodeCreation(Data node, HdfNameAttrs element) {
+    return element.toData(node);
+  }
+
+  private void handleAssign(ParseState state, HdfNameAttrs element, String value) {
+    // TODO: think about moving interning here
+    Data child = handleNodeCreation(state.currentNode, element);
+    child.setValue(value);
+  }
+
+  private void handleCopy(ParseState state, HdfNameAttrs element, String srcName) {
+    Data child = handleNodeCreation(state.currentNode, element);
+    Data src = state.output.getChild(srcName);
+    if (src != null) {
+      child.setValue(src.getValue());
+    } else {
+      child.setValue("");
+    }
+  }
+
+  private void handleLink(ParseState state, HdfNameAttrs element, String srcName) {
+    Data child = handleNodeCreation(state.currentNode, element);
+    child.setSymlink(state.output.createChild(srcName));
+  }
+
+  private void handleAscend(ParseState state) {
+    if (state.context.isEmpty()) {
+      reportError(state, "Too many '}'");
+      return;
+    }
+    state.currentNode = state.context.pop();
+  }
+
+  private void handleInclude(String seq, ParseState state) throws IOException {
+    String includeFileName = internStrategy.intern(seq);
+
+    // Load the file
+    Reader reader = state.resourceLoader.open(includeFileName);
+    if (reader == null) {
+      reportError(state, "Unable to find file " + includeFileName);
+      return;
+    }
+
+    // Check whether we are in include loop
+    if (!state.includeStack.push(includeFileName)) {
+      reportError(state, createIncludeStackTraceMessage(state.includeStack, includeFileName));
+      return;
+    }
+
+    // Parse the file
+    state.hdfParser.parse(ParseState
+        .createParseStateForIncludedFile(state, includeFileName, reader));
+
+    if (!includeFileName.equals(state.includeStack.pop())) {
+      // Include stack trace is corrupted
+      throw new IllegalStateException("Unable to find on include stack: " + includeFileName);
+    }
+  }
+
+  private String createIncludeStackTraceMessage(UniqueStack<String> includeStack,
+      String includeFileName) {
+    StringBuilder message = new StringBuilder();
+    message.append("File included twice: ");
+    message.append(includeFileName);
+
+    message.append(" Include stack: ");
+    for (String fileName : includeStack) {
+      message.append(fileName);
+      message.append(" -> ");
+    }
+    message.append(includeFileName);
+    return message.toString();
+  }
+
+  // /////////////////////////////////////////////////////////////////////////
+  //
+  // Character values
+
+  private static boolean isNumericChar(char c) {
+    if ('0' <= c && c <= '9') {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  private static boolean isAlphaNumericChar(char c) {
+    if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  private static boolean isHdfNameChar(char c) {
+    if (isAlphaNumericChar(c) || c == '_' || c == '.') {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  private static String stripWhitespace(String seq) {
+    int start = skipLeadingWhitespace(seq, 0);
+    int end = seq.length() - 1;
+    while (end > start && Character.isWhitespace(seq.charAt(end))) {
+      --end;
+    }
+    if (start == 0 && end == seq.length() - 1) {
+      return seq;
+    } else {
+      return seq.substring(start, end + 1);
+    }
+  }
+
+  private static int skipLeadingWhitespace(String seq, int index) {
+    while (index < seq.length() && Character.isWhitespace(seq.charAt(index))) {
+      index++;
+    }
+    return index;
+  }
+
+  /**
+   * Determines if a character sequence appears in the given sequence starting at a specified index.
+   * 
+   * @param seq the sequence that we want to see if it contains the string match.
+   * @param start the index into seq where we want to check for match
+   * @param match the String we want to look for in the sequence.
+   * @return {@code true} if the string match appears in seq starting at the index start, {@code
+   *         false} otherwise.
+   */
+  private static boolean matches(String seq, int start, String match) {
+    if (seq.length() - start < match.length()) {
+      return false;
+    }
+    for (int i = 0; i < match.length(); i++) {
+      if (match.charAt(i) != seq.charAt(start + i)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Reads the character at the specified index in the given String. Throws an exception to be
+   * caught above if the index is out of range.
+   */
+  private static char charAt(String seq, int index) throws OutOfCharsException {
+    if (0 <= index && index < seq.length()) {
+      return seq.charAt(index);
+    } else {
+      throw new OutOfCharsException();
+    }
+  }
+
+
+  private static void reportError(ParseState state, String errorMessage) {
+    if (state.errorHandler != null) {
+      state.errorHandler.error(state.lineReader.getLineNumber(), state.line, state.parsedFileName,
+          errorMessage);
+    } else {
+      throw new RuntimeException("Parse Error on line " + state.lineReader.getLineNumber() + ": "
+          + errorMessage + " : " + state.line);
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/NoOpStringInternStrategy.java b/src/com/google/clearsilver/jsilver/data/NoOpStringInternStrategy.java
new file mode 100644
index 0000000..d033c42
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/NoOpStringInternStrategy.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+/**
+ * Pass-through implementation of {@link StringInternStrategy}.
+ */
+public class NoOpStringInternStrategy implements StringInternStrategy {
+
+  @Override
+  public String intern(String value) {
+    return value;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/Parser.java b/src/com/google/clearsilver/jsilver/data/Parser.java
new file mode 100644
index 0000000..4b401aa
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/Parser.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+
+import java.io.Reader;
+import java.io.IOException;
+
+/**
+ * Parses data in HierachicalDataFormat (HDF), generating callbacks for data encountered in the
+ * stream.
+ */
+public interface Parser {
+
+  /** Called whenever an error occurs. */
+  public interface ErrorHandler {
+    /**
+     * Report an error to the ErrorHandler.
+     * 
+     * @param line number of the line where error occurred. The value of -1 represents line number
+     *        unknown
+     * @param lineContent text of the line with error
+     * @param fileName name of the file in which the error occurred
+     * @param errorMessage description of an error
+     */
+    void error(int line, String lineContent, String fileName, String errorMessage);
+  }
+
+  /**
+   * Reads in a stream of characters and parses data from it, putting it into the given Data object.
+   * 
+   * @param reader Reader used to read in the formatted data.
+   * @param output Data object that the read data structure will be dumped into.
+   * @param errorHandler Error callback to be called on any error.
+   * @param resourceLoader ResourceLoader to use to read in included files.
+   * @param dataFileName Name of a file that is read with reader. It is needed for the purpose of
+   *        handling include loops and error messages.
+   * @param ignoreAttributes whether to store parsed HDF attributes in the Data object or not.
+   * @throws IOException when errors occur reading input.
+   */
+  void parse(Reader reader, Data output, ErrorHandler errorHandler, ResourceLoader resourceLoader,
+      String dataFileName, boolean ignoreAttributes) throws IOException;
+}
diff --git a/src/com/google/clearsilver/jsilver/data/ParserFactory.java b/src/com/google/clearsilver/jsilver/data/ParserFactory.java
new file mode 100644
index 0000000..cd6637f
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/ParserFactory.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+/**
+ * Interface for allowing multiple implementations of HdfParser.
+ */
+public interface ParserFactory {
+
+  /**
+   * Returns a new HdfParser object.
+   */
+  Parser newInstance();
+}
diff --git a/src/com/google/clearsilver/jsilver/data/StringInternStrategy.java b/src/com/google/clearsilver/jsilver/data/StringInternStrategy.java
new file mode 100644
index 0000000..9db9824
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/StringInternStrategy.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+/**
+ * Encapsulates the {@code WeakInterningPool<String>} functionality with added optimizations. To be
+ * used to optimize the memory usage and garbage collection during text processing.
+ */
+public interface StringInternStrategy {
+  /**
+   * Interns a String object in a pool and returns a String equal to the one provided.
+   * 
+   * <p>
+   * If there exists a String in the pool equal to the provided value then it will be returned.
+   * Otherwise provided String <b>may</b> be interned.
+   * 
+   * <p>
+   * There is no guarantees on when the pool will return the same object as provided. It is possible
+   * that value == intern(value) will never be true.
+   * 
+   * @param value String to be interned
+   * @return a String that is equal to the one provided.
+   */
+  String intern(String value);
+}
diff --git a/src/com/google/clearsilver/jsilver/data/TypeConverter.java b/src/com/google/clearsilver/jsilver/data/TypeConverter.java
new file mode 100644
index 0000000..04ec8a9
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/TypeConverter.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+
+/**
+ * Static methods for converting stuff in a ClearSilver compatible way.
+ */
+public class TypeConverter {
+  private TypeConverter() {}
+
+  private static final String ZERO = "0";
+  private static final String ONE = "1";
+
+  /**
+   * Determines if the given data node exists in a ClearSilver compatible way.
+   */
+  public static boolean exists(Data data) {
+    return data != null && data.getValue() != null;
+  }
+
+  /**
+   * Helper method to safely convert an arbitrary data instance (including null) into a valid
+   * (non-null) string representation.
+   */
+  public static String asString(Data data) {
+    // Non-existent variables become the empty string
+    // (the data instance will return null to us)
+    String value = data != null ? data.getValue() : null;
+    return value != null ? value : "";
+  }
+
+  /**
+   * Parses a non-null string in a ClearSilver compatible way.
+   * 
+   * The is the underlying parsing function which can fail for badly formatted strings. It is really
+   * important that anyone doing parsing of strings calls this function (rather than doing it
+   * themselves).
+   * 
+   * This is an area where JSilver and ClearSilver have some notable differences. ClearSilver relies
+   * on the template compiler to parse strings in the template and a different parser at runtime for
+   * HDF values. JSilver uses the same code for both cases.
+   * 
+   * In ClearSilver HDF: Numbers are parsed sequentially and partial results are returned when an
+   * invalid character is reached. This means that {@code "123abc"} parses to {@code 123}.
+   * 
+   * Additionally, ClearSilver doesn't do hex in HDF values, so {@code "a.b=0x123"} will just
+   * resolve to {@code 0}.
+   * 
+   * In ClearSilver templates: Hex is supported, including negative values.
+   * 
+   * In JSilver: A string must be a complete, valid numeric value for parsing. This means {@code
+   * "123abc"} is invalid and will default to {@code 0}.
+   * 
+   * In JSilver: Positive hex values are supported for both HDF and templates but negative values
+   * aren't. This means a template containing something like "<?cs if:foo == -0xff ?>" will parse
+   * correctly but fail to render.
+   * 
+   * @throws NumberFormatException is the string is badly formatted
+   */
+  public static int parseNumber(String value) throws NumberFormatException {
+    // NOTE: This is likely to be one of the areas we will want to optimize
+    // for speed eventually.
+    if (value.startsWith("0x") || value.startsWith("0X")) {
+      return Integer.parseInt(value.substring(2), 16);
+    } else {
+      return Integer.parseInt(value);
+    }
+  }
+
+  /**
+   * Parses and returns the given string as an integer in a ClearSilver compatible way.
+   */
+  public static int asNumber(String value) {
+    if (value == null || value.isEmpty()) {
+      return 0;
+    }
+    // fast detection for common constants to avoid parsing common values
+    // TODO: Maybe push this down into parseNumber ??
+    if (value.equals(ONE)) {
+      return 1;
+    }
+    if (value.equals(ZERO)) {
+      return 0;
+    }
+    try {
+      return parseNumber(value);
+    } catch (NumberFormatException e) {
+      return 0;
+    }
+  }
+
+  /**
+   * Helper method to safely convert an arbitrary data instance (including null) into a valid
+   * integer representation.
+   */
+  public static int asNumber(Data data) {
+    // Non-existent variables become zero
+    return data != null ? data.getIntValue() : 0;
+  }
+
+  /**
+   * Parses and returns the given string as a boolean in a ClearSilver compatible way.
+   */
+  public static boolean asBoolean(String value) {
+    if (value == null || value.isEmpty()) {
+      return false;
+    }
+    // fast detection for common constants to avoid parsing common values
+    if (value.equals(ONE)) {
+      return true;
+    }
+    if (value.equals(ZERO)) {
+      return false;
+    }
+    
+    // fast detection of any string not starting with '0'
+    if (value.charAt(0) != '0') {
+      return true;
+    }
+    
+    try {
+      return parseNumber(value) != 0;
+    } catch (NumberFormatException e) {
+      // Unlike number parsing, we return a positive value when the
+      // string is badly formatted (it's what clearsilver does).
+      return true;
+    }
+  }
+
+  /**
+   * Helper method to safely convert an arbitrary data instance (including null) into a valid
+   * boolean representation.
+   */
+  public static boolean asBoolean(Data data) {
+    // Non-existent variables become false
+    return data != null ? data.getBooleanValue() : false;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/UniqueStack.java b/src/com/google/clearsilver/jsilver/data/UniqueStack.java
new file mode 100644
index 0000000..76b328a
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/UniqueStack.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+
+/**
+ * The {@code ResourceStack} represents a LIFO stack of unique objects (resources).
+ * 
+ * <p>
+ * An attempt to insert on a stack an object that is already there will fail and result with a
+ * method {@link #push(Object)} returning false.
+ * 
+ * <p>
+ * All provided operations ({@link #pop()} and {@link #push(Object)}) are done in average constant
+ * time.
+ * 
+ * <p>
+ * This class is iterable
+ */
+public class UniqueStack<T> implements Iterable<T> {
+  // Field used for optimization: when only one object was
+  // added we postpone the initialization and use this field.
+  private T firstObject = null;
+
+  // Contains a stack of all stored objects.
+  private LinkedList<T> objectStack = null;
+  // A HashSet allowing quick look-ups on the stack.
+  private HashSet<T> objectsSet = null;
+
+  /**
+   * A wrapper for a {@code Iterator<T>} object that provides immutability.
+   * 
+   * @param <T>
+   */
+  private static class ImmutableIterator<T> implements Iterator<T> {
+    private static final String MODIFICATION_ERROR_MESSAGE =
+        "ResourceStack cannot be modyfied by Iterator.remove()";
+
+    private final Iterator<T> iterator;
+
+    private ImmutableIterator(Iterator<T> iterator) {
+      this.iterator = iterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+      return iterator.hasNext();
+    }
+
+    @Override
+    public T next() {
+      return iterator.next();
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException(MODIFICATION_ERROR_MESSAGE);
+    }
+  }
+
+  /**
+   * Add an object to a stack. Object will be added only if it is not already on the stack.
+   * 
+   * @param object to be added. If it is {@code null} a {@code NullPointerException} will be thrown.
+   * @return true if the object was added successfully
+   */
+  public boolean push(T object) {
+    if (object == null) {
+      throw new NullPointerException();
+    }
+
+    if (objectStack == null) {
+      if (firstObject == null) {
+        firstObject = object;
+        return true;
+      } else {
+        if (firstObject.equals(object)) {
+          return false;
+        }
+        initStackAndSet();
+      }
+    } else {
+      if (objectsSet.contains(object)) {
+        return false;
+      }
+    }
+
+    objectStack.offerLast(object);
+    objectsSet.add(object);
+    return true;
+  }
+
+  private void initStackAndSet() {
+    objectStack = new LinkedList<T>();
+    objectsSet = new HashSet<T>();
+
+    objectStack.offerLast(firstObject);
+    objectsSet.add(firstObject);
+
+    // there is no need for using firstObject pointer anymore
+    firstObject = null;
+  }
+
+  /**
+   * Removes last added object from the stack.
+   * 
+   * @return last added object
+   * @throws NoSuchElementException - if the stack is empty
+   */
+  public T pop() {
+    T returnedValue = null;
+
+    if (isEmpty()) {
+      throw new NoSuchElementException();
+    }
+
+    if (objectStack == null) {
+      returnedValue = firstObject;
+      firstObject = null;
+    } else {
+      returnedValue = objectStack.pollLast();
+      objectsSet.remove(returnedValue);
+    }
+    return returnedValue;
+  }
+
+  /**
+   * Returns {@code true} if this stack contains no elements.
+   * 
+   * @return {@code true} if this stack contains no elements.
+   */
+  public boolean isEmpty() {
+    if (firstObject != null) {
+      return false;
+    }
+    return (objectStack == null || objectStack.isEmpty());
+  }
+
+  @Override
+  public Iterator<T> iterator() {
+    if (objectStack == null) {
+      initStackAndSet();
+    }
+    return new ImmutableIterator<T>(objectStack.iterator());
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/data/UnmodifiableData.java b/src/com/google/clearsilver/jsilver/data/UnmodifiableData.java
new file mode 100644
index 0000000..6b66a57
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/data/UnmodifiableData.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.data;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+
+import java.util.Iterator;
+
+/**
+ * Data wrapper that prevents modifying the delegated Data node or its tree.
+ */
+public class UnmodifiableData extends DelegatedData {
+
+  private static final String MODIFICATION_ERROR_MSG = "Cannot modify this Data object.";
+
+  public UnmodifiableData(Data delegate) {
+    super(delegate);
+  }
+
+  @Override
+  protected DelegatedData newInstance(Data newDelegate) {
+    return newDelegate == null ? null : new UnmodifiableData(newDelegate);
+  }
+
+  @Override
+  public void copy(Data from) {
+    throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+  }
+
+  @Override
+  public void copy(String toPath, Data from) {
+    throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+  }
+
+  /**
+   * {@link #createChild} calls {@link #getChild} and throws {@link UnsupportedOperationException}
+   * if no object was found.
+   */
+  @Override
+  public Data createChild(String path) {
+    // Check if child already exists
+    Data child = getChild(path);
+
+    if (child == null) {
+      // If the child described by path does not exist we throw
+      // an exception as we cannot create a new one.
+      throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+    }
+    return child;
+  }
+
+  protected class UnmodifiableIterator extends DelegatedIterator {
+    UnmodifiableIterator(Iterator<? extends Data> iterator) {
+      super(iterator);
+    }
+
+    public void remove() {
+      throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+    }
+  }
+
+  /**
+   * Override in order to not allow modifying children with remove().
+   */
+  @Override
+  protected Iterator<DelegatedData> newChildIterator() {
+    return new UnmodifiableIterator(getDelegate().getChildren().iterator());
+  }
+
+  // Below we disable modification operations.
+
+  @Override
+  public void setSymlink(String sourcePath, Data destination) {
+    throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+  }
+
+  @Override
+  public void setSymlink(String sourcePath, String destinationPath) {
+    throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+  }
+
+  @Override
+  public void setSymlink(Data symLink) {
+    throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+  }
+
+  @Override
+  public void setAttribute(String key, String value) {
+    throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+  }
+
+  @Override
+  public void removeTree(String path) {
+    throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+  }
+
+  @Override
+  public void setValue(String path, String value) {
+    throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+  }
+
+  @Override
+  public void setValue(String value) {
+    throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+  }
+
+  @Override
+  public void setEscapeMode(EscapeMode mode) {
+    throw new UnsupportedOperationException(MODIFICATION_ERROR_MSG);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/examples/basic/HelloWorld.java b/src/com/google/clearsilver/jsilver/examples/basic/HelloWorld.java
new file mode 100644
index 0000000..33b7ad8
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/examples/basic/HelloWorld.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.examples.basic;
+
+import com.google.clearsilver.jsilver.JSilver;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader;
+
+import java.io.IOException;
+
+/**
+ * Hello world of templates.
+ */
+public class HelloWorld {
+
+  public static void main(String[] args) throws IOException {
+
+    // Load resources (e.g. templates) from classpath, along side this class.
+    JSilver jSilver = new JSilver(new ClassResourceLoader(HelloWorld.class));
+
+    // Set up some data.
+    Data data = jSilver.createData();
+    data.setValue("name.first", "Mr");
+    data.setValue("name.last", "Man");
+
+    // Render template to System.out.
+    jSilver.render("hello-world.cs", data, System.out);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/examples/basic/Iterate.java b/src/com/google/clearsilver/jsilver/examples/basic/Iterate.java
new file mode 100644
index 0000000..fd81413
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/examples/basic/Iterate.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.examples.basic;
+
+import com.google.clearsilver.jsilver.JSilver;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader;
+
+import java.io.IOException;
+
+/**
+ * A template that iterates over some items.
+ */
+public class Iterate {
+
+  public static void main(String[] args) throws IOException {
+
+    // Load resources (e.g. templates) from classpath, along side this class.
+    JSilver jSilver = new JSilver(new ClassResourceLoader(Iterate.class));
+
+    // Set up some data.
+    Data data = jSilver.createData();
+    data.setValue("query", "Fruit");
+    data.setValue("results.0.title", "Banana");
+    data.setValue("results.0.url", "http://banana.com/");
+    data.setValue("results.1.title", "Apple");
+    data.setValue("results.1.url", "http://apple.com/");
+    data.setValue("results.2.title", "Lemon");
+    data.setValue("results.2.url", "http://lemon.com/");
+
+    // Render template to System.out.
+    jSilver.render("iterate.cs", data, System.out);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/examples/basic/JSilverTest.java b/src/com/google/clearsilver/jsilver/examples/basic/JSilverTest.java
new file mode 100644
index 0000000..eb1042b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/examples/basic/JSilverTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.examples.basic;
+
+import com.google.clearsilver.jsilver.JSilver;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader;
+
+import java.io.IOException;
+
+/**
+ * Command-line template renderer.
+ * 
+ * Usage: JSilverTest file.cs [file.hdf file2.hdf ...]
+ */
+public class JSilverTest {
+  public static void main(String[] args) throws IOException {
+    if (args.length < 1) {
+      System.out.println("Usage: JSilverTest file.cs [file.hdf file2.hdf ...]");
+      System.exit(1);
+    }
+
+    // Load resources from filesystem, relative to the current directory.
+    JSilver jSilver = new JSilver(new FileSystemResourceLoader("."));
+
+    // Load data.
+    Data data = jSilver.createData();
+    for (int i = 1; i < args.length; i++) {
+      jSilver.loadData(args[i], data);
+    }
+
+    // Render template to System.out.
+    jSilver.render(args[0], data, System.out);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/examples/basic/hello-world.cs b/src/com/google/clearsilver/jsilver/examples/basic/hello-world.cs
new file mode 100644
index 0000000..4bb1831
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/examples/basic/hello-world.cs
@@ -0,0 +1,2 @@
+Hello <?cs var:name.first ?> <?cs var:name.last ?>.
+How are you today?
diff --git a/src/com/google/clearsilver/jsilver/examples/basic/iterate.cs b/src/com/google/clearsilver/jsilver/examples/basic/iterate.cs
new file mode 100644
index 0000000..f2a7703
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/examples/basic/iterate.cs
@@ -0,0 +1,6 @@
+Query : <?cs var:query ?>
+
+Results:
+  <?cs each:result = results ?>
+    <?cs var:result.title ?> ---> <?cs var:result.url ?>
+  <?cs /each ?>
diff --git a/src/com/google/clearsilver/jsilver/exceptions/ExceptionUtil.java b/src/com/google/clearsilver/jsilver/exceptions/ExceptionUtil.java
new file mode 100644
index 0000000..23ea0cc
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/exceptions/ExceptionUtil.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.exceptions;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Class to hold utilities related to exceptions used by JSilver code.
+ */
+public final class ExceptionUtil {
+  private ExceptionUtil() {}
+
+  /**
+   * Determines if the given exception was caused by an exception equivalent to FileNotFound.
+   */
+  public static boolean isFileNotFoundException(Throwable th) {
+    while (th != null) {
+      if (th instanceof JSilverTemplateNotFoundException || th instanceof FileNotFoundException) {
+        return true;
+      }
+      th = th.getCause();
+    }
+    return false;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/exceptions/JSilverAutoEscapingException.java b/src/com/google/clearsilver/jsilver/exceptions/JSilverAutoEscapingException.java
new file mode 100644
index 0000000..c02a00a
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/exceptions/JSilverAutoEscapingException.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.exceptions;
+
+/**
+ * Thrown when there is a problem applying auto escaping.
+ */
+public class JSilverAutoEscapingException extends JSilverException {
+
+  public static final int UNKNOWN_POSITION = -1;
+
+  public JSilverAutoEscapingException(String message, String templateName, int line, int column) {
+    super(createMessage(message, templateName, line, column));
+  }
+
+  public JSilverAutoEscapingException(String message, String templateName) {
+    this(message, templateName, UNKNOWN_POSITION, UNKNOWN_POSITION);
+  }
+
+  /**
+   * Keeping the same format as JSilverBadSyntaxException.
+   */
+  private static String createMessage(String message, String resourceName, int line, int column) {
+    StringBuilder result = new StringBuilder(message);
+    if (resourceName != null) {
+      result.append(" resource=").append(resourceName);
+    }
+    if (line != UNKNOWN_POSITION) {
+      result.append(" line=").append(line);
+    }
+    if (column != UNKNOWN_POSITION) {
+      result.append(" column=").append(column);
+    }
+    return result.toString();
+  }
+
+  public JSilverAutoEscapingException(String message) {
+    super(message);
+  }
+
+  public JSilverAutoEscapingException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/exceptions/JSilverBadSyntaxException.java b/src/com/google/clearsilver/jsilver/exceptions/JSilverBadSyntaxException.java
new file mode 100644
index 0000000..768a021
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/exceptions/JSilverBadSyntaxException.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.exceptions;
+
+/**
+ * Thrown when resource (e.g. template or HDF) contains bad syntax.
+ */
+public class JSilverBadSyntaxException extends JSilverException {
+
+  private final String resourceName;
+
+  private final int line;
+
+  private final int column;
+
+  /**
+   * Signifies line or column is not known.
+   */
+  public static final int UNKNOWN_POSITION = -1;
+
+  /**
+   * Constructor of JSilverBadSyntaxException.
+   * 
+   * @param message text of an error message
+   * @param lineContent content of a line where error occurred (can be null)
+   * @param resourceName name of a file where error occurred (can be null)
+   * @param line number of a line in {@code resourceName} where error occurred (ignored if set to
+   *        {@link #UNKNOWN_POSITION})
+   * @param column number of a column in {@code resourceName} where error occurred (ignored if set
+   *        to {@link #UNKNOWN_POSITION})
+   * @param cause an original exception of an error. Null value is permitted and indicates that the
+   *        cause is nonexistent or unknown.
+   */
+  public JSilverBadSyntaxException(String message, String lineContent, String resourceName,
+      int line, int column, Throwable cause) {
+    super(makeMessage(message, lineContent, resourceName, line, column), cause);
+    this.resourceName = resourceName;
+    this.line = line;
+    this.column = column;
+  }
+
+  private static String makeMessage(String message, String lineContent, String resourceName,
+      int line, int column) {
+    StringBuilder result = new StringBuilder(message);
+    if (resourceName != null) {
+      result.append(" resource=").append(resourceName);
+    }
+    if (lineContent != null) {
+      result.append(" content=").append(lineContent);
+    }
+    if (line != UNKNOWN_POSITION) {
+      result.append(" line=").append(line);
+    }
+    if (column != UNKNOWN_POSITION) {
+      result.append(" column=").append(column);
+    }
+    return result.toString();
+  }
+
+  /**
+   * Name of resource that had syntax error (typically a file name).
+   */
+  public String getResourceName() {
+    return resourceName;
+  }
+
+  /**
+   * Line number this syntax error occured, or {@link #UNKNOWN_POSITION}.
+   */
+  public int getLine() {
+    return line;
+  }
+
+  /**
+   * Column number this syntax error occured, or {@link #UNKNOWN_POSITION}.
+   */
+  public int getColumn() {
+    return column;
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/exceptions/JSilverException.java b/src/com/google/clearsilver/jsilver/exceptions/JSilverException.java
new file mode 100644
index 0000000..1e403b4
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/exceptions/JSilverException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.exceptions;
+
+/**
+ * Base class for all JSilver exceptions.
+ */
+public abstract class JSilverException extends RuntimeException {
+
+  protected JSilverException(String message) {
+    super(message);
+  }
+
+  protected JSilverException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/exceptions/JSilverIOException.java b/src/com/google/clearsilver/jsilver/exceptions/JSilverIOException.java
new file mode 100644
index 0000000..fb11f3c
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/exceptions/JSilverIOException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.exceptions;
+
+import java.io.IOException;
+
+/**
+ * JSilver failed to access underlying IO stream. Wraps an IOException to make it a
+ * RuntimeException.
+ */
+public class JSilverIOException extends JSilverException {
+
+  public JSilverIOException(IOException cause) {
+    super(cause.getMessage());
+    initCause(cause);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/exceptions/JSilverInterpreterException.java b/src/com/google/clearsilver/jsilver/exceptions/JSilverInterpreterException.java
new file mode 100644
index 0000000..fa97788
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/exceptions/JSilverInterpreterException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.exceptions;
+
+/**
+ * Signifies JSilver failed to interpret a template at runtime.
+ */
+public class JSilverInterpreterException extends JSilverException {
+
+  public JSilverInterpreterException(String message) {
+    super(message);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/exceptions/JSilverTemplateNotFoundException.java b/src/com/google/clearsilver/jsilver/exceptions/JSilverTemplateNotFoundException.java
new file mode 100644
index 0000000..60cce41
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/exceptions/JSilverTemplateNotFoundException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.exceptions;
+
+/**
+ * Thrown when JSilver is asked to load a template that does not exist.
+ */
+public class JSilverTemplateNotFoundException extends JSilverException {
+
+  public JSilverTemplateNotFoundException(String templateName) {
+    super(templateName);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/EscapingFunction.java b/src/com/google/clearsilver/jsilver/functions/EscapingFunction.java
new file mode 100644
index 0000000..10fa76d
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/EscapingFunction.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions;
+
+public abstract class EscapingFunction implements Function {
+
+  @Override
+  public boolean isEscapingFunction() {
+    return true;
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/Function.java b/src/com/google/clearsilver/jsilver/functions/Function.java
new file mode 100644
index 0000000..f100e59
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/Function.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions;
+
+import com.google.clearsilver.jsilver.values.Value;
+
+/**
+ * Plugin for JSilver functions made available to templates. e.g &lt;cs var:my_function(x, y) &gt;
+ */
+public interface Function {
+
+  /**
+   * Execute a function. Should always return a result.
+   */
+  Value execute(Value... args);
+
+  boolean isEscapingFunction();
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/FunctionExecutor.java b/src/com/google/clearsilver/jsilver/functions/FunctionExecutor.java
new file mode 100644
index 0000000..d49f8e4
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/FunctionExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions;
+
+import com.google.clearsilver.jsilver.values.Value;
+
+import java.io.IOException;
+
+/**
+ * Execute functions in templates.
+ */
+public interface FunctionExecutor {
+
+  /**
+   * Lookup a function by name, execute it and return the results.
+   */
+  Value executeFunction(String functionName, Value... args);
+
+  /**
+   * Escapes some text.
+   * 
+   * @param name Strategy for escaping text. If null or "none", text will be left as is.
+   * @param input Text to be escaped.
+   * @param output Where to write the result to.
+   */
+  void escape(String name, String input, Appendable output) throws IOException;
+
+  /**
+   * Look up a function by name, and report whether it is an escaping function.
+   */
+  boolean isEscapingFunction(String name);
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/FunctionRegistry.java b/src/com/google/clearsilver/jsilver/functions/FunctionRegistry.java
new file mode 100644
index 0000000..b51a2db
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/FunctionRegistry.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
+import com.google.clearsilver.jsilver.values.Value;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Simple implementation of FunctionFinder that you can register your own functions with.
+ *
+ * @see FunctionExecutor
+ */
+public class FunctionRegistry implements FunctionExecutor {
+
+  protected Map<String, Function> functions = new HashMap<String, Function>();
+  protected Map<String, TextFilter> escapers = new HashMap<String, TextFilter>();
+
+  public FunctionRegistry() {
+    setupDefaultFunctions();
+  }
+
+  @Override
+  public Value executeFunction(String name, Value... args) {
+    Function function = functions.get(name);
+    if (function == null) {
+      throw new JSilverInterpreterException("Function not found " + name);
+    }
+    Value result = function.execute(args);
+    if (result == null) {
+      throw new JSilverInterpreterException("Function " + name + " did not return value");
+    }
+    return result;
+  }
+
+  @Override
+  public void escape(String name, String input, Appendable output) throws IOException {
+    if (name == null || name.isEmpty() || name.equals("none")) {
+      output.append(input);
+    } else {
+      TextFilter escaper = escapers.get(name);
+      if (escaper == null) {
+        throw new JSilverInterpreterException("Unknown escaper: " + name);
+      }
+      escaper.filter(input, output);
+    }
+  }
+
+  @Override
+  public boolean isEscapingFunction(String name) {
+    Function function = functions.get(name);
+    if (function == null) {
+      throw new JSilverInterpreterException("Function not found " + name);
+    }
+    return function.isEscapingFunction();
+  }
+
+  /**
+   * Subclasses can override this to register their own functions.
+   */
+  protected void setupDefaultFunctions() {}
+
+  /**
+   * Register a Function with a given name.
+   */
+  public void registerFunction(String name, Function function) {
+    functions.put(name, function);
+  }
+
+  /**
+   * Register a TextFilter as a Function that takes a single String argument and returns the
+   * filtered value.
+   */
+  public void registerFunction(String name, final TextFilter textFilter) {
+    registerFunction(name, textFilter, false);
+  }
+
+  public void registerFunction(String name, final TextFilter textFilter, final boolean isEscaper) {
+
+    // Adapt a TextFilter to the Function interface.
+    registerFunction(name, new Function() {
+      @Override
+      public Value execute(Value... args) {
+        if (args.length != 1) {
+          throw new IllegalArgumentException("Expected 1 argument");
+        }
+        String in = args[0].asString();
+        StringBuilder out = new StringBuilder(in.length());
+        try {
+          textFilter.filter(in, out);
+        } catch (IOException e) {
+          throw new JSilverInterpreterException(e.getMessage());
+        }
+
+        EscapeMode mode;
+        boolean isPartiallyEscaped;
+        if (isEscaper) {
+          // This function escapes its input. Hence the output is
+          // partiallyEscaped.
+          mode = EscapeMode.ESCAPE_IS_CONSTANT;
+          isPartiallyEscaped = true;
+        } else {
+          mode = EscapeMode.ESCAPE_NONE;
+          isPartiallyEscaped = false;
+          for (Value arg : args) {
+            if (arg.isPartiallyEscaped()) {
+              isPartiallyEscaped = true;
+              break;
+            }
+          }
+        }
+        return Value.literalValue(out.toString(), mode, isPartiallyEscaped);
+      }
+
+      public boolean isEscapingFunction() {
+        return isEscaper;
+      }
+    });
+  }
+
+  /**
+   * Registers an escaper, that is called when executing a &lt;?cs escape ?&gt; command.
+   * 
+   * @param name The name with which &lt;?cs escape ?&gt; will invoke this escaper.
+   * @param escaper A TextFilter that implements the escaping functionality.
+   */
+  public void registerEscapeMode(String name, TextFilter escaper) {
+
+    escapers.put(name, escaper);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/NonEscapingFunction.java b/src/com/google/clearsilver/jsilver/functions/NonEscapingFunction.java
new file mode 100644
index 0000000..0ac49a0
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/NonEscapingFunction.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions;
+
+public abstract class NonEscapingFunction implements Function {
+
+  @Override
+  public boolean isEscapingFunction() {
+    return false;
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/TextFilter.java b/src/com/google/clearsilver/jsilver/functions/TextFilter.java
new file mode 100644
index 0000000..1515587
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/TextFilter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions;
+
+import java.io.IOException;
+
+/**
+ * Filters some text.
+ */
+public interface TextFilter {
+
+  void filter(String in, Appendable out) throws IOException;
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/bundles/ClearSilverCompatibleFunctions.java b/src/com/google/clearsilver/jsilver/functions/bundles/ClearSilverCompatibleFunctions.java
new file mode 100644
index 0000000..a200a48
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/bundles/ClearSilverCompatibleFunctions.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.bundles;
+
+import com.google.clearsilver.jsilver.functions.escape.*;
+import com.google.clearsilver.jsilver.functions.html.CssUrlValidateFunction;
+import com.google.clearsilver.jsilver.functions.html.HtmlStripFunction;
+import com.google.clearsilver.jsilver.functions.html.HtmlUrlValidateFunction;
+import com.google.clearsilver.jsilver.functions.html.TextHtmlFunction;
+import com.google.clearsilver.jsilver.functions.numeric.AbsFunction;
+import com.google.clearsilver.jsilver.functions.numeric.MaxFunction;
+import com.google.clearsilver.jsilver.functions.numeric.MinFunction;
+import com.google.clearsilver.jsilver.functions.string.CrcFunction;
+import com.google.clearsilver.jsilver.functions.string.FindFunction;
+import com.google.clearsilver.jsilver.functions.string.LengthFunction;
+import com.google.clearsilver.jsilver.functions.string.SliceFunction;
+import com.google.clearsilver.jsilver.functions.structure.FirstFunction;
+import com.google.clearsilver.jsilver.functions.structure.LastFunction;
+import com.google.clearsilver.jsilver.functions.structure.SubcountFunction;
+
+/**
+ * Set of functions required to allow JSilver to be compatible with ClearSilver.
+ */
+public class ClearSilverCompatibleFunctions extends CoreOperators {
+
+  @Override
+  protected void setupDefaultFunctions() {
+    super.setupDefaultFunctions();
+
+    // Structure functions.
+    registerFunction("subcount", new SubcountFunction());
+    registerFunction("first", new FirstFunction());
+    registerFunction("last", new LastFunction());
+
+    // Deprecated - but here for ClearSilver compatibility.
+    registerFunction("len", new SubcountFunction());
+
+    // Numeric functions.
+    registerFunction("abs", new AbsFunction());
+    registerFunction("max", new MaxFunction());
+    registerFunction("min", new MinFunction());
+
+    // String functions.
+    registerFunction("string.slice", new SliceFunction());
+    registerFunction("string.find", new FindFunction());
+    registerFunction("string.length", new LengthFunction());
+    registerFunction("string.crc", new CrcFunction());
+
+    // Escaping functions.
+    registerFunction("url_escape", new UrlEscapeFunction("UTF-8"), true);
+    registerEscapeMode("url", new UrlEscapeFunction("UTF-8"));
+    registerFunction("html_escape", new HtmlEscapeFunction(false), true);
+    registerEscapeMode("html", new HtmlEscapeFunction(false));
+    registerFunction("js_escape", new JsEscapeFunction(false), true);
+    registerEscapeMode("js", new JsEscapeFunction(false));
+
+    // These functions are available as arguments to <?cs escape: ?>
+    // though they aren't in ClearSilver. This is so that auto escaping
+    // can automatically add <?cs escape ?> nodes with these modes
+    registerEscapeMode("html_unquoted", new HtmlEscapeFunction(true));
+    registerEscapeMode("js_attr_unquoted", new JsEscapeFunction(true));
+    registerEscapeMode("js_check_number", new JsValidateUnquotedLiteral());
+    registerEscapeMode("url_validate_unquoted", new HtmlUrlValidateFunction(true));
+
+    registerEscapeMode("css", new StyleEscapeFunction(false));
+    registerEscapeMode("css_unquoted", new StyleEscapeFunction(true));
+
+    // HTML functions.
+    registerFunction("html_strip", new HtmlStripFunction());
+    registerFunction("text_html", new TextHtmlFunction());
+
+    // url_validate is available as an argument to <?cs escape: ?>
+    // though it isn't in ClearSilver.
+    registerFunction("url_validate", new HtmlUrlValidateFunction(false), true);
+    registerEscapeMode("url_validate", new HtmlUrlValidateFunction(false));
+
+    registerFunction("css_url_validate", new CssUrlValidateFunction(), true);
+    // Register as an EscapingFunction so that autoescaping will be disabled
+    // for the output of this function.
+    registerFunction("null_escape", new NullEscapeFunction(), true);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/bundles/CoreOperators.java b/src/com/google/clearsilver/jsilver/functions/bundles/CoreOperators.java
new file mode 100644
index 0000000..1d35519
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/bundles/CoreOperators.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.bundles;
+
+import com.google.clearsilver.jsilver.functions.FunctionRegistry;
+import com.google.clearsilver.jsilver.functions.operators.AddFunction;
+import com.google.clearsilver.jsilver.functions.operators.AndFunction;
+import com.google.clearsilver.jsilver.functions.operators.DivideFunction;
+import com.google.clearsilver.jsilver.functions.operators.EqualFunction;
+import com.google.clearsilver.jsilver.functions.operators.ExistsFunction;
+import com.google.clearsilver.jsilver.functions.operators.GreaterFunction;
+import com.google.clearsilver.jsilver.functions.operators.GreaterOrEqualFunction;
+import com.google.clearsilver.jsilver.functions.operators.LessFunction;
+import com.google.clearsilver.jsilver.functions.operators.LessOrEqualFunction;
+import com.google.clearsilver.jsilver.functions.operators.ModuloFunction;
+import com.google.clearsilver.jsilver.functions.operators.MultiplyFunction;
+import com.google.clearsilver.jsilver.functions.operators.NotEqualFunction;
+import com.google.clearsilver.jsilver.functions.operators.NotFunction;
+import com.google.clearsilver.jsilver.functions.operators.NumericAddFunction;
+import com.google.clearsilver.jsilver.functions.operators.NumericEqualFunction;
+import com.google.clearsilver.jsilver.functions.operators.NumericFunction;
+import com.google.clearsilver.jsilver.functions.operators.NumericNotEqualFunction;
+import com.google.clearsilver.jsilver.functions.operators.OrFunction;
+import com.google.clearsilver.jsilver.functions.operators.SubtractFunction;
+import com.google.clearsilver.jsilver.functions.structure.NameFunction;
+
+/**
+ * Function registry containing core operators used in expressions.
+ * 
+ * These are: + - * / % ? ! && || == != &lt; &gt; &lt= &gt;=, name.
+ * 
+ * @see FunctionRegistry
+ */
+public class CoreOperators extends FunctionRegistry {
+
+  @Override
+  protected void setupDefaultFunctions() {
+    super.setupDefaultFunctions();
+    registerFunction("+", new AddFunction());
+    registerFunction("#+", new NumericAddFunction());
+    registerFunction("-", new SubtractFunction());
+    registerFunction("*", new MultiplyFunction());
+    registerFunction("/", new DivideFunction());
+    registerFunction("%", new ModuloFunction());
+    registerFunction("?", new ExistsFunction());
+    registerFunction("!", new NotFunction());
+    registerFunction("&&", new AndFunction());
+    registerFunction("||", new OrFunction());
+    registerFunction("==", new EqualFunction());
+    registerFunction("#==", new NumericEqualFunction());
+    registerFunction("!=", new NotEqualFunction());
+    registerFunction("#!=", new NumericNotEqualFunction());
+    registerFunction("<", new LessFunction());
+    registerFunction(">", new GreaterFunction());
+    registerFunction("<=", new LessOrEqualFunction());
+    registerFunction(">=", new GreaterOrEqualFunction());
+    registerFunction("#", new NumericFunction());
+
+    // Not an operator, but JSilver cannot function without as it's used by
+    // the <?cs name ?> command.
+    registerFunction("name", new NameFunction());
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/escape/HtmlEscapeFunction.java b/src/com/google/clearsilver/jsilver/functions/escape/HtmlEscapeFunction.java
new file mode 100644
index 0000000..ca310d2
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/escape/HtmlEscapeFunction.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.escape;
+
+
+/**
+ * This class HTML escapes a string in the same way as the ClearSilver html_escape function.
+ * 
+ * This implementation has been optimized for performance.
+ * 
+ */
+public class HtmlEscapeFunction extends SimpleEscapingFunction {
+
+  // The escape chars
+  private static final char[] ESCAPE_CHARS = {'<', '>', '&', '\'', '"'};
+
+  // UNQUOTED_ESCAPE_CHARS = ESCAPE_CHARS + UNQUOTED_EXTRA_CHARS + chars < 0x20 + 0x7f
+  private static final char[] UNQUOTED_ESCAPE_CHARS;
+
+  private static final char[] UNQUOTED_EXTRA_CHARS = {'=', ' '};
+
+  // The corresponding escape strings for all ascii characters.
+  // With control characters, we simply strip them out if necessary.
+  private static String[] ESCAPE_CODES =
+      {"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+          "", "", "", "", "", "", "", "", "", "", "!", "&quot;", "#", "$", "%", "&amp;", "&#39;",
+          "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+          ":", ";", "&lt;", "&#61;", "&gt;", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I",
+          "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[",
+          "\\", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l",
+          "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~",
+          ""};
+
+  static {
+    UNQUOTED_ESCAPE_CHARS = new char[33 + ESCAPE_CHARS.length + UNQUOTED_EXTRA_CHARS.length];
+    // In unquoted HTML attributes, strip out control characters also, as they could
+    // get interpreted as end of attribute, just like spaces.
+    for (int n = 0; n <= 0x1f; n++) {
+      UNQUOTED_ESCAPE_CHARS[n] = (char) n;
+    }
+    UNQUOTED_ESCAPE_CHARS[32] = (char) 0x7f;
+    System.arraycopy(ESCAPE_CHARS, 0, UNQUOTED_ESCAPE_CHARS, 33, ESCAPE_CHARS.length);
+    System.arraycopy(UNQUOTED_EXTRA_CHARS, 0, UNQUOTED_ESCAPE_CHARS, 33 + ESCAPE_CHARS.length,
+        UNQUOTED_EXTRA_CHARS.length);
+
+  }
+
+  /**
+   * isUnquoted should be true if the function is escaping a string that will appear inside an
+   * unquoted HTML attribute.
+   * 
+   * If the string is unquoted, we strip out all characters 0 - 0x1f and 0x7f for security reasons.
+   */
+  public HtmlEscapeFunction(boolean isUnquoted) {
+    if (isUnquoted) {
+      super.setEscapeChars(UNQUOTED_ESCAPE_CHARS);
+    } else {
+      super.setEscapeChars(ESCAPE_CHARS);
+    }
+  }
+
+  @Override
+  protected String getEscapeString(char c) {
+    if (c < 0x80) {
+      return ESCAPE_CODES[c];
+    }
+    throw new IllegalArgumentException("Unexpected escape character " + c + "[" + (int) c + "]");
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/escape/JsEscapeFunction.java b/src/com/google/clearsilver/jsilver/functions/escape/JsEscapeFunction.java
new file mode 100644
index 0000000..5294466
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/escape/JsEscapeFunction.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.escape;
+
+
+/**
+ * This Javascript escapes the string so it will be valid data for placement into a Javascript
+ * string. This converts characters such as ", ', and \ into their Javascript string safe
+ * equivilants \", \', and \\.
+ * 
+ * This behaves in the same way as the ClearSilver js_escape function.
+ * 
+ * This implementation has been optimized for performance.
+ */
+public class JsEscapeFunction extends SimpleEscapingFunction {
+
+  private static final char[] DIGITS = "0123456789ABCDEF".toCharArray();
+
+  private static final char[] ESCAPE_CHARS;
+
+  private static final char[] UNQUOTED_ESCAPE_CHARS;
+
+  static {
+    char[] SPECIAL_CHARS = {'/', '"', '\'', '\\', '>', '<', '&', ';'};
+    char[] UNQUOTED_SPECIAL_CHARS = {'/', '"', '\'', '\\', '>', '<', '&', ';', '=', ' '};
+
+    ESCAPE_CHARS = new char[32 + SPECIAL_CHARS.length];
+    UNQUOTED_ESCAPE_CHARS = new char[33 + UNQUOTED_SPECIAL_CHARS.length];
+    for (int n = 0; n < 32; n++) {
+      ESCAPE_CHARS[n] = (char) n;
+      UNQUOTED_ESCAPE_CHARS[n] = (char) n;
+    }
+
+    System.arraycopy(SPECIAL_CHARS, 0, ESCAPE_CHARS, 32, SPECIAL_CHARS.length);
+
+    UNQUOTED_ESCAPE_CHARS[32] = 0x7F;
+    System.arraycopy(UNQUOTED_SPECIAL_CHARS, 0, UNQUOTED_ESCAPE_CHARS, 33,
+        UNQUOTED_SPECIAL_CHARS.length);
+  }
+
+  /**
+   * isUnquoted should be true if the function is escaping a string that will appear inside an
+   * unquoted JS attribute (like onClick or onMouseover).
+   * 
+   */
+  public JsEscapeFunction(boolean isAttrUnquoted) {
+    if (isAttrUnquoted) {
+      super.setEscapeChars(UNQUOTED_ESCAPE_CHARS);
+    } else {
+      super.setEscapeChars(ESCAPE_CHARS);
+    }
+  }
+
+  @Override
+  protected String getEscapeString(char c) {
+    return "\\x" + DIGITS[(c >> 4) & 0xF] + DIGITS[c & 0xF];
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/escape/JsValidateUnquotedLiteral.java b/src/com/google/clearsilver/jsilver/functions/escape/JsValidateUnquotedLiteral.java
new file mode 100644
index 0000000..f7f273d
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/escape/JsValidateUnquotedLiteral.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.escape;
+
+import com.google.clearsilver.jsilver.functions.TextFilter;
+
+import java.io.IOException;
+
+/**
+ * This function will be used to sanitize variables introduced into javascript that are not string
+ * literals. e.g. <script> var x = <?cs var: x ?> </script>
+ * 
+ * Currently it only accepts boolean and numeric literals. All other values are replaced with a
+ * 'null'. This behavior may be extended if required at a later time. This replicates the
+ * autoescaping behavior of Clearsilver.
+ */
+public class JsValidateUnquotedLiteral implements TextFilter {
+
+  public void filter(String in, Appendable out) throws IOException {
+    /* Permit boolean literals */
+    if (in.equals("true") || in.equals("false")) {
+      out.append(in);
+      return;
+    }
+
+    boolean valid = true;
+    if (in.startsWith("0x") || in.startsWith("0X")) {
+
+      /*
+       * There must be at least one hex digit after the 0x for it to be valid. Hex number. Check
+       * that it is of the form 0(x|X)[0-9A-Fa-f]+
+       */
+      for (int i = 2; i < in.length(); i++) {
+        char c = in.charAt(i);
+        if (!((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || (c >= '0' && c <= '9'))) {
+          valid = false;
+          break;
+        }
+      }
+    } else {
+      /*
+       * Must be a base-10 (or octal) number. Check that it has the form [0-9+-.eE]+
+       */
+      for (int i = 0; i < in.length(); i++) {
+        char c = in.charAt(i);
+        if (!((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.' || c == 'e' || c == 'E')) {
+          valid = false;
+          break;
+        }
+      }
+    }
+
+    if (valid) {
+      out.append(in);
+    } else {
+      out.append("null");
+    }
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/escape/NullEscapeFunction.java b/src/com/google/clearsilver/jsilver/functions/escape/NullEscapeFunction.java
new file mode 100644
index 0000000..9cf4be9
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/escape/NullEscapeFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.escape;
+
+import com.google.clearsilver.jsilver.functions.TextFilter;
+
+import java.io.IOException;
+
+/**
+ * Returns the input string without any modification.
+ * 
+ * This function is intended to be registered as an {@code EscapingFunction} so that it can be used
+ * to disable autoescaping of expressions.
+ */
+public class NullEscapeFunction implements TextFilter {
+
+  public void filter(String in, Appendable out) throws IOException {
+    out.append(in);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/escape/SimpleEscapingFunction.java b/src/com/google/clearsilver/jsilver/functions/escape/SimpleEscapingFunction.java
new file mode 100644
index 0000000..9b26040
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/escape/SimpleEscapingFunction.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.escape;
+
+import com.google.clearsilver.jsilver.functions.TextFilter;
+
+import java.io.IOException;
+
+/**
+ * Base class to make writing fast, simple escaping functions easy. A simple escaping function is
+ * one where each character in the input is treated independently and there is no runtime state. The
+ * only decision you make is whether the current character should be escaped into some different
+ * string or not.
+ * 
+ * The only serious limitation on using this class it that only low valued characters can be
+ * escaped. This is because (for speed) we use an array of escaped strings, indexed by character
+ * value. In future this limitation may be lifted if there's a call for it.
+ */
+public abstract class SimpleEscapingFunction implements TextFilter {
+  // The limit for how many strings we can store here (max)
+  private static final int CHAR_INDEX_LIMIT = 256;
+
+  // Our fast lookup array of escaped strings. This array is indexed by char
+  // value so it's important not to have it grow too large. For now we have
+  // an artificial limit on it.
+  private String[] ESCAPE_STRINGS;
+
+  /**
+   * Creates an instance to escape the given set of characters.
+   */
+  protected SimpleEscapingFunction(char[] ESCAPE_CHARS) {
+    setEscapeChars(ESCAPE_CHARS);
+  }
+
+  protected SimpleEscapingFunction() {
+    ESCAPE_STRINGS = new String[0];
+  }
+
+  protected void setEscapeChars(char[] ESCAPE_CHARS) throws AssertionError {
+    int highestChar = -1;
+    for (char c : ESCAPE_CHARS) {
+      if (c > highestChar) {
+        highestChar = c;
+      }
+    }
+    if (highestChar >= CHAR_INDEX_LIMIT) {
+      throw new AssertionError("Cannot escape characters with values above " + CHAR_INDEX_LIMIT);
+    }
+    ESCAPE_STRINGS = new String[highestChar + 1];
+    for (char c : ESCAPE_CHARS) {
+      ESCAPE_STRINGS[c] = getEscapeString(c);
+    }
+  }
+
+  /**
+   * Given one of the escape characters supplied to this instance's constructor, return the escape
+   * string for it. This method does not need to be efficient.
+   */
+  protected abstract String getEscapeString(char c);
+
+  /**
+   * Algorithm is as follows:
+   * <ol>
+   * <li>Scan block for contiguous unescaped sequences
+   * <li>Append unescaped sequences to output
+   * <li>Append escaped string to output (if found)
+   * <li>Rinse &amp; Repeat
+   * </ol>
+   */
+  @Override
+  public void filter(String in, Appendable out) throws IOException {
+    final int len = in.length();
+    int pos = 0;
+    int start = pos;
+    while (pos < len) {
+      // We really hope that the hotspot compiler inlines this call properly
+      // (without optimization it accounts for > 50% of the time in this call)
+      final char chr = in.charAt(pos);
+      final String escapeString;
+      if (chr < ESCAPE_STRINGS.length && (escapeString = ESCAPE_STRINGS[chr]) != null) {
+        // We really hope our appendable handles sub-strings nicely
+        // (we know that StringBuilder / StringBuffer does).
+        if (pos > start) {
+          out.append(in, start, pos);
+        }
+        out.append(escapeString);
+        pos += 1;
+        start = pos;
+        continue;
+      }
+      pos += 1;
+    }
+    if (pos > start) {
+      out.append(in, start, pos);
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/escape/StyleEscapeFunction.java b/src/com/google/clearsilver/jsilver/functions/escape/StyleEscapeFunction.java
new file mode 100644
index 0000000..6e07036
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/escape/StyleEscapeFunction.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.escape;
+
+import com.google.clearsilver.jsilver.functions.TextFilter;
+
+import java.io.IOException;
+
+/**
+ * This function will be used to sanitize variables in 'style' attributes. It strips out any
+ * characters that are not part of a whitelist of safe characters. This replicates the autoescaping
+ * behavior of Clearsilver.
+ * 
+ * It does not extend SimpleEscapingFunction because SimpleEscapingFunction requires a blacklist of
+ * characters to escape. The StyleAttrEscapeFunction instead applies a whitelist, and strips out any
+ * characters not in the whitelist.
+ */
+public class StyleEscapeFunction implements TextFilter {
+
+  private static final boolean[] UNQUOTED_VALID_CHARS;
+  private static final boolean[] VALID_CHARS;
+  private static final int MAX_CHARS = 0x80;
+
+  static {
+    // Allow characters likely to occur inside a style property value.
+    // Refer http://www.w3.org/TR/CSS21/ for more details.
+    String SPECIAL_CHARS = "_.,!#%- ";
+    String UNQUOTED_SPECIAL_CHARS = "_.,!#%-";
+
+    VALID_CHARS = new boolean[MAX_CHARS];
+    UNQUOTED_VALID_CHARS = new boolean[MAX_CHARS];
+
+    for (int n = 0; n < MAX_CHARS; n++) {
+      VALID_CHARS[n] = false;
+      UNQUOTED_VALID_CHARS[n] = false;
+
+      if (Character.isLetterOrDigit(n)) {
+        VALID_CHARS[n] = true;
+        UNQUOTED_VALID_CHARS[n] = true;
+      } else {
+        if (SPECIAL_CHARS.indexOf(n) != -1) {
+          VALID_CHARS[n] = true;
+        }
+
+        if (UNQUOTED_SPECIAL_CHARS.indexOf(n) != -1) {
+          UNQUOTED_VALID_CHARS[n] = true;
+        }
+      }
+    }
+  }
+
+  private final boolean[] validChars;
+
+  /**
+   * isUnquoted should be true if the function is escaping a string that will appear inside an
+   * unquoted style attribute.
+   * 
+   */
+  public StyleEscapeFunction(boolean isUnquoted) {
+    if (isUnquoted) {
+      validChars = UNQUOTED_VALID_CHARS;
+    } else {
+      validChars = VALID_CHARS;
+    }
+  }
+
+  public void filter(String in, Appendable out) throws IOException {
+    for (char c : in.toCharArray()) {
+      if (c < MAX_CHARS && validChars[c]) {
+        out.append(c);
+      } else if (c >= MAX_CHARS) {
+        out.append(c);
+      }
+    }
+  }
+
+  public void dumpInfo() {
+    for (int i = 0; i < MAX_CHARS; i++) {
+      System.out.println(i + "(" + (char) i + ")" + " :" + VALID_CHARS[i]);
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/escape/UrlEscapeFunction.java b/src/com/google/clearsilver/jsilver/functions/escape/UrlEscapeFunction.java
new file mode 100644
index 0000000..284c053
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/escape/UrlEscapeFunction.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.escape;
+
+import com.google.clearsilver.jsilver.functions.TextFilter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * This URL encodes the string. This converts characters such as ?, ampersands, and = into their URL
+ * safe equivilants using the %hh syntax.
+ */
+public class UrlEscapeFunction implements TextFilter {
+
+  private final String encoding;
+
+  public UrlEscapeFunction(String encoding) {
+    try {
+      // Sanity check. Fail at construction time rather than render time.
+      new OutputStreamWriter(new ByteArrayOutputStream(), encoding);
+    } catch (UnsupportedEncodingException e) {
+      throw new IllegalArgumentException("Unsupported encoding : " + encoding);
+    }
+    this.encoding = encoding;
+  }
+
+  @Override
+  public void filter(String in, Appendable out) throws IOException {
+    try {
+      out.append(URLEncoder.encode(in, encoding));
+    } catch (UnsupportedEncodingException e) {
+      // The sanity check in the constructor should have caught this.
+      // Things must be really broken for this to happen, so throw an Error.
+      throw new Error("Unsuported encoding : " + encoding);
+    }
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/html/BaseUrlValidateFunction.java b/src/com/google/clearsilver/jsilver/functions/html/BaseUrlValidateFunction.java
new file mode 100644
index 0000000..7e61e99
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/html/BaseUrlValidateFunction.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.html;
+
+import com.google.clearsilver.jsilver.functions.TextFilter;
+
+import java.io.IOException;
+import java.lang.Character.UnicodeBlock;
+
+/**
+ * Validates that a given string is either something that looks like a relative URI, or looks like
+ * an absolute URI using one of a set of allowed schemes (http, https, ftp, mailto). If the string
+ * is valid according to these criteria, the string is escaped with an appropriate escaping
+ * function. Otherwise, the string "#" is returned.
+ * 
+ * Subclasses will apply the necessary escaping function to the string by overriding {@code
+ * applyEscaping}.
+ * 
+ * <p>
+ * Note: this function does <em>not</em> validate that the URI is well-formed beyond the scheme part
+ * (and if the URI appears to be relative, not even then). Note in particular that this function
+ * considers strings of the form "www.google.com:80" to be invalid.
+ */
+public abstract class BaseUrlValidateFunction implements TextFilter {
+
+  @Override
+  public void filter(String in, Appendable out) throws IOException {
+    if (!isValidUri(in)) {
+      out.append('#');
+      return;
+    }
+    applyEscaping(in, out);
+  }
+
+  /**
+   * Called by {@code filter} after verifying that the input is a valid URI. Should apply any
+   * appropriate escaping to the input string.
+   * 
+   * @throws IOException
+   */
+  protected abstract void applyEscaping(String in, Appendable out) throws IOException;
+
+  /**
+   * @return true if a given string either looks like a relative URI, or like an absolute URI with
+   *         an allowed scheme.
+   */
+  protected boolean isValidUri(String in) {
+    // Quick check for the allowed absolute URI schemes.
+    String maybeScheme = toLowerCaseAsciiOnly(in.substring(0, Math.min(in.length(), 8)));
+    if (maybeScheme.startsWith("http://") || maybeScheme.startsWith("https://")
+        || maybeScheme.startsWith("ftp://") || maybeScheme.startsWith("mailto:")) {
+      return true;
+    }
+
+    // If it's an absolute URI with a different scheme, it's invalid.
+    // ClearSilver defines an absolute URI as one that contains a colon prior
+    // to any slash.
+    int slashPos = in.indexOf('/');
+    if (slashPos != -1) {
+      // only colons before this point are bad.
+      return in.lastIndexOf(':', slashPos - 1) == -1;
+    } else {
+      // then any colon is bad.
+      return in.indexOf(':') == -1;
+    }
+  }
+
+  /**
+   * Converts an ASCII string to lowercase. Non-ASCII characters are replaced with '?'.
+   */
+  private String toLowerCaseAsciiOnly(String string) {
+    char[] ca = string.toCharArray();
+    for (int i = 0; i < ca.length; i++) {
+      char ch = ca[i];
+      ca[i] =
+          (Character.UnicodeBlock.of(ch) == UnicodeBlock.BASIC_LATIN)
+              ? Character.toLowerCase(ch)
+              : '?';
+    }
+    return new String(ca);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/html/CssUrlValidateFunction.java b/src/com/google/clearsilver/jsilver/functions/html/CssUrlValidateFunction.java
new file mode 100644
index 0000000..1eed712
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/html/CssUrlValidateFunction.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.html;
+
+import java.io.IOException;
+
+/**
+ * Validates that input string is a valid URI. If it is not valid, the string {@code #} is returned.
+ * If it is valid, the characters [\n\r\\'"()<>*] are URL encoded to ensure the string can be safely
+ * inserted in a CSS URL context. In particular:
+ * <ol>
+ * <li>In an '@import url("URL");' statement
+ * <li>In a CSS property such as 'background: url("URL");'
+ * </ol>
+ * In both cases, enclosing quotes are optional but parenthesis are not. This filter ensures that
+ * the URL cannot exit the parens enclosure, close a STYLE tag or reset the browser's CSS parser
+ * (via comments or newlines).
+ * <p>
+ * References:
+ * <ol>
+ * <li>CSS 2.1 URLs: http://www.w3.org/TR/CSS21/syndata.html#url
+ * <li>CSS 1 URLs: http://www.w3.org/TR/REC-CSS1/#url
+ * </ol>
+ * 
+ * @see BaseUrlValidateFunction
+ */
+public class CssUrlValidateFunction extends BaseUrlValidateFunction {
+
+  protected void applyEscaping(String in, Appendable out) throws IOException {
+    for (int i = 0; i < in.length(); i++) {
+      char ch = in.charAt(i);
+      switch (ch) {
+        case '\n':
+          out.append("%0A");
+          break;
+        case '\r':
+          out.append("%0D");
+          break;
+        case '"':
+          out.append("%22");
+          break;
+        case '\'':
+          out.append("%27");
+          break;
+        case '(':
+          out.append("%28");
+          break;
+        case ')':
+          out.append("%29");
+          break;
+        case '*':
+          out.append("%2A");
+          break;
+        case '<':
+          out.append("%3C");
+          break;
+        case '>':
+          out.append("%3E");
+          break;
+        case '\\':
+          out.append("%5C");
+          break;
+        default:
+          out.append(ch);
+      }
+    }
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/html/HtmlStripFunction.java b/src/com/google/clearsilver/jsilver/functions/html/HtmlStripFunction.java
new file mode 100644
index 0000000..126ab34
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/html/HtmlStripFunction.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.html;
+
+import com.google.clearsilver.jsilver.functions.TextFilter;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class implements the html_strip function. It removes html tags from text, and expands
+ * numbered and named html entities to their corresponding special characters.
+ */
+public class HtmlStripFunction implements TextFilter {
+
+  // The maximum length of an entity (preceded by an &)
+  private static final int MAX_AMP_LENGTH = 9;
+
+  // The state the strip function can be, normal, in an amp escaped entity or
+  // inside a html tag.
+  private enum State {
+    DEFAULT, IN_AMP, IN_TAG
+  }
+
+  // Map of entity names to special characters.
+  private static final Map<String, String> entityValues;
+
+  // Initialize the entityName lookup map.
+  static {
+    Map<String, String> tempMap = new HashMap<String, String>();
+
+    // Html specific characters.
+    tempMap.put("amp", "&");
+    tempMap.put("quot", "\"");
+    tempMap.put("gt", ">");
+    tempMap.put("lt", "<");
+
+    tempMap.put("agrave", "\u00e0");
+    tempMap.put("aacute", "\u00e1");
+    tempMap.put("acirc", "\u00e2");
+    tempMap.put("atilde", "\u00e3");
+    tempMap.put("auml", "\u00e4");
+    tempMap.put("aring", "\u00e5");
+    tempMap.put("aelig", "\u00e6");
+    tempMap.put("ccedil", "\u00e7");
+    tempMap.put("egrave", "\u00e8");
+    tempMap.put("eacute", "\u00e9");
+    tempMap.put("ecirc", "\u00ea");
+    tempMap.put("euml", "\u00eb");
+    tempMap.put("eth", "\u00f0");
+    tempMap.put("igrave", "\u00ec");
+    tempMap.put("iacute", "\u00ed");
+    tempMap.put("icirc", "\u00ee");
+    tempMap.put("iuml", "\u00ef");
+    tempMap.put("ntilde", "\u00f1");
+    tempMap.put("nbsp", " ");
+    tempMap.put("ograve", "\u00f2");
+    tempMap.put("oacute", "\u00f3");
+    tempMap.put("ocirc", "\u00f4");
+    tempMap.put("otilde", "\u00f5");
+    tempMap.put("ouml", "\u00f6");
+    tempMap.put("oslash", "\u00f8");
+    tempMap.put("szlig", "\u00df");
+    tempMap.put("thorn", "\u00fe");
+    tempMap.put("ugrave", "\u00f9");
+    tempMap.put("uacute", "\u00fa");
+    tempMap.put("ucirc", "\u00fb");
+    tempMap.put("uuml", "\u00fc");
+    tempMap.put("yacute", "\u00fd");
+
+    // Clearsilver's Copyright symbol!
+    tempMap.put("copy", "(C)");
+
+    // Copy the temporary map to an unmodifiable map for the static lookup.
+    entityValues = Collections.unmodifiableMap(tempMap);
+  }
+
+  @Override
+  public void filter(String in, Appendable out) throws IOException {
+    char[] inChars = in.toCharArray();
+
+    // Holds the contents of an & (amp) entity before its decoded.
+    StringBuilder amp = new StringBuilder();
+    State state = State.DEFAULT;
+
+    // Loop over the input string, ignoring tags, and decoding entities.
+    for (int i = 0; i < inChars.length; i++) {
+      char c = inChars[i];
+      switch (state) {
+
+        case DEFAULT:
+          switch (c) {
+            case '&':
+              state = State.IN_AMP;
+              break;
+            case '<':
+              state = State.IN_TAG;
+              break;
+            default:
+              // If this is isn't the start of an amp of a tag, treat as plain
+              // text and just output.
+              out.append(c);
+          }
+          break;
+
+        case IN_TAG:
+          // When in a tag, all input is ignored until the end of the tag.
+          if (c == '>') {
+            state = State.DEFAULT;
+          }
+          break;
+
+        case IN_AMP:
+          // Semi-colon terminates an entity, try and decode it.
+          if (c == ';') {
+            state = State.DEFAULT;
+            appendDecodedEntityReference(out, amp);
+            amp = new StringBuilder();
+          } else {
+            if (amp.length() < MAX_AMP_LENGTH) {
+              // If this is not the last character in the input, append to the
+              // amp buffer and continue, if it is the last, dump the buffer
+              // to stop the contents of it being lost.
+              if (i != inChars.length - 1) {
+                amp.append(c);
+              } else {
+                out.append('&').append(amp).append(c);
+              }
+            } else {
+              // More than 8 chars, so not a valid entity, dump as plain text.
+              out.append('&').append(amp).append(c);
+              amp = new StringBuilder();
+              state = State.DEFAULT;
+            }
+          }
+          break;
+      }
+    }
+  }
+
+  /**
+   * Attempts to decode the entity provided, if it succeeds appends it to the out string.
+   * 
+   * @param out the string builder to add the decoded entity to.
+   * @param entityName to decode.
+   */
+  private void appendDecodedEntityReference(Appendable out, CharSequence entityName)
+      throws IOException {
+
+    // All the valid entities are at least two characters long.
+    if (entityName.length() < 2) {
+      return;
+    }
+
+    entityName = entityName.toString().toLowerCase();
+
+    // Numbered entity.
+    if (entityName.charAt(0) == '#') {
+      appendNumberedEntity(out, entityName.subSequence(1, entityName.length()));
+      return;
+    }
+
+    // If the entity is not a numeric value, try looking it up by name.
+    String entity = entityValues.get(entityName);
+
+    // If there is an entity by that name add it to the output.
+    if (entity != null) {
+      out.append(entity);
+    }
+  }
+
+  /**
+   * Appends an entity to a string by numeric code.
+   * 
+   * @param out the string to add the entity to.
+   * @param entity the numeric code for the entity as a char sequence.
+   */
+  private void appendNumberedEntity(Appendable out, CharSequence entity) throws IOException {
+
+    if (entity.length() != 0) {
+      try {
+        char c;
+        // Hex numbered entities start with x.
+        if (entity.charAt(0) == 'x') {
+          c = (char) Integer.parseInt(entity.subSequence(1, entity.length()).toString(), 16);
+        } else {
+          // If its numbered, but not hex, its decimal.
+          c = (char) Integer.parseInt(entity.toString(), 10);
+        }
+
+        // Don't append null characters, this is to remain Clearsilver compatible.
+        if (c != '\u0000') {
+          out.append(c);
+        }
+      } catch (NumberFormatException e) {
+        // Do nothing if this is not a valid numbered entity.
+      }
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/html/HtmlUrlValidateFunction.java b/src/com/google/clearsilver/jsilver/functions/html/HtmlUrlValidateFunction.java
new file mode 100644
index 0000000..ddb849c
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/html/HtmlUrlValidateFunction.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.html;
+
+import com.google.clearsilver.jsilver.functions.escape.HtmlEscapeFunction;
+
+import java.io.IOException;
+
+/**
+ * Validates that a given string is a valid URI and return the HTML escaped string if it is.
+ * Otherwise the string {@code #} is returned.
+ * 
+ * @see BaseUrlValidateFunction
+ */
+public class HtmlUrlValidateFunction extends BaseUrlValidateFunction {
+
+  private final HtmlEscapeFunction htmlEscape;
+
+  /**
+   * isUnquoted should be true if the URL appears in an unquoted attribute. like: &lt;a href=&lt;?cs
+   * var: uri ?&gt;&gt;
+   */
+  public HtmlUrlValidateFunction(boolean isUnquoted) {
+    htmlEscape = new HtmlEscapeFunction(isUnquoted);
+  }
+
+  protected void applyEscaping(String in, Appendable out) throws IOException {
+    htmlEscape.filter(in, out);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/html/TextHtmlFunction.java b/src/com/google/clearsilver/jsilver/functions/html/TextHtmlFunction.java
new file mode 100644
index 0000000..e827a4e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/html/TextHtmlFunction.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.html;
+
+import com.google.clearsilver.jsilver.functions.TextFilter;
+import com.google.clearsilver.jsilver.functions.escape.HtmlEscapeFunction;
+import com.google.clearsilver.jsilver.functions.escape.SimpleEscapingFunction;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class implements the ClearSilver text_html function.
+ * 
+ * It converts plain text into html, including adding 'tt' tags to ascii art and linking email and
+ * web addresses.
+ * 
+ * Note this implementation differs from ClearSilver, in that it html escapes the contents of links
+ * and mailtos.
+ */
+public class TextHtmlFunction implements TextFilter {
+
+  // These regular expressions are adapted from html.c in the ClearSilver
+  // source.
+
+  // Regular expression used to match email addresses, taken from the
+  // ClearSilver source to maintain compatibility.
+  private static final String EMAIL_REGEXP =
+      "[^]\\[@:;<>\\\"()\\s\\p{Cntrl}]+@[-+a-zA-Z0-9]+\\.[-+a-zA-Z0-9\\.]+[-+a-zA-Z0-9]";
+
+  // Regular expression used to match urls without a scheme (www.foo.com),
+  // adapted from the ClearSilver source to maintain compatibility.
+  private static final String WITH_SCHEME_REGEXP = "(?:http|https|ftp|mailto):[^\\s>\"]*";
+
+  // Regular expression used to match urls with a scheme (http://www.foo.com),
+  // adapted from the ClearSilver source to maintain compatibility.
+  private static final String WITHOUT_SCHEME_REGEXP = "www\\.[-a-z0-9\\.]+[^\\s;\">]*";
+
+  // Pattern to match any string in the input that is linkable.
+  private static final Pattern LINKABLES =
+      Pattern.compile("(" + EMAIL_REGEXP + ")|(" + WITH_SCHEME_REGEXP + ")|("
+          + WITHOUT_SCHEME_REGEXP + ")", Pattern.CASE_INSENSITIVE);
+
+  // Matching groups for the LINKABLES pattern.
+  private static final int EMAIL_GROUP = 1;
+  private static final int WITH_SCHEME_GROUP = 2;
+
+  // We don't have access to the global html escaper here, so create a new one.
+  private final HtmlEscapeFunction htmlEscaper = new HtmlEscapeFunction(false);
+
+  // Escapes a small set of non-safe html characters, and does a a very small
+  // amount of formatting.
+  private final SimpleEscapingFunction htmlCharEscaper =
+      new SimpleEscapingFunction(new char[] {'<', '>', '&', '\n', '\r'}) {
+
+        @Override
+        protected String getEscapeString(char c) {
+          switch (c) {
+            case '<':
+              return "&lt;";
+            case '>':
+              return "&gt;";
+            case '&':
+              return "&amp;";
+            case '\n':
+              return "<br/>\n";
+            case '\r':
+              return "";
+            default:
+              return null;
+          }
+        }
+
+      };
+
+  @Override
+  public void filter(String in, Appendable out) throws IOException {
+
+    boolean hasAsciiArt = hasAsciiArt(in);
+
+    // Add 'tt' tag to a string that contains 'ascii-art'.
+    if (hasAsciiArt) {
+      out.append("<tt>");
+    }
+
+    splitAndConvert(in, out);
+
+    if (hasAsciiArt) {
+      out.append("</tt>");
+    }
+  }
+
+  /**
+   * Splits the input string into blocks of normal text or linkable text. The linkable text is
+   * converted into anchor tags before being appended to the output. The normal text is escaped and
+   * appended to the output.
+   */
+  private void splitAndConvert(String in, Appendable out) throws IOException {
+    Matcher matcher = LINKABLES.matcher(in);
+    int end = in.length();
+    int matchStart;
+    int matchEnd;
+    int regionStart = 0;
+
+    // Keep looking for email addresses and web links until there are none left.
+    while (matcher.find()) {
+      matchStart = matcher.start();
+      matchEnd = matcher.end();
+
+      // Escape all the text from the end of the previous match to the start of
+      // this match, and append it to the output.
+      htmlCharEscaper.filter(in.subSequence(regionStart, matchStart).toString(), out);
+
+      // Don't include a . or , in the text that is linked.
+      if (in.charAt(matchEnd - 1) == ',' || in.charAt(matchEnd - 1) == '.') {
+        matchEnd--;
+      }
+
+      if (matcher.group(EMAIL_GROUP) != null) {
+        formatEmail(in, matchStart, matchEnd, out);
+      } else {
+        formatUrl(in, matchStart, matchEnd,
+        // Add a scheme if the one wasn't found.
+            matcher.group(WITH_SCHEME_GROUP) == null, out);
+      }
+
+      regionStart = matchEnd;
+    }
+
+    // Escape the text after the last match, and append it to the output.
+    htmlCharEscaper.filter(in.substring(regionStart, end), out);
+  }
+
+  /**
+   * Formats the input sequence into a suitable mailto: anchor tag and appends it to the output.
+   * 
+   * @param in The string that contains the email.
+   * @param start The start of the email address in the whole string.
+   * @param end The end of the email in the whole string.
+   * @param out The text output that the email address should be appended to.
+   * @throws IOException
+   */
+  private void formatEmail(String in, int start, int end, Appendable out) throws IOException {
+
+    String emailPart = in.substring(start, end);
+
+    out.append("<a href=\"mailto:");
+    htmlEscaper.filter(emailPart, out);
+    out.append("\">");
+    htmlEscaper.filter(emailPart, out);
+    out.append("</a>");
+  }
+
+  /**
+   * Formats the input sequence into a suitable anchor tag and appends it to the output.
+   * 
+   * @param in The string that contains the url.
+   * @param start The start of the url in the containing string.
+   * @param end The end of the url in the containing string.
+   * @param addScheme true if 'http://' should be added to the anchor.
+   * @param out The text output that the url should be appended to.
+   * @throws IOException
+   */
+  private void formatUrl(String in, int start, int end, boolean addScheme, Appendable out)
+      throws IOException {
+
+    String urlPart = in.substring(start, end);
+
+    out.append(" <a target=\"_blank\" href=\"");
+    if (addScheme) {
+      out.append("http://");
+    }
+    htmlEscaper.filter(urlPart, out);
+    out.append("\">");
+    htmlEscaper.filter(urlPart, out);
+    out.append("</a>");
+  }
+
+  /**
+   * Attempts to detect if a string contains ascii art, whitespace such as tabs will suppress ascii
+   * art detection.
+   * 
+   * This method takes its conditions from ClearSilver to maintain compatibility. See
+   * has_space_formatting in html.c in the ClearSilver source.
+   * 
+   * @param in The string to analyze for ascii art.
+   * @return true if it is believed that the string contains ascii art.
+   */
+  private boolean hasAsciiArt(String in) {
+    int spaces = 0;
+    int returns = 0;
+    int asciiArt = 0;
+    int x = 0;
+    char[] inChars = in.toCharArray();
+
+    int length = in.length();
+    for (x = 0; x < length; x++) {
+
+      switch (inChars[x]) {
+        case '\t':
+          return false;
+
+        case '\r':
+          break;
+
+        case ' ':
+          // Ignore spaces after full stops.
+          if (x == 0 || inChars[x - 1] != '.') {
+            spaces++;
+          }
+          break;
+
+        case '\n':
+          spaces = 0;
+          returns++;
+          break;
+
+        // Characters to count towards the art total.
+        case '/':
+        case '\\':
+        case '<':
+        case '>':
+        case ':':
+        case '[':
+        case ']':
+        case '!':
+        case '@':
+        case '#':
+        case '$':
+        case '%':
+        case '^':
+        case '&':
+        case '*':
+        case '(':
+        case ')':
+        case '|':
+          asciiArt++;
+          if (asciiArt > 3) {
+            return true;
+          }
+          break;
+
+        default:
+          if (returns > 2) {
+            return false;
+          }
+          if (spaces > 2) {
+            return false;
+          }
+          returns = 0;
+          spaces = 0;
+          asciiArt = 0;
+          break;
+      }
+    }
+
+    return false;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/numeric/AbsFunction.java b/src/com/google/clearsilver/jsilver/functions/numeric/AbsFunction.java
new file mode 100644
index 0000000..4b5637d
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/numeric/AbsFunction.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.numeric;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+import static java.lang.Math.abs;
+
+/**
+ * Returns the absolute value of the numeric expressions.
+ */
+public class AbsFunction extends NonEscapingFunction {
+
+  /**
+   * @param args Single numeric value
+   * @return Absolute value
+   */
+  public Value execute(Value... args) {
+    Value arg = args[0];
+    return literalConstant(abs(arg.asNumber()), arg);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/numeric/MaxFunction.java b/src/com/google/clearsilver/jsilver/functions/numeric/MaxFunction.java
new file mode 100644
index 0000000..9c95664
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/numeric/MaxFunction.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.numeric;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+import static java.lang.Math.max;
+
+/**
+ * Returns the larger of two numeric expressions.
+ */
+public class MaxFunction extends NonEscapingFunction {
+
+  /**
+   * @param args Two numeric values
+   * @return Largest of these
+   */
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(max(left.asNumber(), right.asNumber()), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/numeric/MinFunction.java b/src/com/google/clearsilver/jsilver/functions/numeric/MinFunction.java
new file mode 100644
index 0000000..21fa2fd
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/numeric/MinFunction.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.numeric;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+import static java.lang.Math.min;
+
+/**
+ * Returns the smaller of two numeric expressions.
+ */
+public class MinFunction extends NonEscapingFunction {
+
+  /**
+   * @param args Two numeric values
+   * @return Smallest of these
+   */
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(min(left.asNumber(), right.asNumber()), left, right);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/AddFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/AddFunction.java
new file mode 100644
index 0000000..c64d73b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/AddFunction.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalValue;
+
+/**
+ * X + Y (string).
+ */
+public class AddFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    EscapeMode mode = EscapeMode.combineModes(left.getEscapeMode(), right.getEscapeMode());
+    return literalValue(left.asString() + right.asString(), mode, left.isPartiallyEscaped()
+        || right.isPartiallyEscaped());
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/AndFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/AndFunction.java
new file mode 100644
index 0000000..db09856
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/AndFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X && Y.
+ */
+public class AndFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asBoolean() && right.asBoolean(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/DivideFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/DivideFunction.java
new file mode 100644
index 0000000..7b3a695
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/DivideFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X / Y.
+ */
+public class DivideFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asNumber() / right.asNumber(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/EqualFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/EqualFunction.java
new file mode 100644
index 0000000..bd6cc72
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/EqualFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X == Y (string).
+ */
+public class EqualFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.equals(right), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/ExistsFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/ExistsFunction.java
new file mode 100644
index 0000000..3b29b6e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/ExistsFunction.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * ?X.
+ */
+public class ExistsFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value value = args[0];
+    return literalConstant(value.exists(), value);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/GreaterFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/GreaterFunction.java
new file mode 100644
index 0000000..cfa132e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/GreaterFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X &gt; Y.
+ */
+public class GreaterFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asNumber() > right.asNumber(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/GreaterOrEqualFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/GreaterOrEqualFunction.java
new file mode 100644
index 0000000..fdacdf9
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/GreaterOrEqualFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X &gt;= Y.
+ */
+public class GreaterOrEqualFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asNumber() >= right.asNumber(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/LessFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/LessFunction.java
new file mode 100644
index 0000000..b227d9b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/LessFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X &lt; Y.
+ */
+public class LessFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asNumber() < right.asNumber(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/LessOrEqualFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/LessOrEqualFunction.java
new file mode 100644
index 0000000..658f804
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/LessOrEqualFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X &lt;= Y.
+ */
+public class LessOrEqualFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asNumber() <= right.asNumber(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/ModuloFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/ModuloFunction.java
new file mode 100644
index 0000000..5af08a5
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/ModuloFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X % Y.
+ */
+public class ModuloFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asNumber() % right.asNumber(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/MultiplyFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/MultiplyFunction.java
new file mode 100644
index 0000000..18ac665
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/MultiplyFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X * Y.
+ */
+public class MultiplyFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asNumber() * right.asNumber(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/NotEqualFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/NotEqualFunction.java
new file mode 100644
index 0000000..7823bec
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/NotEqualFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X != Y (string).
+ */
+public class NotEqualFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(!left.equals(right), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/NotFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/NotFunction.java
new file mode 100644
index 0000000..9723e04
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/NotFunction.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * !X.
+ */
+public class NotFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value value = args[0];
+    return literalConstant(!value.asBoolean(), value);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/NumericAddFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/NumericAddFunction.java
new file mode 100644
index 0000000..7e19d32
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/NumericAddFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X + Y (numeric).
+ */
+public class NumericAddFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asNumber() + right.asNumber(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/NumericEqualFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/NumericEqualFunction.java
new file mode 100644
index 0000000..21c92c8
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/NumericEqualFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X == Y (numeric).
+ */
+public class NumericEqualFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asNumber() == right.asNumber(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/NumericFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/NumericFunction.java
new file mode 100644
index 0000000..b10b0cb
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/NumericFunction.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * #X (numeric). Forces a value to a number.
+ */
+public class NumericFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value value = args[0];
+    return literalConstant(value.asNumber(), value);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/NumericNotEqualFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/NumericNotEqualFunction.java
new file mode 100644
index 0000000..f40390c
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/NumericNotEqualFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X != Y (numeric).
+ */
+public class NumericNotEqualFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asNumber() != right.asNumber(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/OrFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/OrFunction.java
new file mode 100644
index 0000000..55ceeb7
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/OrFunction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X || Y.
+ */
+public class OrFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    Value left = args[0];
+    Value right = args[1];
+    return literalConstant(left.asBoolean() || right.asBoolean(), left, right);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/operators/SubtractFunction.java b/src/com/google/clearsilver/jsilver/functions/operators/SubtractFunction.java
new file mode 100644
index 0000000..387745b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/operators/SubtractFunction.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.operators;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * X - Y.
+ */
+public class SubtractFunction extends NonEscapingFunction {
+
+  public Value execute(Value... args) {
+    if (args.length == 1) {
+      Value value = args[0];
+      return literalConstant(0 - value.asNumber(), value);
+    } else {
+      Value left = args[0];
+      Value right = args[1];
+      return literalConstant(left.asNumber() - right.asNumber(), left, right);
+    }
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/string/CrcFunction.java b/src/com/google/clearsilver/jsilver/functions/string/CrcFunction.java
new file mode 100644
index 0000000..6e08bf6
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/string/CrcFunction.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.string;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+import java.io.UnsupportedEncodingException;
+import java.util.zip.CRC32;
+import java.util.zip.Checksum;
+
+/**
+ * Returns the CRC-32 of a string.
+ */
+public class CrcFunction extends NonEscapingFunction {
+
+  /**
+   * @param args 1 string expression
+   * @return CRC-32 of string as number value
+   */
+  public Value execute(Value... args) {
+    String string = args[0].asString();
+    // This function produces a 'standard' CRC-32 (IV -1, reflected polynomial,
+    // and final complement step). The CRC-32 of "123456789" is 0xCBF43926.
+    Checksum crc = new CRC32();
+    byte[] b;
+    try {
+      b = string.getBytes("UTF-8");
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError("UTF-8 must be supported");
+    }
+    crc.update(b, 0, b.length);
+    // Note that we need to cast to signed int, but that's okay because the
+    // CRC fits into 32 bits by definition.
+    return literalConstant((int) crc.getValue(), args[0]);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/string/FindFunction.java b/src/com/google/clearsilver/jsilver/functions/string/FindFunction.java
new file mode 100644
index 0000000..55b6c6f
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/string/FindFunction.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.string;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * Returns the numeric position of the substring in the string (if found), otherwise returns -1
+ * similar to the Python string.find method.
+ */
+public class FindFunction extends NonEscapingFunction {
+
+  /**
+   * @param args 2 string expressions (full string and substring)
+   * @return Position of the start of substring (or -1 if not found) as number value
+   */
+  public Value execute(Value... args) {
+    Value fullStringValue = args[0];
+    Value subStringValue = args[1];
+    return literalConstant(fullStringValue.asString().indexOf(subStringValue.asString()),
+        fullStringValue, subStringValue);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/string/LengthFunction.java b/src/com/google/clearsilver/jsilver/functions/string/LengthFunction.java
new file mode 100644
index 0000000..f232641
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/string/LengthFunction.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.string;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+
+/**
+ * Returns the length of the string expression.
+ */
+public class LengthFunction extends NonEscapingFunction {
+
+  /**
+   * @param args A single string value
+   * @return Length as number value
+   */
+  public Value execute(Value... args) {
+    Value value = args[0];
+    return literalConstant(value.asString().length(), value);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/string/SliceFunction.java b/src/com/google/clearsilver/jsilver/functions/string/SliceFunction.java
new file mode 100644
index 0000000..b917c95
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/string/SliceFunction.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.string;
+
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+import static com.google.clearsilver.jsilver.values.Value.literalValue;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+/**
+ * Returns the string slice starting at start and ending at end, similar to the Python slice
+ * operator.
+ */
+public class SliceFunction extends NonEscapingFunction {
+
+  /**
+   * @param args 1 string values then 2 numeric values (start and end).
+   * @return Sliced string
+   */
+  public Value execute(Value... args) {
+    Value stringValue = args[0];
+    Value startValue = args[1];
+    Value endValue = args[2];
+    String string = stringValue.asString();
+    int start = startValue.asNumber();
+    int end = endValue.asNumber();
+    int length = string.length();
+
+    if (start < 0) {
+      start += max(-start, length);
+      if (end == 0) {
+        end = length;
+      }
+    }
+
+    if (end < 0) {
+      end += length;
+    }
+
+    end = min(end, length);
+
+    if (end < start) {
+      return literalConstant("", args[0]);
+    }
+
+    return literalValue(string.substring(start, end), stringValue.getEscapeMode(), stringValue
+        .isPartiallyEscaped()
+        || startValue.isPartiallyEscaped() || endValue.isPartiallyEscaped());
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/structure/FirstFunction.java b/src/com/google/clearsilver/jsilver/functions/structure/FirstFunction.java
new file mode 100644
index 0000000..9b08504
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/structure/FirstFunction.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.structure;
+
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+import com.google.clearsilver.jsilver.values.VariableValue;
+
+/**
+ * Returns true if the local variable is the first in a loop or each.
+ */
+public class FirstFunction extends NonEscapingFunction {
+
+  /**
+   * @param args A local variable.
+   * @return Boolean value.
+   */
+  public Value execute(Value... args) {
+    VariableValue arg = (VariableValue) args[0];
+    if (arg.getReference() == null) {
+      return literalConstant(false, arg);
+    }
+
+    Data thisNode = arg.getReference().getSymlink();
+    return literalConstant(thisNode.isFirstSibling(), arg);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/structure/LastFunction.java b/src/com/google/clearsilver/jsilver/functions/structure/LastFunction.java
new file mode 100644
index 0000000..4c31e6c
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/structure/LastFunction.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.structure;
+
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+import com.google.clearsilver.jsilver.values.VariableValue;
+
+/**
+ * Returns true if the local variable is the last in a loop or each.
+ */
+public class LastFunction extends NonEscapingFunction {
+
+  /**
+   * @param args A local variable.
+   * @return Boolean value.
+   */
+  public Value execute(Value... args) {
+    VariableValue arg = (VariableValue) args[0];
+    if (arg.getReference() == null) {
+      return literalConstant(false, arg);
+    }
+
+    Data thisNode = arg.getReference().getSymlink();
+    return literalConstant(thisNode.isLastSibling(), arg);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/structure/NameFunction.java b/src/com/google/clearsilver/jsilver/functions/structure/NameFunction.java
new file mode 100644
index 0000000..1faf114
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/structure/NameFunction.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.structure;
+
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+import static com.google.clearsilver.jsilver.values.Value.literalValue;
+import com.google.clearsilver.jsilver.values.VariableValue;
+
+/**
+ * Returns the Data variable name for a local variable alias.
+ */
+public class NameFunction extends NonEscapingFunction {
+
+  /**
+   * @param args A local variable
+   * @return Name (as string)
+   */
+  public Value execute(Value... args) {
+    Value value = args[0];
+    if (value instanceof VariableValue) {
+      VariableValue variableValue = (VariableValue) value;
+      Data variable = variableValue.getReference();
+      if (variable != null) {
+        return literalValue(variable.getSymlink().getName(), variableValue.getEscapeMode(),
+            variableValue.isPartiallyEscaped());
+      }
+    }
+    return literalConstant("", value);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/functions/structure/SubcountFunction.java b/src/com/google/clearsilver/jsilver/functions/structure/SubcountFunction.java
new file mode 100644
index 0000000..1156d14
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/functions/structure/SubcountFunction.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.functions.structure;
+
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.functions.NonEscapingFunction;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalConstant;
+import com.google.clearsilver.jsilver.values.VariableValue;
+
+/**
+ * Returns the number of child nodes for the HDF variable.
+ */
+public class SubcountFunction extends NonEscapingFunction {
+
+  /**
+   * @param args A variable value referring to an HDF node
+   * @return Number of children
+   */
+  public Value execute(Value... args) {
+    VariableValue arg = (VariableValue) args[0];
+    if (arg.getReference() == null) {
+      return literalConstant(0, arg);
+    }
+
+    Data thisNode = arg.getReference().getSymlink();
+    return literalConstant(thisNode.getChildCount(), arg);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/interpreter/ExpressionEvaluator.java b/src/com/google/clearsilver/jsilver/interpreter/ExpressionEvaluator.java
new file mode 100644
index 0000000..8742bff
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/interpreter/ExpressionEvaluator.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.interpreter;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.DataContext;
+import com.google.clearsilver.jsilver.functions.FunctionExecutor;
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.AAddExpression;
+import com.google.clearsilver.jsilver.syntax.node.AAndExpression;
+import com.google.clearsilver.jsilver.syntax.node.ADecimalExpression;
+import com.google.clearsilver.jsilver.syntax.node.ADescendVariable;
+import com.google.clearsilver.jsilver.syntax.node.ADivideExpression;
+import com.google.clearsilver.jsilver.syntax.node.AEqExpression;
+import com.google.clearsilver.jsilver.syntax.node.AExistsExpression;
+import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression;
+import com.google.clearsilver.jsilver.syntax.node.AGtExpression;
+import com.google.clearsilver.jsilver.syntax.node.AGteExpression;
+import com.google.clearsilver.jsilver.syntax.node.AHexExpression;
+import com.google.clearsilver.jsilver.syntax.node.ALtExpression;
+import com.google.clearsilver.jsilver.syntax.node.ALteExpression;
+import com.google.clearsilver.jsilver.syntax.node.AModuloExpression;
+import com.google.clearsilver.jsilver.syntax.node.AMultiplyExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
+import com.google.clearsilver.jsilver.syntax.node.ANeExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANegativeExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANotExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericAddExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericEqExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericNeExpression;
+import com.google.clearsilver.jsilver.syntax.node.AOrExpression;
+import com.google.clearsilver.jsilver.syntax.node.AStringExpression;
+import com.google.clearsilver.jsilver.syntax.node.ASubtractExpression;
+import com.google.clearsilver.jsilver.syntax.node.AVariableExpression;
+import com.google.clearsilver.jsilver.syntax.node.PExpression;
+import com.google.clearsilver.jsilver.values.Value;
+import static com.google.clearsilver.jsilver.values.Value.literalValue;
+
+import java.util.LinkedList;
+
+/**
+ * Walks the tree of a PExpression node and evaluates the expression.
+ * @see #evaluate(PExpression)
+ */
+public class ExpressionEvaluator extends DepthFirstAdapter {
+
+  private Value currentValue;
+
+  private final DataContext context;
+
+  private final FunctionExecutor functionExecutor;
+
+  /**
+   * @param context
+   * @param functionExecutor Used for executing functions in expressions. As well as looking up
+   *        named functions (e.g. html_escape), it also uses
+   */
+  public ExpressionEvaluator(DataContext context, FunctionExecutor functionExecutor) {
+    this.context = context;
+    this.functionExecutor = functionExecutor;
+  }
+
+  /**
+   * Evaluate an expression into a single value.
+   */
+  public Value evaluate(PExpression expression) {
+    assert currentValue == null;
+
+    expression.apply(this);
+    Value result = currentValue;
+    currentValue = null;
+
+    assert result != null : "No result set from " + expression.getClass();
+    return result;
+  }
+
+  @Override
+  public void caseAVariableExpression(AVariableExpression node) {
+    VariableLocator variableLocator = new VariableLocator(this);
+    String variableName = variableLocator.getVariableName(node.getVariable());
+    setResult(Value.variableValue(variableName, context));
+  }
+
+  @Override
+  public void caseAStringExpression(AStringExpression node) {
+    String value = node.getValue().getText();
+    value = value.substring(1, value.length() - 1); // Remove enclosing quotes.
+    // The expression was a constant string literal. Does not
+    // need to be autoescaped, as it was created by the template developer.
+    Value result = literalValue(value, EscapeMode.ESCAPE_IS_CONSTANT, false);
+    setResult(result);
+  }
+
+  @Override
+  public void caseADecimalExpression(ADecimalExpression node) {
+    String value = node.getValue().getText();
+    setResult(literalValue(Integer.parseInt(value), EscapeMode.ESCAPE_IS_CONSTANT, false));
+  }
+
+  @Override
+  public void caseAHexExpression(AHexExpression node) {
+    String value = node.getValue().getText();
+    value = value.substring(2); // Remove 0x prefix.
+    setResult(literalValue(Integer.parseInt(value, 16), EscapeMode.ESCAPE_IS_CONSTANT, false));
+  }
+
+  @Override
+  public void caseANumericExpression(ANumericExpression node) {
+    executeFunction("#", node.getExpression());
+  }
+
+  @Override
+  public void caseANotExpression(ANotExpression node) {
+    executeFunction("!", node.getExpression());
+  }
+
+  @Override
+  public void caseAExistsExpression(AExistsExpression node) {
+    executeFunction("?", node.getExpression());
+  }
+
+  @Override
+  public void caseAEqExpression(AEqExpression node) {
+    executeFunction("==", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseANumericEqExpression(ANumericEqExpression node) {
+    executeFunction("#==", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseANeExpression(ANeExpression node) {
+    executeFunction("!=", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseANumericNeExpression(ANumericNeExpression node) {
+    executeFunction("#!=", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseALtExpression(ALtExpression node) {
+    executeFunction("<", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseAGtExpression(AGtExpression node) {
+    executeFunction(">", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseALteExpression(ALteExpression node) {
+    executeFunction("<=", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseAGteExpression(AGteExpression node) {
+    executeFunction(">=", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseAAndExpression(AAndExpression node) {
+    executeFunction("&&", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseAOrExpression(AOrExpression node) {
+    executeFunction("||", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseAAddExpression(AAddExpression node) {
+    executeFunction("+", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseANumericAddExpression(ANumericAddExpression node) {
+    executeFunction("#+", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseASubtractExpression(ASubtractExpression node) {
+    executeFunction("-", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseAMultiplyExpression(AMultiplyExpression node) {
+    executeFunction("*", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseADivideExpression(ADivideExpression node) {
+    executeFunction("/", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseAModuloExpression(AModuloExpression node) {
+    executeFunction("%", node.getLeft(), node.getRight());
+  }
+
+  @Override
+  public void caseANegativeExpression(ANegativeExpression node) {
+    executeFunction("-", node.getExpression());
+  }
+
+  @Override
+  public void caseAFunctionExpression(AFunctionExpression node) {
+    LinkedList<PExpression> argsList = node.getArgs();
+    PExpression[] args = argsList.toArray(new PExpression[argsList.size()]);
+
+    executeFunction(getFullFunctionName(node), args);
+  }
+
+  private void executeFunction(String name, PExpression... expressions) {
+    Value[] args = new Value[expressions.length];
+    for (int i = 0; i < args.length; i++) {
+      args[i] = evaluate(expressions[i]);
+    }
+
+    setResult(functionExecutor.executeFunction(name, args));
+  }
+
+  /**
+   * Sets a result from inside an expression.
+   */
+  private void setResult(Value value) {
+    assert value != null;
+
+    currentValue = value;
+  }
+
+  private String getFullFunctionName(AFunctionExpression node) {
+    final StringBuilder result = new StringBuilder();
+    node.getName().apply(new DepthFirstAdapter() {
+
+      @Override
+      public void caseANameVariable(ANameVariable node) {
+        result.append(node.getWord().getText());
+      }
+
+      @Override
+      public void caseADescendVariable(ADescendVariable node) {
+        node.getParent().apply(this);
+        result.append('.');
+        node.getChild().apply(this);
+      }
+    });
+    return result.toString();
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/interpreter/InterpretedMacro.java b/src/com/google/clearsilver/jsilver/interpreter/InterpretedMacro.java
new file mode 100644
index 0000000..8b9c439
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/interpreter/InterpretedMacro.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.interpreter;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.syntax.node.PCommand;
+import com.google.clearsilver.jsilver.template.Macro;
+import com.google.clearsilver.jsilver.template.RenderingContext;
+import com.google.clearsilver.jsilver.template.Template;
+
+import java.io.IOException;
+
+/**
+ * User defined macro that will be executed by the interpreter.
+ * 
+ * NOTE: This is not thread safe and cannot be shared between RenderingContexts. This is taken care
+ * of by the TemplateInterpreter.
+ */
+public class InterpretedMacro implements Macro {
+
+  private final PCommand command;
+  private final Template owningTemplate;
+  private final String macroName;
+  private final String[] argumentNames;
+  private final TemplateInterpreter templateInterpreter;
+  private final RenderingContext owningContext;
+
+  public InterpretedMacro(PCommand command, Template owningTemplate, String macroName,
+      String[] argumentNames, TemplateInterpreter templateInterpreter,
+      RenderingContext owningContext) {
+    this.command = command;
+    this.owningTemplate = owningTemplate;
+    this.macroName = macroName;
+    this.argumentNames = argumentNames;
+    this.templateInterpreter = templateInterpreter;
+    this.owningContext = owningContext;
+  }
+
+  @Override
+  public void render(RenderingContext context) throws IOException {
+    assert context == owningContext : "Cannot render macro defined in another context";
+    context.pushExecutionContext(this);
+    boolean doRuntimeAutoEscaping = !(context.isRuntimeAutoEscaping());
+    if (doRuntimeAutoEscaping) {
+      context.startRuntimeAutoEscaping();
+    }
+    command.apply(templateInterpreter);
+    if (doRuntimeAutoEscaping) {
+      context.stopRuntimeAutoEscaping();
+    }
+    context.popExecutionContext();
+  }
+
+  @Override
+  public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException {
+    render(createRenderingContext(data, out, resourceLoader));
+  }
+
+  @Override
+  public RenderingContext createRenderingContext(Data data, Appendable out,
+      ResourceLoader resourceLoader) {
+    return owningTemplate.createRenderingContext(data, out, resourceLoader);
+  }
+
+  @Override
+  public String getTemplateName() {
+    return owningTemplate.getTemplateName();
+  }
+
+  @Override
+  public EscapeMode getEscapeMode() {
+    return owningTemplate.getEscapeMode();
+  }
+
+  @Override
+  public String getDisplayName() {
+    return owningTemplate.getDisplayName() + ":" + macroName;
+  }
+
+  @Override
+  public String getMacroName() {
+    return macroName;
+  }
+
+  @Override
+  public String getArgumentName(int index) {
+    if (index >= argumentNames.length) {
+      // TODO: Make sure this behavior of failing if too many
+      // arguments are passed to a macro is consistent with JNI / interpreter.
+      throw new JSilverInterpreterException("Too many arguments supplied to macro " + macroName);
+    }
+    return argumentNames[index];
+  }
+
+  @Override
+  public int getArgumentCount() {
+    return argumentNames.length;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/interpreter/InterpretedTemplate.java b/src/com/google/clearsilver/jsilver/interpreter/InterpretedTemplate.java
new file mode 100644
index 0000000..506965e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/interpreter/InterpretedTemplate.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.interpreter;
+
+import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.data.DataContext;
+import com.google.clearsilver.jsilver.data.DefaultDataContext;
+import com.google.clearsilver.jsilver.functions.FunctionExecutor;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree;
+import com.google.clearsilver.jsilver.template.DefaultRenderingContext;
+import com.google.clearsilver.jsilver.template.RenderingContext;
+import com.google.clearsilver.jsilver.template.Template;
+import com.google.clearsilver.jsilver.template.TemplateLoader;
+
+import java.io.IOException;
+
+/**
+ * Template implementation that uses the interpreter to render itself.
+ */
+public class InterpretedTemplate implements Template {
+
+  private final TemplateLoader loader;
+
+  private final TemplateSyntaxTree syntaxTree;
+  private final String name;
+  private final FunctionExecutor functionExecutor;
+  private final EscapeMode escapeMode;
+  private final AutoEscapeOptions autoEscapeOptions;
+
+  public InterpretedTemplate(TemplateLoader loader, TemplateSyntaxTree syntaxTree, String name,
+      FunctionExecutor functionExecutor, AutoEscapeOptions autoEscapeOptions, EscapeMode mode) {
+    this.loader = loader;
+    this.syntaxTree = syntaxTree;
+    this.name = name;
+    this.functionExecutor = functionExecutor;
+    this.escapeMode = mode;
+    this.autoEscapeOptions = autoEscapeOptions;
+  }
+
+  @Override
+  public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException {
+    render(createRenderingContext(data, out, resourceLoader));
+  }
+
+  @Override
+  public void render(RenderingContext context) throws IOException {
+    TemplateInterpreter interpreter =
+        new TemplateInterpreter(this, loader, context, functionExecutor);
+    context.pushExecutionContext(this);
+    syntaxTree.apply(interpreter);
+    context.popExecutionContext();
+  }
+
+  @Override
+  public RenderingContext createRenderingContext(Data data, Appendable out,
+      ResourceLoader resourceLoader) {
+    DataContext dataContext = new DefaultDataContext(data);
+    return new DefaultRenderingContext(dataContext, resourceLoader, out, functionExecutor,
+        autoEscapeOptions);
+  }
+
+  @Override
+  public String getTemplateName() {
+    return name;
+  }
+
+  @Override
+  public EscapeMode getEscapeMode() {
+    return escapeMode;
+  }
+
+  @Override
+  public String getDisplayName() {
+    return name;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/interpreter/InterpretedTemplateLoader.java b/src/com/google/clearsilver/jsilver/interpreter/InterpretedTemplateLoader.java
new file mode 100644
index 0000000..988c6e9
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/interpreter/InterpretedTemplateLoader.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.interpreter;
+
+import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.functions.FunctionExecutor;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader;
+import com.google.clearsilver.jsilver.template.Template;
+import com.google.clearsilver.jsilver.template.TemplateLoader;
+
+/**
+ * TemplateLoader that loads InterpretedTemplates.
+ */
+public class InterpretedTemplateLoader implements DelegatingTemplateLoader {
+
+  private final TemplateFactory templateFactory;
+
+  private final FunctionExecutor globalFunctionExecutor;
+  private final AutoEscapeOptions autoEscapeOptions;
+  private TemplateLoader templateLoaderDelegate = this;
+
+  public InterpretedTemplateLoader(TemplateFactory templateFactory,
+      FunctionExecutor globalFunctionExecutor, AutoEscapeOptions autoEscapeOptions) {
+    this.templateFactory = templateFactory;
+    this.globalFunctionExecutor = globalFunctionExecutor;
+    this.autoEscapeOptions = autoEscapeOptions;
+  }
+
+  @Override
+  public void setTemplateLoaderDelegate(TemplateLoader templateLoaderDelegate) {
+    this.templateLoaderDelegate = templateLoaderDelegate;
+  }
+
+  @Override
+  public Template load(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode) {
+    return new InterpretedTemplate(templateLoaderDelegate, templateFactory.find(templateName,
+        resourceLoader, escapeMode), templateName, globalFunctionExecutor, autoEscapeOptions,
+        escapeMode);
+  }
+
+  @Override
+  public Template createTemp(String name, String content, EscapeMode escapingMode) {
+    return new InterpretedTemplate(templateLoaderDelegate, templateFactory.createTemp(content,
+        escapingMode), name, globalFunctionExecutor, autoEscapeOptions, escapingMode);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/interpreter/LoadingTemplateFactory.java b/src/com/google/clearsilver/jsilver/interpreter/LoadingTemplateFactory.java
new file mode 100644
index 0000000..019ccd6
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/interpreter/LoadingTemplateFactory.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.interpreter;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.exceptions.JSilverIOException;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.syntax.SyntaxTreeBuilder;
+import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+/**
+ * Loads a template from disk, and parses it into an AST. Does not do any caching.
+ */
+public class LoadingTemplateFactory implements TemplateFactory {
+
+  private final SyntaxTreeBuilder syntaxTreeBuilder = new SyntaxTreeBuilder();
+
+  public TemplateSyntaxTree find(String templateName, ResourceLoader resourceLoader,
+      EscapeMode escapeMode) {
+    try {
+      Reader reader = resourceLoader.openOrFail(templateName);
+      try {
+        return syntaxTreeBuilder.parse(reader, templateName, escapeMode);
+      } finally {
+        reader.close();
+      }
+    } catch (IOException e) {
+      throw new JSilverIOException(e);
+    }
+  }
+
+  public TemplateSyntaxTree createTemp(String content, EscapeMode escapeMode) {
+    return syntaxTreeBuilder.parse(new StringReader(content), "", escapeMode);
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/interpreter/OptimizerProvider.java b/src/com/google/clearsilver/jsilver/interpreter/OptimizerProvider.java
new file mode 100644
index 0000000..b3be53e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/interpreter/OptimizerProvider.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.interpreter;
+
+import com.google.clearsilver.jsilver.syntax.node.Switch;
+
+/**
+ * This interface is used to provide Optimizer Switches to OptimizingTemplateFactory, some which may
+ * need to be constructed each time it runs.
+ */
+public interface OptimizerProvider {
+  Switch getOptimizer();
+}
diff --git a/src/com/google/clearsilver/jsilver/interpreter/OptimizingTemplateFactory.java b/src/com/google/clearsilver/jsilver/interpreter/OptimizingTemplateFactory.java
new file mode 100644
index 0000000..e894379
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/interpreter/OptimizingTemplateFactory.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.interpreter;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Wraps a template factory with a series of optimization steps. Any null optimization steps are
+ * ignored.
+ */
+public class OptimizingTemplateFactory implements TemplateFactory {
+  private final TemplateFactory wrapped;
+  private final List<OptimizerProvider> optimizers;
+
+  /**
+   * Creates a factory from the given optimization steps that wraps another TemplateFactory.
+   * 
+   * @param wrapped the template factory instance to be wrapped.
+   * @param optimizers the optimizers to apply (null optimizations are ignored).
+   */
+  public OptimizingTemplateFactory(TemplateFactory wrapped, OptimizerProvider... optimizers) {
+    this.wrapped = wrapped;
+    // Ignore null providers during construction.
+    this.optimizers = new ArrayList<OptimizerProvider>();
+    for (OptimizerProvider optimizer : optimizers) {
+      if (optimizer != null) {
+        this.optimizers.add(optimizer);
+      }
+    }
+  }
+
+  private void optimize(TemplateSyntaxTree ast) {
+    for (OptimizerProvider optimizer : optimizers) {
+      ast.apply(optimizer.getOptimizer());
+    }
+  }
+
+  @Override
+  public TemplateSyntaxTree createTemp(String content, EscapeMode escapeMode) {
+    TemplateSyntaxTree result = wrapped.createTemp(content, escapeMode);
+    optimize(result);
+    return result;
+  }
+
+  @Override
+  public TemplateSyntaxTree find(String templateName, ResourceLoader resourceLoader,
+      EscapeMode escapeMode) {
+    TemplateSyntaxTree result = wrapped.find(templateName, resourceLoader, escapeMode);
+    optimize(result);
+    return result;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/interpreter/TemplateFactory.java b/src/com/google/clearsilver/jsilver/interpreter/TemplateFactory.java
new file mode 100644
index 0000000..28fcaa7
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/interpreter/TemplateFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.interpreter;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree;
+
+/**
+ * Responsible for creating/retrieving an AST tree for a template with a given name.
+ * <p>
+ * This interface always expects to take a ResourceLoader object from the caller. This helps
+ * guarantee that per-Template resourceLoaders are respected.
+ */
+public interface TemplateFactory {
+
+  /**
+   * Load a template from the source.
+   * 
+   * @param templateName e.g. some/path/to/template.cs
+   * @param resourceLoader use this ResourceLoader to locate the named template file and any
+   *        included files.
+   * @param escapeMode the type of escaping to apply to the entire template.
+   */
+  TemplateSyntaxTree find(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode);
+
+  /**
+   * Create a temporary template from content.
+   * 
+   * @param content e.g. "Hello &lt;cs var:name &gt;"
+   * @param escapeMode
+   * @param escapeMode the type of escaping to apply to the entire template.
+   */
+  TemplateSyntaxTree createTemp(String content, EscapeMode escapeMode);
+
+}
diff --git a/src/com/google/clearsilver/jsilver/interpreter/TemplateInterpreter.java b/src/com/google/clearsilver/jsilver/interpreter/TemplateInterpreter.java
new file mode 100644
index 0000000..e38d637
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/interpreter/TemplateInterpreter.java
@@ -0,0 +1,669 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.interpreter;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.data.DataContext;
+import com.google.clearsilver.jsilver.exceptions.ExceptionUtil;
+import com.google.clearsilver.jsilver.exceptions.JSilverIOException;
+import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
+import com.google.clearsilver.jsilver.functions.FunctionExecutor;
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.AAltCommand;
+import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ACallCommand;
+import com.google.clearsilver.jsilver.syntax.node.ADataCommand;
+import com.google.clearsilver.jsilver.syntax.node.ADefCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEachCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AIfCommand;
+import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopIncCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopToCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.ANameCommand;
+import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
+import com.google.clearsilver.jsilver.syntax.node.ASetCommand;
+import com.google.clearsilver.jsilver.syntax.node.AUvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AVarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AWithCommand;
+import com.google.clearsilver.jsilver.syntax.node.PCommand;
+import com.google.clearsilver.jsilver.syntax.node.PExpression;
+import com.google.clearsilver.jsilver.syntax.node.PPosition;
+import com.google.clearsilver.jsilver.syntax.node.PVariable;
+import com.google.clearsilver.jsilver.syntax.node.TCsOpen;
+import com.google.clearsilver.jsilver.syntax.node.TWord;
+import com.google.clearsilver.jsilver.template.Macro;
+import com.google.clearsilver.jsilver.template.RenderingContext;
+import com.google.clearsilver.jsilver.template.Template;
+import com.google.clearsilver.jsilver.template.TemplateLoader;
+import com.google.clearsilver.jsilver.values.Value;
+import com.google.clearsilver.jsilver.values.VariableValue;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * Main JSilver interpreter. This walks a template's AST and renders the result out.
+ */
+public class TemplateInterpreter extends DepthFirstAdapter {
+
+  private final Template template;
+
+  private final ExpressionEvaluator expressionEvaluator;
+  private final VariableLocator variableLocator;
+  private final TemplateLoader templateLoader;
+  private final RenderingContext context;
+  private final DataContext dataContext;
+
+  public TemplateInterpreter(Template template, TemplateLoader templateLoader,
+      RenderingContext context, FunctionExecutor functionExecutor) {
+    this.template = template;
+    this.templateLoader = templateLoader;
+    this.context = context;
+    this.dataContext = context.getDataContext();
+
+    expressionEvaluator = new ExpressionEvaluator(dataContext, functionExecutor);
+    variableLocator = new VariableLocator(expressionEvaluator);
+  }
+
+  // ------------------------------------------------------------------------
+  // COMMAND PROCESSING
+
+  /**
+   * Chunk of data (i.e. not a CS command).
+   */
+  @Override
+  public void caseADataCommand(ADataCommand node) {
+    context.writeUnescaped(node.getData().getText());
+  }
+
+  /**
+   * &lt;?cs var:blah &gt; expression. Evaluate as string and write output, using default escaping.
+   */
+  @Override
+  public void caseAVarCommand(AVarCommand node) {
+    setLastPosition(node.getPosition());
+
+    // Evaluate expression.
+    Value value = expressionEvaluator.evaluate(node.getExpression());
+    writeVariable(value);
+  }
+
+  /**
+   * &lt;?cs uvar:blah &gt; expression. Evaluate as string and write output, but don't escape.
+   */
+  @Override
+  public void caseAUvarCommand(AUvarCommand node) {
+    setLastPosition(node.getPosition());
+
+    // Evaluate expression.
+    Value value = expressionEvaluator.evaluate(node.getExpression());
+    context.writeUnescaped(value.asString());
+  }
+
+  /**
+   * &lt;?cs lvar:blah &gt; command. Evaluate expression and execute commands within.
+   */
+  @Override
+  public void caseALvarCommand(ALvarCommand node) {
+    setLastPosition(node.getPosition());
+    evaluateVariable(node.getExpression(), "[lvar expression]");
+  }
+
+  /**
+   * &lt;?cs evar:blah &gt; command. Evaluate expression and execute commands within.
+   */
+  @Override
+  public void caseAEvarCommand(AEvarCommand node) {
+    setLastPosition(node.getPosition());
+    evaluateVariable(node.getExpression(), "[evar expression]");
+  }
+
+  private void evaluateVariable(PExpression expression, String stackTraceDescription) {
+    // Evaluate expression.
+    Value value = expressionEvaluator.evaluate(expression);
+
+    // Now parse result, into new mini template.
+    Template template =
+        templateLoader.createTemp(stackTraceDescription, value.asString(), context
+            .getAutoEscapeMode());
+
+    // Intepret new template.
+    try {
+      template.render(context);
+    } catch (IOException e) {
+      throw new JSilverInterpreterException(e.getMessage());
+    }
+  }
+
+  /**
+   * &lt;?cs linclude!'somefile.cs' &gt; command. Lazily includes another template (at render time).
+   * Throw an error if file does not exist.
+   */
+  @Override
+  public void caseAHardLincludeCommand(AHardLincludeCommand node) {
+    setLastPosition(node.getPosition());
+    include(node.getExpression(), false);
+  }
+
+  /**
+   * &lt;?cs linclude:'somefile.cs' &gt; command. Lazily includes another template (at render time).
+   * Silently ignore if the included file does not exist.
+   */
+  @Override
+  public void caseALincludeCommand(ALincludeCommand node) {
+    setLastPosition(node.getPosition());
+    include(node.getExpression(), true);
+  }
+
+  /**
+   * &lt;?cs include!'somefile.cs' &gt; command. Throw an error if file does not exist.
+   */
+  @Override
+  public void caseAHardIncludeCommand(AHardIncludeCommand node) {
+    setLastPosition(node.getPosition());
+    include(node.getExpression(), false);
+  }
+
+  /**
+   * &lt;?cs include:'somefile.cs' &gt; command. Silently ignore if the included file does not
+   * exist.
+   */
+  @Override
+  public void caseAIncludeCommand(AIncludeCommand node) {
+    setLastPosition(node.getPosition());
+    include(node.getExpression(), true);
+  }
+
+  /**
+   * &lt;?cs set:x='y' &gt; command.
+   */
+  @Override
+  public void caseASetCommand(ASetCommand node) {
+    setLastPosition(node.getPosition());
+    String variableName = variableLocator.getVariableName(node.getVariable());
+
+    try {
+      Data variable = dataContext.findVariable(variableName, true);
+      Value value = expressionEvaluator.evaluate(node.getExpression());
+      variable.setValue(value.asString());
+      // TODO: what about nested structures?
+      // "set" was used to set a variable to a constant or escaped value like
+      // <?cs set: x = "<b>X</b>" ?> or <?cs set: y = html_escape(x) ?>
+      // Keep track of this so autoescaping code can take it into account.
+      variable.setEscapeMode(value.getEscapeMode());
+    } catch (UnsupportedOperationException e) {
+      // An error occurred - probably due to trying to modify an UnmodifiableData
+      throw new UnsupportedOperationException(createUnsupportedOperationMessage(node, context
+          .getIncludedTemplateNames()), e);
+    }
+  }
+
+  /**
+   * &lt;?cs name:blah &gt; command. Writes out the name of the original variable referred to by a
+   * given node.
+   */
+  @Override
+  public void caseANameCommand(ANameCommand node) {
+    setLastPosition(node.getPosition());
+    String variableName = variableLocator.getVariableName(node.getVariable());
+    Data variable = dataContext.findVariable(variableName, false);
+    if (variable != null) {
+      context.writeEscaped(variable.getSymlink().getName());
+    }
+  }
+
+  /**
+   * &lt;?cs if:blah &gt; ... &lt;?cs else &gt; ... &lt;?cs /if &gt; command.
+   */
+  @Override
+  public void caseAIfCommand(AIfCommand node) {
+    setLastPosition(node.getPosition());
+    Value value = expressionEvaluator.evaluate(node.getExpression());
+    if (value.asBoolean()) {
+      node.getBlock().apply(this);
+    } else {
+      node.getOtherwise().apply(this);
+    }
+  }
+
+
+  /**
+   * &lt;?cs escape:'html' &gt; command. Changes default escaping function.
+   */
+  @Override
+  public void caseAEscapeCommand(AEscapeCommand node) {
+    setLastPosition(node.getPosition());
+    Value value = expressionEvaluator.evaluate(node.getExpression());
+    String escapeStrategy = value.asString();
+
+    context.pushEscapingFunction(escapeStrategy);
+    node.getCommand().apply(this);
+    context.popEscapingFunction();
+  }
+
+  /**
+   * A fake command injected by AutoEscaper.
+   * 
+   * AutoEscaper determines the html context in which an include or lvar or evar command is called
+   * and stores this context in the AAutoescapeCommand node.
+   */
+  @Override
+  public void caseAAutoescapeCommand(AAutoescapeCommand node) {
+    setLastPosition(node.getPosition());
+    Value value = expressionEvaluator.evaluate(node.getExpression());
+    String escapeStrategy = value.asString();
+
+    EscapeMode mode = EscapeMode.computeEscapeMode(escapeStrategy);
+
+    context.pushAutoEscapeMode(mode);
+    node.getCommand().apply(this);
+    context.popAutoEscapeMode();
+  }
+
+  /**
+   * &lt;?cs with:x=Something &gt; ... &lt;?cs /with &gt; command. Aliases a value within a specific
+   * scope.
+   */
+  @Override
+  public void caseAWithCommand(AWithCommand node) {
+    setLastPosition(node.getPosition());
+    VariableLocator variableLocator = new VariableLocator(expressionEvaluator);
+    String withVar = variableLocator.getVariableName(node.getVariable());
+    Value value = expressionEvaluator.evaluate(node.getExpression());
+
+    if (value instanceof VariableValue) {
+      if (((VariableValue) value).getReference() == null) {
+        // With refers to a non-existent variable. Do nothing.
+        return;
+      }
+    }
+
+    dataContext.pushVariableScope();
+    setTempVariable(withVar, value);
+    node.getCommand().apply(this);
+    dataContext.popVariableScope();
+  }
+
+  /**
+   * &lt;?cs loop:10 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers, starting at
+   * zero.
+   */
+  @Override
+  public void caseALoopToCommand(ALoopToCommand node) {
+    setLastPosition(node.getPosition());
+    int end = expressionEvaluator.evaluate(node.getExpression()).asNumber();
+
+    // Start is always zero, increment is always 1, so end < 0 is invalid.
+    if (end < 0) {
+      return; // Incrementing the wrong way. Avoid infinite loop.
+    }
+
+    loop(node.getVariable(), 0, end, 1, node.getCommand());
+  }
+
+  /**
+   * &lt;?cs loop:0,10 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers.
+   */
+  @Override
+  public void caseALoopCommand(ALoopCommand node) {
+    setLastPosition(node.getPosition());
+    int start = expressionEvaluator.evaluate(node.getStart()).asNumber();
+    int end = expressionEvaluator.evaluate(node.getEnd()).asNumber();
+
+    // Start is always zero, increment is always 1, so end < 0 is invalid.
+    if (end < start) {
+      return; // Incrementing the wrong way. Avoid infinite loop.
+    }
+
+    loop(node.getVariable(), start, end, 1, node.getCommand());
+  }
+
+  /**
+   * &lt;?cs loop:0,10,2 &gt; ... &lt;?cs /loop &gt; command. Loops over a range of numbers, with a
+   * specific increment.
+   */
+  @Override
+  public void caseALoopIncCommand(ALoopIncCommand node) {
+    setLastPosition(node.getPosition());
+    int start = expressionEvaluator.evaluate(node.getStart()).asNumber();
+    int end = expressionEvaluator.evaluate(node.getEnd()).asNumber();
+    int incr = expressionEvaluator.evaluate(node.getIncrement()).asNumber();
+
+    if (incr == 0) {
+      return; // No increment. Avoid infinite loop.
+    }
+    if (incr > 0 && start > end) {
+      return; // Incrementing the wrong way. Avoid infinite loop.
+    }
+    if (incr < 0 && start < end) {
+      return; // Incrementing the wrong way. Avoid infinite loop.
+    }
+
+    loop(node.getVariable(), start, end, incr, node.getCommand());
+  }
+
+  /**
+   * &lt;?cs each:x=Stuff &gt; ... &lt;?cs /each &gt; command. Loops over child items of a data
+   * node.
+   */
+  @Override
+  public void caseAEachCommand(AEachCommand node) {
+    setLastPosition(node.getPosition());
+    Value expression = expressionEvaluator.evaluate(node.getExpression());
+
+    if (expression instanceof VariableValue) {
+      VariableValue variableValue = (VariableValue) expression;
+      Data parent = variableValue.getReference();
+      if (parent != null) {
+        each(node.getVariable(), variableValue.getName(), parent, node.getCommand());
+      }
+    }
+  }
+
+  /**
+   * &lt;?cs alt:someValue &gt; ... &lt;?cs /alt &gt; command. If value exists, write it, otherwise
+   * write the body of the command.
+   */
+  @Override
+  public void caseAAltCommand(AAltCommand node) {
+    setLastPosition(node.getPosition());
+    Value value = expressionEvaluator.evaluate(node.getExpression());
+    if (value.asBoolean()) {
+      writeVariable(value);
+    } else {
+      node.getCommand().apply(this);
+    }
+  }
+
+  private void writeVariable(Value value) {
+    if (template.getEscapeMode().isAutoEscapingMode()) {
+      autoEscapeAndWriteVariable(value);
+    } else if (value.isPartiallyEscaped()) {
+      context.writeUnescaped(value.asString());
+    } else {
+      context.writeEscaped(value.asString());
+    }
+  }
+
+  private void autoEscapeAndWriteVariable(Value value) {
+    if (isTrustedValue(value) || value.isPartiallyEscaped()) {
+      context.writeUnescaped(value.asString());
+    } else {
+      context.writeEscaped(value.asString());
+    }
+  }
+
+  private boolean isTrustedValue(Value value) {
+    // True if PropagateEscapeStatus is enabled and value has either been
+    // escaped or contains a constant string.
+    return context.getAutoEscapeOptions().getPropagateEscapeStatus()
+        && !value.getEscapeMode().equals(EscapeMode.ESCAPE_NONE);
+  }
+
+  // ------------------------------------------------------------------------
+  // MACROS
+
+  /**
+   * &lt;?cs def:someMacro(x,y) &gt; ... &lt;?cs /def &gt; command. Define a macro (available for
+   * the remainder of the interpreter context.
+   */
+  @Override
+  public void caseADefCommand(ADefCommand node) {
+    String macroName = makeWord(node.getMacro());
+    LinkedList<PVariable> arguments = node.getArguments();
+    String[] argumentNames = new String[arguments.size()];
+    int i = 0;
+    for (PVariable argument : arguments) {
+      if (!(argument instanceof ANameVariable)) {
+        throw new JSilverInterpreterException("Invalid name for macro '" + macroName
+            + "' argument " + i + " : " + argument);
+      }
+      argumentNames[i++] = ((ANameVariable) argument).getWord().getText();
+    }
+    // TODO: Should we enforce that macro args can't repeat the same
+    // name?
+    context.registerMacro(macroName, new InterpretedMacro(node.getCommand(), template, macroName,
+        argumentNames, this, context));
+  }
+
+  private String makeWord(LinkedList<TWord> words) {
+    if (words.size() == 1) {
+      return words.getFirst().getText();
+    }
+    StringBuilder result = new StringBuilder();
+    for (TWord word : words) {
+      if (result.length() > 0) {
+        result.append('.');
+      }
+      result.append(word.getText());
+    }
+    return result.toString();
+  }
+
+  /**
+   * &lt;?cs call:someMacro(x,y) command. Call a macro. Need to create a new variable scope to hold
+   * the local variables defined by the parameters of the macro definition
+   */
+  @Override
+  public void caseACallCommand(ACallCommand node) {
+    String macroName = makeWord(node.getMacro());
+    Macro macro = context.findMacro(macroName);
+
+    // Make sure that the number of arguments passed to the macro match the
+    // number expected.
+    if (node.getArguments().size() != macro.getArgumentCount()) {
+      throw new JSilverInterpreterException("Number of arguments to macro " + macroName + " ("
+          + node.getArguments().size() + ") does not match " + "number of expected arguments ("
+          + macro.getArgumentCount() + ")");
+    }
+
+    int numArgs = node.getArguments().size();
+    if (numArgs > 0) {
+      Value[] argValues = new Value[numArgs];
+
+      // We must first evaluate the parameters we are passing or there could be
+      // conflicts if new argument names match existing variables.
+      Iterator<PExpression> argumentValues = node.getArguments().iterator();
+      for (int i = 0; argumentValues.hasNext(); i++) {
+        argValues[i] = expressionEvaluator.evaluate(argumentValues.next());
+      }
+
+      // No need to bother pushing and popping the variable scope stack
+      // if there are no new local variables to declare.
+      dataContext.pushVariableScope();
+
+      for (int i = 0; i < argValues.length; i++) {
+        setTempVariable(macro.getArgumentName(i), argValues[i]);
+      }
+    }
+    try {
+      macro.render(context);
+    } catch (IOException e) {
+      throw new JSilverIOException(e);
+    }
+    if (numArgs > 0) {
+      // No need to bother pushing and popping the variable scope stack
+      // if there are no new local variables to declare.
+      dataContext.popVariableScope();
+    }
+  }
+
+  // ------------------------------------------------------------------------
+  // HELPERS
+  //
+  // Much of the functionality in this section could easily be inlined,
+  // however it makes the rest of the interpreter much easier to understand
+  // and refactor with them defined here.
+
+  private void each(PVariable variable, String parentName, Data items, PCommand command) {
+    // Since HDF variables are now passed to macro parameters by path name
+    // we need to create a path for each child when generating the
+    // VariableValue object.
+    VariableLocator variableLocator = new VariableLocator(expressionEvaluator);
+    String eachVar = variableLocator.getVariableName(variable);
+    StringBuilder pathBuilder = new StringBuilder(parentName);
+    pathBuilder.append('.');
+    int length = pathBuilder.length();
+    dataContext.pushVariableScope();
+    for (Data child : items.getChildren()) {
+      pathBuilder.delete(length, pathBuilder.length());
+      pathBuilder.append(child.getName());
+      setTempVariable(eachVar, Value.variableValue(pathBuilder.toString(), dataContext));
+      command.apply(this);
+    }
+    dataContext.popVariableScope();
+  }
+
+  private void loop(PVariable loopVar, int start, int end, int incr, PCommand command) {
+    VariableLocator variableLocator = new VariableLocator(expressionEvaluator);
+    String varName = variableLocator.getVariableName(loopVar);
+
+    dataContext.pushVariableScope();
+    // Loop deals with counting forward or backwards.
+    for (int index = start; incr > 0 ? index <= end : index >= end; index += incr) {
+      // We reuse the same scope for efficiency and simply overwrite the
+      // previous value of the loop variable.
+      dataContext.createLocalVariableByValue(varName, String.valueOf(index), index == start,
+          index == end);
+
+      command.apply(this);
+    }
+    dataContext.popVariableScope();
+  }
+
+  /**
+   * Code common to all three include commands.
+   * 
+   * @param expression expression representing name of file to include.
+   * @param ignoreMissingFile {@code true} if any FileNotFound error generated by the template
+   *        loader should be ignored, {@code false} otherwise.
+   */
+  private void include(PExpression expression, boolean ignoreMissingFile) {
+    // Evaluate expression.
+    Value path = expressionEvaluator.evaluate(expression);
+
+    String templateName = path.asString();
+    if (!context.pushIncludeStackEntry(templateName)) {
+      throw new JSilverInterpreterException(createIncludeLoopErrorMessage(templateName, context
+          .getIncludedTemplateNames()));
+    }
+
+    loadAndRenderIncludedTemplate(templateName, ignoreMissingFile);
+
+    if (!context.popIncludeStackEntry(templateName)) {
+      // Include stack trace is corrupted
+      throw new IllegalStateException("Unable to find on include stack: " + templateName);
+    }
+  }
+
+  private String createIncludeLoopErrorMessage(String templateName, Iterable<String> includeStack) {
+    StringBuilder message = new StringBuilder();
+    message.append("File included twice: ");
+    message.append(templateName);
+
+    message.append(" Include stack:");
+    for (String fileName : includeStack) {
+      message.append("\n -> ");
+      message.append(fileName);
+    }
+    message.append("\n -> ");
+    message.append(templateName);
+    return message.toString();
+  }
+
+  private String createUnsupportedOperationMessage(PCommand node, Iterable<String> includeStack) {
+    StringBuilder message = new StringBuilder();
+
+    message.append("exception thrown while parsing node: ");
+    message.append(node.toString());
+    message.append(" (class ").append(node.getClass().getSimpleName()).append(")");
+    message.append("\nTemplate include stack: ");
+
+    for (Iterator<String> iter = includeStack.iterator(); iter.hasNext();) {
+      message.append(iter.next());
+      if (iter.hasNext()) {
+        message.append(" -> ");
+      }
+    }
+    message.append("\n");
+
+    return message.toString();
+  }
+
+  // This method should ONLY be called from include()
+  private void loadAndRenderIncludedTemplate(String templateName, boolean ignoreMissingFile) {
+    // Now load new template with given name.
+    Template template = null;
+    try {
+      template =
+          templateLoader.load(templateName, context.getResourceLoader(), context
+              .getAutoEscapeMode());
+    } catch (RuntimeException e) {
+      if (ignoreMissingFile && ExceptionUtil.isFileNotFoundException(e)) {
+        return;
+      } else {
+        throw e;
+      }
+    }
+
+    // Intepret loaded template.
+    try {
+      // TODO: Execute lincludes (but not includes) in a separate
+      // context.
+      template.render(context);
+    } catch (IOException e) {
+      throw new JSilverInterpreterException(e.getMessage());
+    }
+  }
+
+  private void setLastPosition(PPosition position) {
+    // Walks position node which will eventually result in calling
+    // caseTCsOpen().
+    position.apply(this);
+  }
+
+  /**
+   * Every time a &lt;cs token is found, grab the line and position (for helpful error messages).
+   */
+  @Override
+  public void caseTCsOpen(TCsOpen node) {
+    int line = node.getLine();
+    int column = node.getPos();
+    context.setCurrentPosition(line, column);
+  }
+
+  private void setTempVariable(String variableName, Value value) {
+    if (value instanceof VariableValue) {
+      // If the value is a Data variable name, then we store a reference to its
+      // name as discovered by the expression evaluator and resolve it each
+      // time for correctness.
+      dataContext.createLocalVariableByPath(variableName, ((VariableValue) value).getName());
+    } else {
+      dataContext.createLocalVariableByValue(variableName, value.asString(), value.getEscapeMode());
+    }
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/interpreter/VariableLocator.java b/src/com/google/clearsilver/jsilver/interpreter/VariableLocator.java
new file mode 100644
index 0000000..76d4c9a
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/interpreter/VariableLocator.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.interpreter;
+
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.ADecNumberVariable;
+import com.google.clearsilver.jsilver.syntax.node.ADescendVariable;
+import com.google.clearsilver.jsilver.syntax.node.AExpandVariable;
+import com.google.clearsilver.jsilver.syntax.node.AHexNumberVariable;
+import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
+import com.google.clearsilver.jsilver.syntax.node.PVariable;
+import com.google.clearsilver.jsilver.values.Value;
+
+/**
+ * Walks a PVariable node from the parse tree and returns a Data path name.
+ *
+ * @see #getVariableName(com.google.clearsilver.jsilver.syntax.node.PVariable)
+ */
+public class VariableLocator extends DepthFirstAdapter {
+
+  private StringBuilder currentName;
+
+  private final ExpressionEvaluator expressionEvaluator;
+
+  public VariableLocator(ExpressionEvaluator expressionEvaluator) {
+    this.expressionEvaluator = expressionEvaluator;
+  }
+
+  /**
+   * If the root PVariable we are evaluating is a simple one then skip creating the StringBuilder
+   * and descending the tree.
+   * 
+   * @param variable the variable node to evaluate.
+   * @return a String representing the Variable name, or {@code null} if it is a compound variable
+   *         node.
+   */
+  private String quickEval(PVariable variable) {
+    if (variable instanceof ANameVariable) {
+      return ((ANameVariable) variable).getWord().getText();
+    } else if (variable instanceof ADecNumberVariable) {
+      return ((ADecNumberVariable) variable).getDecNumber().getText();
+    } else if (variable instanceof AHexNumberVariable) {
+      return ((AHexNumberVariable) variable).getHexNumber().getText();
+    } else {
+      // This is a compound variable. Evaluate the slow way.
+      return null;
+    }
+  }
+
+  /**
+   * Returns a Data variable name extracted during evaluation.
+   * 
+   * @param variable the parsed variable name node to traverse
+   */
+  public String getVariableName(PVariable variable) {
+    String result = quickEval(variable);
+    if (result != null) {
+      return result;
+    }
+    StringBuilder lastName = currentName;
+    currentName = new StringBuilder(10);
+    variable.apply(this);
+    result = currentName.toString();
+    currentName = lastName;
+    return result;
+  }
+
+  @Override
+  public void caseANameVariable(ANameVariable node) {
+    descendVariable(node.getWord().getText());
+  }
+
+  @Override
+  public void caseADecNumberVariable(ADecNumberVariable node) {
+    descendVariable(node.getDecNumber().getText());
+  }
+
+  @Override
+  public void caseAHexNumberVariable(AHexNumberVariable node) {
+    descendVariable(node.getHexNumber().getText());
+  }
+
+  @Override
+  public void caseADescendVariable(ADescendVariable node) {
+    node.getParent().apply(this);
+    node.getChild().apply(this);
+  }
+
+  @Override
+  public void caseAExpandVariable(AExpandVariable node) {
+    node.getParent().apply(this);
+    Value value = expressionEvaluator.evaluate(node.getChild());
+    descendVariable(value.asString());
+  }
+
+  private void descendVariable(String name) {
+    if (currentName.length() != 0) {
+      currentName.append('.');
+    }
+    currentName.append(name);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/output/InstanceOutputBufferProvider.java b/src/com/google/clearsilver/jsilver/output/InstanceOutputBufferProvider.java
new file mode 100644
index 0000000..e2f2706
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/output/InstanceOutputBufferProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.output;
+
+/**
+ * Implementation of OutputBufferProvider that creates a new StringBuilder
+ */
+public class InstanceOutputBufferProvider implements OutputBufferProvider {
+
+  private final int bufferSize;
+
+  public InstanceOutputBufferProvider(int bufferSize) {
+    this.bufferSize = bufferSize;
+  }
+
+  @Override
+  public Appendable get() {
+    return new StringBuilder(bufferSize);
+  }
+
+  @Override
+  public void release(Appendable buffer) {
+  // Nothing to do.
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/output/OutputBufferProvider.java b/src/com/google/clearsilver/jsilver/output/OutputBufferProvider.java
new file mode 100644
index 0000000..8f84a19
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/output/OutputBufferProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.output;
+
+/**
+ * Simple Provider interface for the output buffer.
+ */
+public interface OutputBufferProvider {
+
+  /**
+   * Returns a clean Appendable buffer ready to use while rendering.
+   */
+  Appendable get();
+
+  /**
+   * Tells the provider that this buffer is free to be reused.
+   * 
+   * @param buffer the Appendable object handed out by {@link #get}
+   */
+  void release(Appendable buffer);
+}
diff --git a/src/com/google/clearsilver/jsilver/output/ThreadLocalOutputBufferProvider.java b/src/com/google/clearsilver/jsilver/output/ThreadLocalOutputBufferProvider.java
new file mode 100644
index 0000000..3b50910
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/output/ThreadLocalOutputBufferProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.output;
+
+/**
+ * Implementation of OutputBufferProvider that reuses the same StringBuilder in each Thread.
+ */
+public class ThreadLocalOutputBufferProvider implements OutputBufferProvider {
+
+  private final ThreadLocal<StringBuilder> pool;
+  private final ThreadLocal<Boolean> available;
+
+  public ThreadLocalOutputBufferProvider(final int bufferSize) {
+    pool = new ThreadLocal<StringBuilder>() {
+      protected StringBuilder initialValue() {
+        return new StringBuilder(bufferSize);
+      }
+    };
+    available = new ThreadLocal<Boolean>() {
+      protected Boolean initialValue() {
+        return true;
+      }
+    };
+  }
+
+  @Override
+  public Appendable get() {
+    if (!available.get()) {
+      throw new IllegalStateException("Thread buffer is not free.");
+    }
+    StringBuilder sb = pool.get();
+    available.set(false);
+    sb.setLength(0);
+    return sb;
+  }
+
+  @Override
+  public void release(Appendable buffer) {
+    if (buffer != pool.get()) {
+      throw new IllegalArgumentException("Can't release buffer that does not "
+          + "correspond to this thread.");
+    }
+    available.set(true);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateLoader.java b/src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateLoader.java
new file mode 100644
index 0000000..c750ed7
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateLoader.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.precompiler;
+
+import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.compiler.BaseCompiledTemplate;
+import com.google.clearsilver.jsilver.functions.FunctionExecutor;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader;
+import com.google.clearsilver.jsilver.template.Template;
+import com.google.clearsilver.jsilver.template.TemplateLoader;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TemplateLoader that stores objects from precompiled Template classes and serves them when asked
+ * for them. If not found, it passes the request on to the delegate TemplateLoader.
+ */
+public class PrecompiledTemplateLoader implements DelegatingTemplateLoader {
+
+  /**
+   * This is the next TemplateLoader to ask if we don't find the template.
+   */
+  private final TemplateLoader nextLoader;
+
+  private final Map<Object, BaseCompiledTemplate> templateMap;
+  private final AutoEscapeOptions autoEscapeOptions;
+
+  public PrecompiledTemplateLoader(TemplateLoader nextLoader,
+      Map<Object, String> templateToClassNameMap, FunctionExecutor globalFunctionExecutor,
+      AutoEscapeOptions autoEscapeOptions) {
+    this.nextLoader = nextLoader;
+    this.autoEscapeOptions = autoEscapeOptions;
+    this.templateMap = makeTemplateMap(templateToClassNameMap, globalFunctionExecutor);
+  }
+
+  private Map<Object, BaseCompiledTemplate> makeTemplateMap(
+      Map<Object, String> templateToClassNameMap, FunctionExecutor globalFunctionExecutor) {
+    Map<Object, BaseCompiledTemplate> templateMap = new HashMap<Object, BaseCompiledTemplate>();
+    ClassLoader classLoader = getClass().getClassLoader();
+    for (Map.Entry<Object, String> entry : templateToClassNameMap.entrySet()) {
+      String className = entry.getValue();
+      BaseCompiledTemplate compiledTemplate = loadTemplateObject(className, classLoader);
+      // Fill in the necessary
+      compiledTemplate.setFunctionExecutor(globalFunctionExecutor);
+      compiledTemplate.setTemplateName(entry.getKey().toString());
+      compiledTemplate.setTemplateLoader(this);
+      if (entry.getKey() instanceof PrecompiledTemplateMapKey) {
+        PrecompiledTemplateMapKey mapKey = (PrecompiledTemplateMapKey) entry.getKey();
+        // The template's escapeMode is not currently used as the autoescaping is all
+        // handled at compile time. Still set it in case it is needed later on.
+        compiledTemplate.setEscapeMode(mapKey.getEscapeMode());
+      } else {
+        compiledTemplate.setEscapeMode(EscapeMode.ESCAPE_NONE);
+      }
+      compiledTemplate.setAutoEscapeOptions(autoEscapeOptions);
+      templateMap.put(entry.getKey(), compiledTemplate);
+    }
+    return ImmutableMap.copyOf(templateMap);
+  }
+
+  @VisibleForTesting
+  protected BaseCompiledTemplate loadTemplateObject(String className, ClassLoader classLoader) {
+    try {
+      Class<?> templateClass = classLoader.loadClass(className);
+      // TODO: Not safe to use in untrusted environments
+      // Does not handle ClassCastException or
+      // verify class type before calling newInstance.
+      return (BaseCompiledTemplate) templateClass.newInstance();
+    } catch (ClassNotFoundException e) {
+      throw new IllegalArgumentException("Class not found: " + className, e);
+    } catch (IllegalAccessException e) {
+      throw new Error(e);
+    } catch (InstantiationException e) {
+      throw new Error(e);
+    }
+  }
+
+  @Override
+  public void setTemplateLoaderDelegate(TemplateLoader templateLoaderDelegate) {
+    for (BaseCompiledTemplate template : templateMap.values()) {
+      template.setTemplateLoader(templateLoaderDelegate);
+    }
+  }
+
+  @Override
+  public Template load(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode) {
+    Object key = resourceLoader.getKey(templateName);
+    PrecompiledTemplateMapKey mapKey = new PrecompiledTemplateMapKey(key, escapeMode);
+    Template template = templateMap.get(mapKey);
+    if (template != null) {
+      return template;
+    } else {
+      return nextLoader.load(templateName, resourceLoader, escapeMode);
+    }
+  }
+
+  /**
+   * We don't cache temporary templates here so we just call delegate TemplateLoader.
+   */
+  @Override
+  public Template createTemp(String name, String content, EscapeMode escapeMode) {
+    return nextLoader.createTemp(name, content, escapeMode);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateMapFileReader.java b/src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateMapFileReader.java
new file mode 100644
index 0000000..8918fde
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateMapFileReader.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.precompiler;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * Utility class that reads in the file output by BatchCompiler that is a list of template names and
+ * corresponding class names and returns a Map of template filenames to class names which can be fed
+ * to {@see com.google.clearsilver.jsilver.JSilverOptions#setPrecompiledTemplateMap}
+ */
+public class PrecompiledTemplateMapFileReader {
+
+  private final String mapFileName;
+  private final String dirPattern;
+  private final String rootDir;
+
+  private Map<Object, String> templateMap = null;
+
+  /**
+   * Helper object that reads in the specified resource file and generates a mapping of template
+   * filenames to corresponding BaseCompiledTemplate class names.
+   * 
+   * @param filename name of the resource file to read the map from.
+   * @param dirPattern prefix to remove from read in template names. Used in conjunction with
+   *        rootDir to update template file paths.
+   * @param rootDir optional string to prepend to all non-absolute template filenames. Should be set
+   *        to the location of the templates in production via a flag.
+   */
+  public PrecompiledTemplateMapFileReader(String filename, String dirPattern, String rootDir) {
+    this.mapFileName = filename;
+    this.dirPattern = dirPattern;
+    this.rootDir = rootDir;
+  }
+
+  public Map<Object, String> getTemplateMap() throws IOException {
+    if (templateMap == null) {
+      templateMap = makeTemplateMap(mapFileName, rootDir);
+    }
+    return templateMap;
+  }
+
+  private Map<Object, String> makeTemplateMap(String templateMapFile, String rootDir)
+      throws IOException {
+    Map<Object, String> templateMap = new HashMap<Object, String>();
+    LineNumberReader reader = null;
+    try {
+      reader = new LineNumberReader(getMapFileReader(templateMapFile));
+      for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+        // Process single line from the templateMapFile
+        // and put found templates into templateMap.
+        processTemplateMapFileLine(line, reader.getLineNumber(), templateMap, templateMapFile,
+            rootDir);
+      }
+    } finally {
+      if (reader != null) {
+        reader.close();
+      }
+    }
+    return ImmutableMap.copyOf(templateMap);
+  }
+
+  private void processTemplateMapFileLine(String line, int lineNumber,
+      Map<Object, String> templateMap, String templateMapFile, String rootDir) {
+
+    line = line.trim();
+    if (line.isEmpty() || line.startsWith("#")) {
+      // Ignore blank lines and comment lines.
+      return;
+    }
+    StringTokenizer st = new StringTokenizer(line);
+    if (!st.hasMoreTokens()) {
+      throw new IllegalArgumentException("No template file name found in " + templateMapFile
+          + " on line " + lineNumber + ": " + line);
+    }
+    String templateName = st.nextToken();
+    if (dirPattern != null && templateName.startsWith(dirPattern)) {
+      templateName = templateName.substring(dirPattern.length());
+    }
+    if (rootDir != null) {
+      // If it is not an absolute path and we were given a root directory,
+      // prepend it.
+      templateName = rootDir + templateName;
+    }
+    if (!st.hasMoreTokens()) {
+      throw new IllegalArgumentException("No class name found in " + templateMapFile + " on line "
+          + lineNumber + ": " + line);
+    }
+    String className = st.nextToken();
+    EscapeMode escapeMode;
+    if (!st.hasMoreTokens()) {
+      escapeMode = EscapeMode.ESCAPE_NONE;
+    } else {
+      String escapeCmd = st.nextToken();
+      try {
+        escapeMode = EscapeMode.computeEscapeMode(escapeCmd);
+      } catch (JSilverAutoEscapingException e) {
+        throw new IllegalArgumentException("Invalid escape mode found in " + templateMapFile
+            + " on line " + lineNumber + ": " + escapeCmd);
+      }
+    }
+    PrecompiledTemplateMapKey key = new PrecompiledTemplateMapKey(templateName, escapeMode);
+    templateMap.put(key, className);
+  }
+
+  @VisibleForTesting
+  protected Reader getMapFileReader(String templateMapFile) throws IOException {
+    ClassLoader classLoader = getClass().getClassLoader();
+    InputStream in = classLoader.getResourceAsStream(templateMapFile);
+    if (in == null) {
+      throw new FileNotFoundException("Unable to locate resource: " + templateMapFile);
+    }
+    return new InputStreamReader(in, "UTF-8");
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateMapKey.java b/src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateMapKey.java
new file mode 100644
index 0000000..f019d52
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/precompiler/PrecompiledTemplateMapKey.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.precompiler;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+
+/**
+ * Object to use as key when looking up precompiled templates. It encapsulates the template name, as
+ * well as the {@link EscapeMode} for which the template was compiled.
+ */
+public class PrecompiledTemplateMapKey {
+  private final Object templateName;
+  private final EscapeMode escapeMode;
+  private final String toStringName;
+
+  public PrecompiledTemplateMapKey(Object templateName, EscapeMode escapeMode) {
+    this.templateName = templateName;
+    this.escapeMode = escapeMode;
+
+    if (escapeMode == EscapeMode.ESCAPE_NONE) {
+      toStringName = templateName.toString();
+    } else {
+      toStringName = templateName.toString() + "_" + escapeMode.getEscapeCommand();
+    }
+  }
+
+  public boolean equals(Object o) {
+    if (o == this) {
+      return true;
+    }
+
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    PrecompiledTemplateMapKey that = (PrecompiledTemplateMapKey) o;
+
+    return templateName.equals(that.templateName) && (escapeMode == that.escapeMode);
+  }
+
+  public int hashCode() {
+    int hash = 17;
+
+    hash = 31 * hash + templateName.hashCode();
+    hash = 31 * hash + escapeMode.hashCode();
+    return hash;
+  }
+
+  /**
+   * String representation of key. If the template was auto escaped, it appends the
+   * {@link EscapeMode} to the template name.
+   * 
+   */
+  public String toString() {
+    return toStringName;
+  }
+
+  /**
+   * Return the escape mode used for this template.
+   */
+  public EscapeMode getEscapeMode() {
+    return escapeMode;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/precompiler/compile_cs b/src/com/google/clearsilver/jsilver/precompiler/compile_cs
new file mode 100644
index 0000000..e0bb18b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/precompiler/compile_cs
@@ -0,0 +1,82 @@
+# -*- mode: python; -*-
+# Copyright 2008 Google Inc. All Rights Reserved.
+
+# Use with
+# subinclude('//java/com/google/clearsilver/jsilver/precompiler:compile_cs')
+
+"""compile_cs build target
+
+compile_cs(name, srcs)
+
+This rule produces generated Java source code that represents JSilver Template
+classes for rendering the given source CS files
+It'll output one .java file for each .cs file.
+
+Arguments
+
+    * name: A unique name for this rule. (Name; required)
+    * srcs: The list of cs files to pass to the code generator. (List of files,
+            required)
+"""
+
+def compile_cs(name, srcs, mode='none'):
+  if not srcs:
+    raise BadRule(None, '%s: srcs is empty' % name)
+  if mode == 'none':
+    suffix = '.java'
+  else:
+    suffix = '_' + mode + '.java'
+  gen_java_files = [ file.replace('.cs', suffix) for file in srcs]
+  input_file = 'gen_cs_' + name + '.in'
+  output_file = 'gen_cs_' + name + '.out'
+  map_file = name + '.map' 
+  genrule(name = 'gen_cs_' + name,
+          srcs = srcs,
+          outs = [ map_file ] + gen_java_files,
+          deps = [
+                   '//java/com/google/clearsilver/jsilver/precompiler:BatchCompiler',
+                 ],
+          cmd = (
+              'echo "$(SRCS)" > $(@D)/' + input_file + ' && '
+              'echo "$(OUTS)" > $(@D)/' + output_file + ' && '
+              '//java/com/google/clearsilver/jsilver/precompiler:BatchCompiler '
+              '--src_list_file=$(@D)/' + input_file + ' '
+              '--out_list_file=$(@D)/' + output_file + ' '
+              '--escape_mode=' + mode
+            )
+        )
+  java_library(name = name,
+               srcs = gen_java_files,
+               resources = [ map_file ],
+               deps = [ '//java/com/google/clearsilver/jsilver/compiler' ]
+              )
+
+
+"""join_compiled_cs build target
+
+join_compiled_cs(name, deps)
+
+This rule merges multiple compile_cs output libraries and maps into one Java
+library and one map file that will be included as a system resource and can be
+read into the binary that wants to load the compiled template classes.
+
+Arguments
+
+    * name: A unique name for this rule. (Name; required)
+    * deps: The list of compile_cs BUILD targets to merge (List of labels,
+            required)
+"""
+
+def join_compiled_cs(name, deps):
+  if not deps:
+    raise BadRule(None, '%s: deps is empty' % name)
+  map_files = [ file + '.map' for file in deps]
+  joined_map_file = name + '.map'
+  genrule(name = 'gen_' + joined_map_file,
+          srcs = map_files,
+          outs = [ joined_map_file ],
+          cmd = ('cat $(SRCS) > $@')
+         )
+  java_library(name = name,
+               resources = [ joined_map_file ],
+               deps = deps)
diff --git a/src/com/google/clearsilver/jsilver/resourceloader/BaseResourceLoader.java b/src/com/google/clearsilver/jsilver/resourceloader/BaseResourceLoader.java
new file mode 100644
index 0000000..abaa492
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/resourceloader/BaseResourceLoader.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.resourceloader;
+
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Implementations of ResourceLoader should extend this class rather than directly implement the
+ * ResourceLoader interface - this allows changes to be made to the ResourceLoader interface whilst
+ * retaining backwards compatibility with existing implementations.
+ * 
+ * @see ResourceLoader
+ */
+public abstract class BaseResourceLoader implements ResourceLoader {
+
+  @Override
+  public void close(Reader reader) throws IOException {
+    reader.close();
+  }
+
+  /**
+   * Default implementation returns the filename as the ResourceLoaders that subclass this class
+   * tend to assume they are the only ResourceLoader in use. Or at least that the filename is the
+   * only necessary form of uniqueness between two instances of this same ResourceLoader.
+   */
+  @Override
+  public Object getKey(String filename) {
+    return filename;
+  }
+
+  /**
+   * Default implementation does not check whether the resource has changed.
+   */
+  @Override
+  public Object getResourceVersionId(String filename) {
+    return filename;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/resourceloader/BufferedResourceLoader.java b/src/com/google/clearsilver/jsilver/resourceloader/BufferedResourceLoader.java
new file mode 100644
index 0000000..62d3c8d
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/resourceloader/BufferedResourceLoader.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.resourceloader;
+
+import java.io.Reader;
+import java.io.BufferedReader;
+
+/**
+ * Base class for ResourceLoader implementations that require the Reader to be buffered (i.e.
+ * there's IO latency involved).
+ * 
+ * @see ResourceLoader
+ */
+public abstract class BufferedResourceLoader extends BaseResourceLoader {
+
+  public static final int DEFAULT_BUFFER_SIZE = 1024;
+  public static final String DEFAULT_CHARACTER_SET = "UTF-8";
+
+  private int bufferSize = DEFAULT_BUFFER_SIZE;
+  private String characterSet = DEFAULT_CHARACTER_SET;
+
+  /**
+   * Subclasses can wrap a Reader in a BufferedReader by calling this method.
+   */
+  protected Reader buffer(Reader reader) {
+    return reader == null ? null : new BufferedReader(reader, bufferSize);
+  }
+
+  public int getBufferSize() {
+    return bufferSize;
+  }
+
+  public void setBufferSize(int bufferSize) {
+    this.bufferSize = bufferSize;
+  }
+
+  public void setCharacterSet(String characterSet) {
+    this.characterSet = characterSet;
+  }
+
+  public String getCharacterSet() {
+    return characterSet;
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/resourceloader/ClassLoaderResourceLoader.java b/src/com/google/clearsilver/jsilver/resourceloader/ClassLoaderResourceLoader.java
new file mode 100644
index 0000000..2c20484
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/resourceloader/ClassLoaderResourceLoader.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.resourceloader;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+/**
+ * Loads resources from classpath.
+ * 
+ * <p>
+ * For example, suppose the classpath contains:
+ * 
+ * <pre>
+ * com/foo/my-template.cs
+ * com/foo/subdir/another-template.cs
+ * </pre>
+ * 
+ * <p>
+ * You can access the resources like this:
+ * 
+ * <pre>
+ * ResourceLoader loader =
+ *     new ClassPathResourceLoader(getClassLoader(), "com/foo");
+ * loader.open("my-template.cs");
+ * loader.open("subdir/my-template.cs");
+ * </pre>
+ * 
+ * @see ResourceLoader
+ * @see ClassResourceLoader
+ */
+public class ClassLoaderResourceLoader extends BufferedResourceLoader {
+
+  private final ClassLoader classLoader;
+  private String basePath;
+
+  public ClassLoaderResourceLoader(ClassLoader classLoader, String basePath) {
+    this.classLoader = classLoader;
+    this.basePath = basePath;
+  }
+
+  public ClassLoaderResourceLoader(ClassLoader classLoader) {
+    this(classLoader, ".");
+  }
+
+  @Override
+  public Reader open(String name) throws IOException {
+    String path = basePath + '/' + name;
+    InputStream stream = classLoader.getResourceAsStream(path);
+    return stream == null ? null : buffer(new InputStreamReader(stream, getCharacterSet()));
+  }
+
+  @Override
+  public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException {
+    Reader reader = open(name);
+    if (reader == null) {
+      throw new JSilverTemplateNotFoundException("No class loader resource '" + name + "' in '"
+          + basePath + "'");
+    } else {
+      return reader;
+    }
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/resourceloader/ClassResourceLoader.java b/src/com/google/clearsilver/jsilver/resourceloader/ClassResourceLoader.java
new file mode 100644
index 0000000..e89f87a
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/resourceloader/ClassResourceLoader.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.resourceloader;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+/**
+ * Loads resources from classpath, alongside a given class.
+ * 
+ * <p>For example, suppose the classpath contains:
+ * <pre>
+ * com/foo/SomeThing.class
+ * com/foo/my-template.cs
+ * com/foo/subdir/another-template.cs
+ * </pre>
+ * 
+ * <p>You can access the resources in the class's package like this:
+ * <pre>
+ * ResourceLoader loader = new ClassResourceLoader(SomeThing.class);
+ * loader.open("my-template.cs");
+ * loader.open("subdir/my-template.cs");
+ * </pre>
+ * Or by using a relative path:
+ * <pre>
+ * ResourceLoader loader = new ClassResourceLoader(Something.class, "subdir");
+ * loader.open("my-template.cs");
+ * </pre>
+ * 
+ * @see ResourceLoader
+ * @see ClassLoaderResourceLoader
+ */
+public class ClassResourceLoader extends BufferedResourceLoader {
+
+  private final Class<?> cls;
+  private final String basePath;
+
+  public ClassResourceLoader(Class<?> cls) {
+    this.cls = cls;
+    this.basePath = ".";
+  }
+  
+  /**
+   * Load resources from the given subdirectory {@code basePath},
+   * relative to the .class file of {@code cls}.
+   */
+  public ClassResourceLoader(Class<?> cls, String basePath) {
+    this.cls = cls;
+    this.basePath = basePath;
+  }
+
+  @Override
+  public Reader open(String name) throws IOException {
+    InputStream stream = cls.getResourceAsStream(basePath + '/' + name);
+    return stream == null ? null : buffer(new InputStreamReader(stream, getCharacterSet()));
+  }
+
+  @Override
+  public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException {
+    Reader reader = open(name);
+    if (reader == null) {
+      throw new JSilverTemplateNotFoundException("No '" + name + "' as class resource of "
+          + cls.getName());
+    } else {
+      return reader;
+    }
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/resourceloader/CompositeResourceLoader.java b/src/com/google/clearsilver/jsilver/resourceloader/CompositeResourceLoader.java
new file mode 100644
index 0000000..d8cad25
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/resourceloader/CompositeResourceLoader.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.resourceloader;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException;
+
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ResourceLoader composed of other ResourceLoaders. When a resource is loaded, it will search
+ * through each ResourceLoader until it finds something.
+ *
+ * @see ResourceLoader
+ */
+public class CompositeResourceLoader implements ResourceLoader {
+
+  private final List<ResourceLoader> loaders = new ArrayList<ResourceLoader>();
+
+  public CompositeResourceLoader(Iterable<ResourceLoader> loaders) {
+    for (ResourceLoader loader : loaders) {
+      add(loader);
+    }
+  }
+
+  public CompositeResourceLoader(ResourceLoader... loaders) {
+    for (ResourceLoader loader : loaders) {
+      add(loader);
+    }
+  }
+
+  public void add(ResourceLoader loader) {
+    loaders.add(loader);
+  }
+
+  public Reader open(String name) throws IOException {
+    for (ResourceLoader loader : loaders) {
+      Reader reader = loader.open(name);
+      if (reader != null) {
+        return new ReaderTracer(reader, loader);
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException {
+    Reader reader = open(name);
+    if (reader == null) {
+      throw new JSilverTemplateNotFoundException(name);
+    } else {
+      return reader;
+    }
+  }
+
+  public void close(Reader reader) throws IOException {
+    if (!(reader instanceof ReaderTracer)) {
+      throw new IllegalArgumentException("I can't close a reader I didn't open.");
+    }
+    reader.close();
+  }
+
+  /**
+   * We return the filename as the key of uniqueness as we assume that if this
+   * CompositeResourceLoader is in use, then there won't be another ResourceLoader that we are
+   * competing against. If we did need to worry about it we would want to prepend the key from
+   * above.
+   */
+  @Override
+  public Object getKey(String filename) {
+    return filename;
+  }
+
+  /**
+   * Return the first non-null version identifier found among the ResourceLoaders, using the same
+   * search order as {@link #open(String)}.
+   */
+  @Override
+  public Object getResourceVersionId(String filename) {
+    for (ResourceLoader loader : loaders) {
+      Object currentKey = loader.getResourceVersionId(filename);
+      if (currentKey != null) {
+        return currentKey;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Wraps a reader, associating it with the original ResourceLoader - this is necessary so when
+   * close() is called, we delegate back to original ResourceLoader.
+   */
+  private static class ReaderTracer extends FilterReader {
+
+    private final ResourceLoader originalLoader;
+
+    public ReaderTracer(Reader in, ResourceLoader originalLoader) {
+      super(in);
+      this.originalLoader = originalLoader;
+    }
+
+    public void close() throws IOException {
+      originalLoader.close(in);
+    }
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/resourceloader/FileSystemResourceLoader.java b/src/com/google/clearsilver/jsilver/resourceloader/FileSystemResourceLoader.java
new file mode 100644
index 0000000..5ff0514
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/resourceloader/FileSystemResourceLoader.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.resourceloader;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Loads resources from a directory.
+ * 
+ * @see ResourceLoader
+ */
+public class FileSystemResourceLoader extends BufferedResourceLoader {
+
+  private final File rootDir;
+
+  public FileSystemResourceLoader(File rootDir) {
+    this.rootDir = rootDir;
+  }
+
+  public FileSystemResourceLoader(String rootDir) {
+    this(new File(rootDir));
+  }
+
+  @Override
+  public Reader open(String name) throws IOException {
+    File file = new File(rootDir, name);
+    // Check for non-directory rather than is-file so that reads from
+    // e.g. pipes work.
+    if (file.exists() && !file.isDirectory() && file.canRead()) {
+      return buffer(new InputStreamReader(new FileInputStream(file), getCharacterSet()));
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException {
+    Reader reader = open(name);
+    if (reader == null) {
+      throw new JSilverTemplateNotFoundException("No file '" + name + "' inside directory '"
+          + rootDir + "'");
+    } else {
+      return reader;
+    }
+  }
+
+  /**
+   * Some applications, e.g. online help, need to know when a file has changed due to a symlink
+   * modification hence the use of {@link File#getCanonicalFile()}, if possible.
+   */
+  @Override
+  public Object getResourceVersionId(String filename) {
+    File file = new File(rootDir, filename);
+    // Check for non-directory rather than is-file so that reads from
+    // e.g. pipes work.
+    if (file.exists() && !file.isDirectory() && file.canRead()) {
+      String fullPath;
+      try {
+        fullPath = file.getCanonicalPath();
+      } catch (IOException e) {
+        fullPath = file.getAbsolutePath();
+      }
+      return String.format("%s@%s", fullPath, file.lastModified());
+    } else {
+      return null;
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/resourceloader/InMemoryResourceLoader.java b/src/com/google/clearsilver/jsilver/resourceloader/InMemoryResourceLoader.java
new file mode 100644
index 0000000..0b15027
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/resourceloader/InMemoryResourceLoader.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.resourceloader;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * ResourceLoader that pulls all items from memory. This is particularly useful for small templates
+ * that can be embedded in code (e.g. in unit tests).
+ * 
+ * Content needs to be stored first using the {@link #store(String, String)} method.
+ * 
+ * @see ResourceLoader
+ */
+public class InMemoryResourceLoader extends BaseResourceLoader {
+
+  private ConcurrentMap<String, String> items = new ConcurrentHashMap<String, String>();
+
+  @Override
+  public Reader open(String name) throws IOException {
+    String content = items.get(name);
+    return content == null ? null : new StringReader(content);
+  }
+
+  @Override
+  public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException {
+    Reader reader = open(name);
+    if (reader == null) {
+      throw new JSilverTemplateNotFoundException(name);
+    } else {
+      return reader;
+    }
+  }
+
+  public void store(String name, String contents) {
+    items.put(name, contents);
+  }
+
+  public void remove(String name) {
+    items.remove(name);
+  }
+
+  public ConcurrentMap<String, String> getItems() {
+    return items;
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/resourceloader/ResourceLoader.java b/src/com/google/clearsilver/jsilver/resourceloader/ResourceLoader.java
new file mode 100644
index 0000000..a4d7cbf
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/resourceloader/ResourceLoader.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.resourceloader;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException;
+
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Loads resources, from somewhere.
+ * 
+ * This is a service provider interface (SPI) for JSilver. Users of JSilver can easily create their
+ * own implementations. However, it is recommended that new implementations don't implement this
+ * interface directly, but instead extends {@link BaseResourceLoader}. This allows API changes to be
+ * made to JSilver that maintain compatibility with existing ResourceLoader implementations.
+ * 
+ * @see BaseResourceLoader
+ * @see InMemoryResourceLoader
+ * @see FileSystemResourceLoader
+ * @see ClassLoaderResourceLoader
+ * @see ClassResourceLoader
+ */
+public interface ResourceLoader {
+
+  /**
+   * Open a resource. If this resource is not found, null should be returned.
+   * 
+   * The caller of this method is guaranteed to call {@link #close(Reader)} when done with the
+   * reader.
+   * 
+   * @param name the name of the resource
+   * @return Reader, or null if not found.
+   * @throws IOException if resource fails to open
+   */
+  Reader open(String name) throws IOException;
+
+  /**
+   * Open a resource or throw an exception if no such resource is found.
+   * 
+   * The caller of this method is guaranteed to call {@link #close(Reader)} when done with the
+   * reader.
+   * 
+   * @param name the name of the resource
+   * @return Reader, or null if not found.
+   * @throws JSilverTemplateNotFoundException if resource is not found
+   * @throws IOException if resource fails to open
+   */
+  Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException;
+
+  /**
+   * Close the reader. Allows ResourceLoader to perform any additional clean up.
+   * 
+   * @param reader the reader to close
+   * @throws IOException if reader fasils to close
+   */
+  void close(Reader reader) throws IOException;
+
+  /**
+   * Returns an object that can be used to uniquely identify the file corresponding to the given
+   * file name in the context of this ResourceLoader. (e.g. ordered list of directories + filename,
+   * or absolute file path.).
+   * 
+   * @param filename the name we want to identify
+   * @return unique identifier
+   */
+  Object getKey(String filename);
+
+  /**
+   * Returns an object that can be used to identify when a resource has changed. This key should be
+   * based on some piece(s) of metadata that strongly indicates the resource has changed, for
+   * example a file's last modified time. Since the object is expected to be used as part of a cache
+   * key, it should be immutable and implement {@link Object#equals(Object)} and
+   * {@link Object#hashCode()} .
+   * 
+   * If the ResourceLoader does not or cannot compute a version identifier then it is sufficient to
+   * always return the same Object, e.g. the resource name. Null, however, should only be returned
+   * if a call to {@link #open(String)} would also return null.
+   * 
+   * @param name the name of the resource to check for resources
+   * @return unique identifier for the current version of the resource or null if the resource
+   *         cannot be found
+   */
+  Object getResourceVersionId(String name);
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/AutoEscaper.java b/src/com/google/clearsilver/jsilver/syntax/AutoEscaper.java
new file mode 100644
index 0000000..f95fdcd
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/AutoEscaper.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.syntax;
+
+import com.google.clearsilver.jsilver.autoescape.AutoEscapeContext;
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException;
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.AAltCommand;
+import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ACallCommand;
+import com.google.clearsilver.jsilver.syntax.node.AContentTypeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ACsOpenPosition;
+import com.google.clearsilver.jsilver.syntax.node.ADataCommand;
+import com.google.clearsilver.jsilver.syntax.node.ADefCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AIfCommand;
+import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.ANameCommand;
+import com.google.clearsilver.jsilver.syntax.node.AStringExpression;
+import com.google.clearsilver.jsilver.syntax.node.AUvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AVarCommand;
+import com.google.clearsilver.jsilver.syntax.node.Node;
+import com.google.clearsilver.jsilver.syntax.node.PCommand;
+import com.google.clearsilver.jsilver.syntax.node.PPosition;
+import com.google.clearsilver.jsilver.syntax.node.Start;
+import com.google.clearsilver.jsilver.syntax.node.TCsOpen;
+import com.google.clearsilver.jsilver.syntax.node.TString;
+import com.google.clearsilver.jsilver.syntax.node.Token;
+
+/**
+ * Run a context parser (currently only HTML parser) over the AST, determine nodes that need
+ * escaping, and apply the appropriate escaping command to those nodes. The parser is fed literal
+ * data (from DataCommands), which it uses to track the context. When variables (e.g. VarCommand)
+ * are encountered, we query the parser for its current context, and apply the appropriate escaping
+ * command.
+ */
+public class AutoEscaper extends DepthFirstAdapter {
+
+  private AutoEscapeContext autoEscapeContext;
+  private boolean skipAutoEscape;
+  private final EscapeMode escapeMode;
+  private final String templateName;
+  private boolean contentTypeCalled;
+
+  /**
+   * Create an AutoEscaper, which will apply the specified escaping mode. If templateName is
+   * non-null, it will be used while displaying error messages.
+   * 
+   * @param mode
+   * @param templateName
+   */
+  public AutoEscaper(EscapeMode mode, String templateName) {
+    this.templateName = templateName;
+    if (mode.equals(EscapeMode.ESCAPE_NONE)) {
+      throw new JSilverAutoEscapingException("AutoEscaper called when no escaping is required",
+          templateName);
+    }
+    escapeMode = mode;
+    if (mode.isAutoEscapingMode()) {
+      autoEscapeContext = new AutoEscapeContext(mode, templateName);
+      skipAutoEscape = false;
+    } else {
+      autoEscapeContext = null;
+    }
+  }
+
+  /**
+   * Create an AutoEscaper, which will apply the specified escaping mode. When possible, use
+   * #AutoEscaper(EscapeMode, String) instead. It specifies the template being auto escaped, which
+   * is useful when displaying error messages.
+   * 
+   * @param mode
+   */
+  public AutoEscaper(EscapeMode mode) {
+    this(mode, null);
+  }
+
+  @Override
+  public void caseStart(Start start) {
+    if (!escapeMode.isAutoEscapingMode()) {
+      // For an explicit EscapeMode like {@code EscapeMode.ESCAPE_HTML}, we
+      // do not need to parse the rest of the tree. Instead, we just wrap the
+      // entire tree in a <?cs escape ?> node.
+      handleExplicitEscapeMode(start);
+    } else {
+      AutoEscapeContext.AutoEscapeState startState = autoEscapeContext.getCurrentState();
+      // call super.caseStart, which will make us visit the rest of the tree,
+      // so we can determine the appropriate escaping to apply for each
+      // variable.
+      super.caseStart(start);
+      AutoEscapeContext.AutoEscapeState endState = autoEscapeContext.getCurrentState();
+      if (!autoEscapeContext.isPermittedStateChangeForIncludes(startState, endState)) {
+        // If template contains a content-type command, the escaping context
+        // was intentionally changed. Such a change in context is fine as long
+        // as the current template is not included inside another. There is no
+        // way to verify that the template is not an include template however,
+        // so ignore the error and depend on developers doing the right thing.
+        if (contentTypeCalled) {
+          return;
+        }
+        // We do not permit templates to end in a different context than they start in.
+        // This is so that an included template does not modify the context of
+        // the template that includes it.
+        throw new JSilverAutoEscapingException("Template starts in context " + startState
+            + " but ends in different context " + endState, templateName);
+      }
+    }
+  }
+
+  private void handleExplicitEscapeMode(Start start) {
+    AStringExpression escapeExpr =
+        new AStringExpression(new TString("\"" + escapeMode.getEscapeCommand() + "\""));
+
+    PCommand node = start.getPCommand();
+    AEscapeCommand escape =
+        new AEscapeCommand(new ACsOpenPosition(new TCsOpen("<?cs ", 0, 0)), escapeExpr,
+            (PCommand) node.clone());
+
+    node.replaceBy(escape);
+  }
+
+  @Override
+  public void caseADataCommand(ADataCommand node) {
+    String data = node.getData().getText();
+    autoEscapeContext.setCurrentPosition(node.getData().getLine(), node.getData().getPos());
+    autoEscapeContext.parseData(data);
+  }
+
+  @Override
+  public void caseADefCommand(ADefCommand node) {
+  // Ignore the entire defcommand subtree, don't even parse it.
+  }
+
+  @Override
+  public void caseAIfCommand(AIfCommand node) {
+    setCurrentPosition(node.getPosition());
+
+    /*
+     * Since AutoEscaper is being applied while building the AST, and not during rendering, the html
+     * context of variables is sometimes ambiguous. For instance: <?cs if: X ?><script><?cs /if ?>
+     * <?cs var: MyVar ?>
+     * 
+     * Here MyVar may require js escaping or html escaping depending on whether the "if" condition
+     * is true or false.
+     * 
+     * To avoid such ambiguity, we require all branches of a conditional statement to end in the
+     * same context. So, <?cs if: X ?><script>X <?cs else ?><script>Y<?cs /if ?> is fine but,
+     * 
+     * <?cs if: X ?><script>X <?cs elif: Y ?><script>Y<?cs /if ?> is not.
+     */
+    AutoEscapeContext originalEscapedContext = autoEscapeContext.cloneCurrentEscapeContext();
+    // Save position of the start of if statement.
+    int line = autoEscapeContext.getLineNumber();
+    int column = autoEscapeContext.getColumnNumber();
+
+    if (node.getBlock() != null) {
+      node.getBlock().apply(this);
+    }
+    AutoEscapeContext.AutoEscapeState ifEndState = autoEscapeContext.getCurrentState();
+    // restore original context before executing else block
+    autoEscapeContext = originalEscapedContext;
+
+    // Interestingly, getOtherwise() is not null even when the if command
+    // has no else branch. In such cases, getOtherwise() contains a
+    // Noop command.
+    // In practice this does not matter for the checks being run here.
+    if (node.getOtherwise() != null) {
+      node.getOtherwise().apply(this);
+    }
+    AutoEscapeContext.AutoEscapeState elseEndState = autoEscapeContext.getCurrentState();
+
+    if (!ifEndState.equals(elseEndState)) {
+      throw new JSilverAutoEscapingException("'if/else' branches have different ending contexts "
+          + ifEndState + " and " + elseEndState, templateName, line, column);
+    }
+  }
+
+  @Override
+  public void caseAEscapeCommand(AEscapeCommand node) {
+    boolean saved_skip = skipAutoEscape;
+    skipAutoEscape = true;
+    node.getCommand().apply(this);
+    skipAutoEscape = saved_skip;
+  }
+
+  @Override
+  public void caseACallCommand(ACallCommand node) {
+    saveAutoEscapingContext(node, node.getPosition());
+  }
+
+  @Override
+  public void caseALvarCommand(ALvarCommand node) {
+    saveAutoEscapingContext(node, node.getPosition());
+  }
+
+  @Override
+  public void caseAEvarCommand(AEvarCommand node) {
+    saveAutoEscapingContext(node, node.getPosition());
+  }
+
+  @Override
+  public void caseALincludeCommand(ALincludeCommand node) {
+    saveAutoEscapingContext(node, node.getPosition());
+  }
+
+  @Override
+  public void caseAIncludeCommand(AIncludeCommand node) {
+    saveAutoEscapingContext(node, node.getPosition());
+  }
+
+  @Override
+  public void caseAHardLincludeCommand(AHardLincludeCommand node) {
+    saveAutoEscapingContext(node, node.getPosition());
+  }
+
+  @Override
+  public void caseAHardIncludeCommand(AHardIncludeCommand node) {
+    saveAutoEscapingContext(node, node.getPosition());
+  }
+
+  @Override
+  public void caseAVarCommand(AVarCommand node) {
+    applyAutoEscaping(node, node.getPosition());
+  }
+
+  @Override
+  public void caseAAltCommand(AAltCommand node) {
+    applyAutoEscaping(node, node.getPosition());
+  }
+
+  @Override
+  public void caseANameCommand(ANameCommand node) {
+    applyAutoEscaping(node, node.getPosition());
+  }
+
+  @Override
+  public void caseAUvarCommand(AUvarCommand node) {
+    // Let parser know that was some text that it has not seen
+    setCurrentPosition(node.getPosition());
+    autoEscapeContext.insertText();
+  }
+
+  /**
+   * Handles a &lt;?cs content-type: "content type" ?&gt; command.
+   * 
+   * This command is used when the auto escaping context of a template cannot be determined from its
+   * contents - for example, a CSS stylesheet or a javascript source file. Note that &lt;?cs
+   * content-type: ?&gt; command is not required for all javascript and css templates. If the
+   * template contains a &lt;script&gt; or &lt;style&gt; tag (or is included from another template
+   * within the right tag), auto escaping will recognize the tag and switch context accordingly. On
+   * the other hand, if the template serves a resource that is loaded via a &lt;script src= &gt; or
+   * &lt;link rel &gt; command, the explicit &lt;?cs content-type: ?&gt; command would be required.
+   */
+  @Override
+  public void caseAContentTypeCommand(AContentTypeCommand node) {
+    setCurrentPosition(node.getPosition());
+    String contentType = node.getString().getText();
+    // Strip out quotes around the string
+    contentType = contentType.substring(1, contentType.length() - 1);
+    autoEscapeContext.setContentType(contentType);
+    contentTypeCalled = true;
+  }
+
+  private void applyAutoEscaping(PCommand node, PPosition position) {
+    setCurrentPosition(position);
+    if (skipAutoEscape) {
+      return;
+    }
+
+    AStringExpression escapeExpr = new AStringExpression(new TString("\"" + getEscaping() + "\""));
+    AEscapeCommand escape = new AEscapeCommand(position, escapeExpr, (PCommand) node.clone());
+
+    node.replaceBy(escape);
+    // Now that we have determined the correct escaping for this variable,
+    // let parser know that there was some text that it has not seen. The
+    // parser may choose to update its state based on this.
+    autoEscapeContext.insertText();
+
+  }
+
+  private void setCurrentPosition(PPosition position) {
+    // Will eventually call caseACsOpenPosition
+    position.apply(this);
+  }
+
+  @Override
+  public void caseACsOpenPosition(ACsOpenPosition node) {
+    Token token = node.getCsOpen();
+    autoEscapeContext.setCurrentPosition(token.getLine(), token.getPos());
+  }
+
+  private void saveAutoEscapingContext(Node node, PPosition position) {
+    setCurrentPosition(position);
+    if (skipAutoEscape) {
+      return;
+    }
+    EscapeMode mode = autoEscapeContext.getEscapeModeForCurrentState();
+    AStringExpression escapeStrategy =
+        new AStringExpression(new TString("\"" + mode.getEscapeCommand() + "\""));
+    AAutoescapeCommand command =
+        new AAutoescapeCommand(position, escapeStrategy, (PCommand) node.clone());
+    node.replaceBy(command);
+    autoEscapeContext.insertText();
+  }
+
+  private String getEscaping() {
+    return autoEscapeContext.getEscapingFunctionForCurrentState();
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/DataCommandConsolidator.java b/src/com/google/clearsilver/jsilver/syntax/DataCommandConsolidator.java
new file mode 100644
index 0000000..2efad26
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/DataCommandConsolidator.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.syntax;
+
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.AAltCommand;
+import com.google.clearsilver.jsilver.syntax.node.ACallCommand;
+import com.google.clearsilver.jsilver.syntax.node.ADataCommand;
+import com.google.clearsilver.jsilver.syntax.node.ADefCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEachCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AIfCommand;
+import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopIncCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopToCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.ANameCommand;
+import com.google.clearsilver.jsilver.syntax.node.AUvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AVarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AWithCommand;
+import com.google.clearsilver.jsilver.syntax.node.EOF;
+import com.google.clearsilver.jsilver.syntax.node.TData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Consolidates runs of (unescaped literal output) data commands, deferring output until another
+ * output command (var, call, etc) is encountered.
+ */
+public class DataCommandConsolidator extends DepthFirstAdapter {
+  /**
+   * The current block nesting level. This is incremented whenever a conditional command is
+   * encountered.
+   */
+  private int currentBlockNestingLevel = 0;
+  /**
+   * A list of the data commands we're currently considering for consolidation.
+   */
+  private final List<ADataCommand> datas = new ArrayList<ADataCommand>();
+  /** The block nesting level of the data commands above. */
+  private int datasBlockNestingLevel = -1;
+
+  /**
+   * Data consolidation barrier: consolidates all data contents into the last data command in the
+   * datas list, replacing all but the last node with no-ops.
+   */
+  private void barrier() {
+    if (datas.size() > 1) {
+      // Put aside the last data command for later, then remove all the other
+      // data commands, coalescing their contents into the last command.
+      ADataCommand last = datas.remove(datas.size() - 1);
+
+      StringBuilder sb = new StringBuilder();
+      for (ADataCommand data : datas) {
+        sb.append(data.getData().getText());
+        data.replaceBy(null); // removes the node
+      }
+
+      sb.append(last.getData().getText());
+      last.replaceBy(new ADataCommand(new TData(sb.toString())));
+    }
+    datas.clear();
+    datasBlockNestingLevel = -1;
+  }
+
+  /** Block entry: just increments the current block nesting level. */
+  private void blockEntry() {
+    assert datasBlockNestingLevel <= currentBlockNestingLevel;
+    ++currentBlockNestingLevel;
+  }
+
+  /**
+   * Block exit: acts as a conditional barrier only to data contained within the block.
+   */
+  private void blockExit() {
+    assert datasBlockNestingLevel <= currentBlockNestingLevel;
+    if (datasBlockNestingLevel == currentBlockNestingLevel) {
+      barrier();
+    }
+    --currentBlockNestingLevel;
+  }
+
+  // data commands: continue to accumulate as long as the block nesting level
+  // is unchanged.
+
+  @Override
+  public void caseADataCommand(ADataCommand node) {
+    assert datasBlockNestingLevel <= currentBlockNestingLevel;
+    if (currentBlockNestingLevel != datasBlockNestingLevel) {
+      barrier();
+    }
+    datas.add(node);
+    datasBlockNestingLevel = currentBlockNestingLevel;
+  }
+
+  // var, lvar, evar, uvar, name: all unconditional barriers.
+
+  @Override
+  public void inAVarCommand(AVarCommand node) {
+    barrier();
+  }
+
+  @Override
+  public void inALvarCommand(ALvarCommand node) {
+    barrier();
+  }
+
+  @Override
+  public void inAUvarCommand(AUvarCommand node) {
+    barrier();
+  }
+
+  @Override
+  public void inAEvarCommand(AEvarCommand node) {
+    barrier();
+  }
+
+  @Override
+  public void inANameCommand(ANameCommand node) {
+    barrier();
+  }
+
+  // loop, each: block barriers.
+
+  @Override
+  public void inALoopCommand(ALoopCommand node) {
+    blockEntry();
+  }
+
+  @Override
+  public void inALoopIncCommand(ALoopIncCommand node) {
+    blockEntry();
+  }
+
+  @Override
+  public void inALoopToCommand(ALoopToCommand node) {
+    blockEntry();
+  }
+
+  @Override
+  public void inAEachCommand(AEachCommand node) {
+    blockEntry();
+  }
+
+  @Override
+  public void inAWithCommand(AWithCommand node) {
+    blockEntry();
+  }
+
+  @Override
+  public void outALoopCommand(ALoopCommand node) {
+    blockExit();
+  }
+
+  @Override
+  public void outALoopIncCommand(ALoopIncCommand node) {
+    blockExit();
+  }
+
+  @Override
+  public void outALoopToCommand(ALoopToCommand node) {
+    blockExit();
+  }
+
+  @Override
+  public void outAEachCommand(AEachCommand node) {
+    blockExit();
+  }
+
+  @Override
+  public void outAWithCommand(AWithCommand node) {
+    blockExit();
+  }
+
+  // def: special case: run another instance of this optimizer on the contained
+  // commands. def produces no output, so it should not act as a barrier to
+  // any accumulated data nodes; however, it contains data nodes, so we can
+  // (and should) consolidate them.
+
+  @Override
+  public void caseADefCommand(ADefCommand node) {
+    DataCommandConsolidator consolidator = new DataCommandConsolidator();
+    node.getCommand().apply(consolidator);
+    consolidator.barrier(); // Force final consolidation, just like EOF would.
+  }
+
+  // call: unconditional barrier.
+
+  @Override
+  public void inACallCommand(ACallCommand node) {
+    barrier();
+  }
+
+  // if: special case: each branch is a block barrier.
+
+  @Override
+  public void caseAIfCommand(AIfCommand node) {
+    if (node.getBlock() != null) {
+      blockEntry();
+      node.getBlock().apply(this);
+      blockExit();
+    }
+    if (node.getOtherwise() != null) {
+      blockEntry();
+      node.getOtherwise().apply(this);
+      blockExit();
+    }
+  }
+
+  // alt: block barrier.
+
+  @Override
+  public void inAAltCommand(AAltCommand node) {
+    blockEntry();
+  }
+
+  @Override
+  public void outAAltCommand(AAltCommand node) {
+    blockExit();
+  }
+
+  // include, hard include, linclude, hard linclude unconditional barriers.
+
+  @Override
+  public void caseAIncludeCommand(AIncludeCommand node) {
+    barrier();
+  }
+
+  @Override
+  public void caseAHardIncludeCommand(AHardIncludeCommand node) {
+    barrier();
+  }
+
+  @Override
+  public void caseALincludeCommand(ALincludeCommand node) {
+    barrier();
+  }
+
+  @Override
+  public void caseAHardLincludeCommand(AHardLincludeCommand node) {
+    barrier();
+  }
+
+  // EOF: unconditional barrier.
+
+  @Override
+  public void caseEOF(EOF node) {
+    barrier();
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/InlineRewriter.java b/src/com/google/clearsilver/jsilver/syntax/InlineRewriter.java
new file mode 100644
index 0000000..7bd2952
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/InlineRewriter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.syntax;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException;
+import com.google.clearsilver.jsilver.syntax.analysis.AnalysisAdapter;
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.ADataCommand;
+import com.google.clearsilver.jsilver.syntax.node.AInlineCommand;
+import com.google.clearsilver.jsilver.syntax.node.ANoopCommand;
+import com.google.clearsilver.jsilver.syntax.node.PCommand;
+import com.google.clearsilver.jsilver.syntax.node.TData;
+
+/**
+ * Rewrites the AST to replace all 'inline' commands with their associated inner
+ * command sub-tree, where all whitespace data commands have been removed.
+ *
+ * <p>The following template:
+ * <pre>
+ * <?cs inline?>
+ * <?cs if:x.flag?>
+ *   <?cs var:">> " + x.foo + " <<"?>
+ * <?cs /if?>
+ * <?cs /inline?>
+ * </pre>
+ *
+ * <p>will render as if it had been written:
+ * <pre>
+ * <?cs if:x.flag?><?cs var:">> " + x.foo + " <<"?><?cs /if?>
+ * </pre>
+ *
+ * <p>The inline command is intended only to allow neater template authoring.
+ * As such there is a restriction that any data commands (ie, bare literal text)
+ * inside an inline command can consist only of whitespace characters. This
+ * limits the risk of accidentally modifying the template's output in an
+ * unexpected way when using the inline command. Literal text may still be
+ * rendered in an inlined section if it is part of a var command.
+ *
+ * <p>Data commands containing only whitespace are effectively removed by
+ * replacing them with noop commands. These can be removed (if needed) by a
+ * later optimization step but shouldn't cause any issues.
+ */
+public class InlineRewriter extends DepthFirstAdapter {
+
+  /**
+   * Inner visitor class to recursively replace data commands with noops.
+   */
+  private static AnalysisAdapter WHITESPACE_STRIPPER = new DepthFirstAdapter() {
+    @Override
+    public void caseADataCommand(ADataCommand node) {
+      TData data = node.getData();
+      if (isAllWhitespace(data.getText())) {
+        node.replaceBy(new ANoopCommand());
+        return;
+      }
+      // TODO: Add more information here (line numbers etc...)
+      throw new JSilverBadSyntaxException(
+          "literal text in an inline block may only contain whitespace", data.getText(), null, data
+              .getLine(), data.getPos(), null);
+    }
+
+    @Override
+    public void caseAInlineCommand(AInlineCommand node) {
+      // Once in an inline block, just remove any more we encounter.
+      PCommand command = node.getCommand();
+      node.replaceBy(command);
+      command.apply(this);
+    }
+  };
+
+  private static boolean isAllWhitespace(String s) {
+    for (int i = 0; i < s.length(); i++) {
+      if (!Character.isWhitespace(s.charAt(i))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Removes data commands within an inline command.
+   * 
+   * @throws JSilverBadSyntaxException if any data commands within the inline block contain
+   *         non-whitespace text.
+   */
+  @Override
+  public void caseAInlineCommand(AInlineCommand node) {
+    node.getCommand().apply(WHITESPACE_STRIPPER);
+    node.replaceBy(node.getCommand());
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/SequenceOptimizer.java b/src/com/google/clearsilver/jsilver/syntax/SequenceOptimizer.java
new file mode 100644
index 0000000..0a4fc76
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/SequenceOptimizer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.syntax;
+
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.ASequenceExpression;
+import com.google.clearsilver.jsilver.syntax.node.PExpression;
+
+import java.util.LinkedList;
+
+/**
+ * Simple optimizer to simplify expression sequences which only have a single element. This
+ * optimization should be run as early as possible because it simplifies the syntax tree (and some
+ * later optimizations may rely on the simplified structure).
+ */
+public class SequenceOptimizer extends DepthFirstAdapter {
+
+  /**
+   * Removes sequence expressions with only one element.
+   */
+  @Override
+  public void caseASequenceExpression(ASequenceExpression originalNode) {
+    super.caseASequenceExpression(originalNode);
+    LinkedList<PExpression> args = originalNode.getArgs();
+    if (args.size() == 1) {
+      originalNode.replaceBy(args.getFirst());
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/StructuralWhitespaceStripper.java b/src/com/google/clearsilver/jsilver/syntax/StructuralWhitespaceStripper.java
new file mode 100644
index 0000000..492c616
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/StructuralWhitespaceStripper.java
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.syntax;
+
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.AAltCommand;
+import com.google.clearsilver.jsilver.syntax.node.ACallCommand;
+import com.google.clearsilver.jsilver.syntax.node.ADataCommand;
+import com.google.clearsilver.jsilver.syntax.node.ADefCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEachCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AEvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AIfCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopIncCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALoopToCommand;
+import com.google.clearsilver.jsilver.syntax.node.ALvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.ANameCommand;
+import com.google.clearsilver.jsilver.syntax.node.ANoopCommand;
+import com.google.clearsilver.jsilver.syntax.node.ASetCommand;
+import com.google.clearsilver.jsilver.syntax.node.AUvarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AVarCommand;
+import com.google.clearsilver.jsilver.syntax.node.AWithCommand;
+import com.google.clearsilver.jsilver.syntax.node.Start;
+import com.google.clearsilver.jsilver.syntax.node.TData;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Detects sequences of commands corresponding to a line in the template containing only structural
+ * commands, comments or whitespace and rewrites the syntax tree to effectively remove any data
+ * (text) associated with that line (including the trailing whitespace).
+ * <p>
+ * A structural command is any command that never emits any output. These come in three types:
+ * <ul>
+ * <li>Commands that can contain other commands (eg, "alt", "each", "escape", "if", "loop", "with",
+ * etc...).
+ * <li>Commands that operate on the template itself (eg, "include", "autoescape", etc...).
+ * <li>Comments.
+ * </ul>
+ * <p>
+ * This makes it much easier to write human readable templates in cases where the output format is
+ * whitespace sensitive.
+ * <p>
+ * Thus the input:
+ * 
+ * <pre>
+ * {@literal
+ * ----------------
+ * Value is:
+ * <?cs if:x>0 ?>
+ *   positive
+ * <?cs elif:x<0 ?>
+ *   negative
+ * <?cs else ?>
+ *   zero
+ * <?cs /if ?>.
+ * ----------------
+ * }
+ * </pre>
+ * is equivalent to:
+ * 
+ * <pre>
+ * {@literal
+ * ----------------
+ * Value is:
+ * <?cs if:x>0 ?>  positive
+ * <?cs elif:x<0 ?>  negative
+ * <?cs else ?>  zero
+ * <?cs /if ?>.
+ * ----------------
+ * }
+ * </pre>
+ * but is much easier to read.
+ * <p>
+ * Where data commands become empty they are replaced with Noop commands, which effectively removes
+ * them from the tree. These can be removed (if needed) by a later optimization step but shouldn't
+ * cause any issues.
+ */
+public class StructuralWhitespaceStripper extends DepthFirstAdapter {
+  /**
+   * A regex snippet to match sequences of inline whitespace. The easiest way to define this is as
+   * "not (non-space or newline)".
+   */
+  private static final String IWS = "[^\\S\\n]*";
+
+  /** Pattern to match strings that consist only of inline whitespace. */
+  private static final Pattern INLINE_WHITESPACE = Pattern.compile(IWS);
+
+  /**
+   * Pattern to match strings that start with arbitrary (inline) whitespace, followed by a newline.
+   */
+  private static final Pattern STARTS_WITH_NEWLINE = Pattern.compile("^" + IWS + "\\n");
+
+  /**
+   * Pattern to match strings that end with a newline, followed by trailing (inline) whitespace.
+   */
+  private static final Pattern ENDS_WITH_NEWLINE = Pattern.compile("\\n" + IWS + "$");
+
+  /**
+   * Pattern to capture the content of a string after a leading newline. Only ever used on input
+   * that previously matched STARTS_WITH_NEWLINE.
+   */
+  private static final Pattern LEADING_WHITESPACE_AND_NEWLINE =
+      Pattern.compile("^" + IWS + "\\n(.*)$", Pattern.DOTALL);
+
+  /**
+   * Pattern to capture the content of a string before a trailing newline. Note that this may have
+   * to match text that has already had the final newline removed so we must greedily match the
+   * whitespace rather than the content.
+   */
+  private static final Pattern TRAILING_WHITESPACE =
+      Pattern.compile("^(.*?)" + IWS + "$", Pattern.DOTALL);
+
+  /**
+   * Flag to tell us if we are in whitespace chomping mode. By default we start in this mode because
+   * the content of the first line in a template is not preceded by a newline (but should behave as
+   * if it was). Once this flag has been set to false, it remains unset until a new line is
+   * encountered.
+   * <p>
+   * Note that we only actually remove whitespace when we find the terminating condition rather than
+   * when as visit the nodes (ie, this mode can be aborted and any visited whitespace will be left
+   * untouched).
+   */
+  private boolean maybeChompWhitespace = true;
+
+  /**
+   * Flag to tell us if the line we are processing has an inline command in it.
+   * <p>
+   * An inline command is a complex command (eg. 'if', 'loop') where both the start and end of the
+   * command exists on the same line. Non-complex commands (eg. 'var', 'name') cannot be considered
+   * inline.
+   * <p>
+   * This flag is set when we process the start of a complex command and unset when we finish
+   * processing a line. Thus if the flag is still true when we encounter the end of a complex
+   * command, it tells us that (at least one) complex command was entirely contained within the
+   * current line and that we should stop chomping whitespace for the current line.
+   * <p>
+   * This means we can detect input such as:
+   * 
+   * <pre>
+   * {@literal <?cs if:x?>   <?cs /if?>}
+   * </pre>
+   * for which the trailing newline and surrounding whitespace should not be removed, as opposed to:
+   * 
+   * <pre>
+   * {@literal <?cs if:x?>
+   *    something
+   *  <?cs /if?>
+   * }
+   * </pre>
+   * where the trailing newlines for both the opening and closing of the 'if' command should be
+   * removed.
+   */
+  private boolean currentLineContainsInlineComplexCommand = false;
+
+  /**
+   * First data command we saw when we started 'chomping' whitespace (note that this can be null if
+   * we are at the beginning of a file or when we have chomped a previous data command down to
+   * nothing).
+   */
+  private ADataCommand firstChompedData = null;
+
+  /**
+   * Intermediate whitespace-only data commands that we may need to remove.
+   * <p>
+   * This list is built up as we visit commands and is either processed when we need to remove
+   * structural whitespace or cleared if we encounter situations that prohibit whitespace removal.
+   */
+  private List<ADataCommand> whitespaceData = new ArrayList<ADataCommand>();
+
+  private static boolean isInlineWhitespace(String text) {
+    return INLINE_WHITESPACE.matcher(text).matches();
+  }
+
+  private static boolean startsWithNewline(String text) {
+    return STARTS_WITH_NEWLINE.matcher(text).find();
+  }
+
+  private static boolean endsWithNewline(String text) {
+    return ENDS_WITH_NEWLINE.matcher(text).find();
+  }
+
+  /**
+   * Removes leading whitespace (including first newline) from the given string. The text must start
+   * with optional whitespace followed by a newline.
+   */
+  private static String stripLeadingWhitespaceAndNewline(String text) {
+    Matcher matcher = LEADING_WHITESPACE_AND_NEWLINE.matcher(text);
+    if (!matcher.matches()) {
+      throw new IllegalStateException("Text '" + text + "' should have leading whitespace/newline.");
+    }
+    return matcher.group(1);
+  }
+
+  /**
+   * Removes trailing whitespace (if present) from the given string.
+   */
+  private static String stripTrailingWhitespace(String text) {
+    Matcher matcher = TRAILING_WHITESPACE.matcher(text);
+    if (!matcher.matches()) {
+      // The trailing whitespace regex should never fail to match a string.
+      throw new AssertionError("Error in regular expression");
+    }
+    return matcher.group(1);
+  }
+
+  /**
+   * Remove whitespace (including first newline) from the start of the given data command (replacing
+   * it with a Noop command if it becomes empty). Returns a modified data command, or null if all
+   * text was removed.
+   * <p>
+   * The given command can be null at the beginning of the file or if the original data command was
+   * entirely consumed by a previous strip operation (remember that data commands can be processed
+   * twice, at both the start and end of a whitespace sequence).
+   */
+  private static ADataCommand stripLeadingWhitespaceAndNewline(ADataCommand data) {
+    if (data != null) {
+      String text = stripLeadingWhitespaceAndNewline(data.getData().getText());
+      if (text.isEmpty()) {
+        data.replaceBy(new ANoopCommand());
+        // Returning null just means we have chomped the whitespace to nothing.
+        data = null;
+      } else {
+        data.setData(new TData(text));
+      }
+    }
+    return data;
+  }
+
+  /**
+   * Removes whitespace from the end of the given data command (replacing it with a Noop command if
+   * it becomes empty).
+   */
+  private static void stripTrailingWhitespace(ADataCommand data) {
+    if (data != null) {
+      String text = stripTrailingWhitespace(data.getData().getText());
+      if (text.isEmpty()) {
+        data.replaceBy(new ANoopCommand());
+      } else {
+        data.setData(new TData(text));
+      }
+    }
+  }
+
+  /**
+   * Removes all data commands collected while chomping the current line and clears the given list.
+   */
+  private static void removeWhitespace(List<ADataCommand> whitespaceData) {
+    for (ADataCommand data : whitespaceData) {
+      data.replaceBy(new ANoopCommand());
+    }
+    whitespaceData.clear();
+  }
+
+  @Override
+  public void caseStart(Start node) {
+    // Process the hierarchy.
+    super.caseStart(node);
+    // We might end after processing a non-data node, so deal with any
+    // unprocessed whitespace before we exit.
+    if (maybeChompWhitespace) {
+      stripTrailingWhitespace(firstChompedData);
+      removeWhitespace(whitespaceData);
+      firstChompedData = null;
+    }
+    // Verify we have consumed (and cleared) any object references.
+    if (firstChompedData != null) {
+      throw new IllegalStateException("Unexpected first data node.");
+    }
+    if (!whitespaceData.isEmpty()) {
+      throw new IllegalStateException("Unexpected data nodes.");
+    }
+  }
+
+  @Override
+  public void caseADataCommand(ADataCommand data) {
+    final String originalText = data.getData().getText();
+    if (maybeChompWhitespace) {
+      if (isInlineWhitespace(originalText)) {
+        // This data command is whitespace between two commands on the same
+        // line, simply chomp it and continue ("Om-nom-nom").
+        whitespaceData.add(data);
+        return;
+      }
+      if (startsWithNewline(originalText)) {
+        // This data command is at the end of a line that contains only
+        // structural commands and whitespace. We remove all whitespace
+        // associated with this line by:
+        // * Stripping whitespace from the end of the data command at the start
+        // of this line.
+        // * Removing all intermediate (whitespace only) data commands.
+        // * Stripping whitespace from the start of the current data command.
+        stripTrailingWhitespace(firstChompedData);
+        removeWhitespace(whitespaceData);
+        data = stripLeadingWhitespaceAndNewline(data);
+        currentLineContainsInlineComplexCommand = false;
+      } else {
+        // This data command contains some non-whitespace text so we must abort
+        // the chomping of this line and output it normally.
+        abortWhitespaceChompingForCurrentLine();
+      }
+    }
+    // Test to see if we should start chomping on the next line.
+    maybeChompWhitespace = endsWithNewline(originalText);
+    // Note that data can be null here if we stripped all the whitespace from
+    // it (which means that firstChompedData can be null next time around).
+    firstChompedData = maybeChompWhitespace ? data : null;
+  }
+
+  /**
+   * Helper method to abort whitespace processing for the current line. This method is idempotent on
+   * a per line basis, and once it has been called the state is only reset at the start of the next
+   * line.
+   */
+  private void abortWhitespaceChompingForCurrentLine() {
+    maybeChompWhitespace = false;
+    currentLineContainsInlineComplexCommand = false;
+    whitespaceData.clear();
+  }
+
+  // ---- Inline commands that prohibit whitespace removal. ----
+
+  @Override
+  public void inAAltCommand(AAltCommand node) {
+    abortWhitespaceChompingForCurrentLine();
+  }
+
+  @Override
+  public void inACallCommand(ACallCommand node) {
+    abortWhitespaceChompingForCurrentLine();
+  }
+
+  @Override
+  public void inAEvarCommand(AEvarCommand node) {
+    abortWhitespaceChompingForCurrentLine();
+  }
+
+  @Override
+  public void inALvarCommand(ALvarCommand node) {
+    abortWhitespaceChompingForCurrentLine();
+  }
+
+  @Override
+  public void inANameCommand(ANameCommand node) {
+    abortWhitespaceChompingForCurrentLine();
+  }
+
+  @Override
+  public void inASetCommand(ASetCommand node) {
+    abortWhitespaceChompingForCurrentLine();
+  }
+
+  @Override
+  public void inAUvarCommand(AUvarCommand node) {
+    abortWhitespaceChompingForCurrentLine();
+  }
+
+  @Override
+  public void inAVarCommand(AVarCommand node) {
+    abortWhitespaceChompingForCurrentLine();
+  }
+
+  // ---- Two part (open/close) commands that can have child commands. ----
+
+  public void enterComplexCommand() {
+    currentLineContainsInlineComplexCommand = true;
+  }
+
+  public void exitComplexCommand() {
+    if (currentLineContainsInlineComplexCommand) {
+      abortWhitespaceChompingForCurrentLine();
+    }
+  }
+
+  @Override
+  public void caseAAltCommand(AAltCommand node) {
+    enterComplexCommand();
+    super.caseAAltCommand(node);
+    exitComplexCommand();
+  }
+
+  @Override
+  public void caseADefCommand(ADefCommand node) {
+    enterComplexCommand();
+    super.caseADefCommand(node);
+    exitComplexCommand();
+  }
+
+  @Override
+  public void caseAEachCommand(AEachCommand node) {
+    enterComplexCommand();
+    super.caseAEachCommand(node);
+    exitComplexCommand();
+  }
+
+  @Override
+  public void caseAEscapeCommand(AEscapeCommand node) {
+    enterComplexCommand();
+    super.caseAEscapeCommand(node);
+    exitComplexCommand();
+  }
+
+  @Override
+  public void caseAIfCommand(AIfCommand node) {
+    enterComplexCommand();
+    super.caseAIfCommand(node);
+    exitComplexCommand();
+  }
+
+  @Override
+  public void caseALoopCommand(ALoopCommand node) {
+    enterComplexCommand();
+    super.caseALoopCommand(node);
+    exitComplexCommand();
+  }
+
+  @Override
+  public void caseALoopIncCommand(ALoopIncCommand node) {
+    enterComplexCommand();
+    super.caseALoopIncCommand(node);
+    exitComplexCommand();
+  }
+
+  @Override
+  public void caseALoopToCommand(ALoopToCommand node) {
+    enterComplexCommand();
+    super.caseALoopToCommand(node);
+    exitComplexCommand();
+  }
+
+  @Override
+  public void caseAWithCommand(AWithCommand node) {
+    enterComplexCommand();
+    super.caseAWithCommand(node);
+    exitComplexCommand();
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/SyntaxTreeBuilder.java b/src/com/google/clearsilver/jsilver/syntax/SyntaxTreeBuilder.java
new file mode 100644
index 0000000..4cf3c98
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/SyntaxTreeBuilder.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.syntax;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException;
+import com.google.clearsilver.jsilver.exceptions.JSilverIOException;
+import com.google.clearsilver.jsilver.syntax.lexer.Lexer;
+import com.google.clearsilver.jsilver.syntax.lexer.LexerException;
+import com.google.clearsilver.jsilver.syntax.node.Start;
+import com.google.clearsilver.jsilver.syntax.node.Switch;
+import com.google.clearsilver.jsilver.syntax.parser.Parser;
+import com.google.clearsilver.jsilver.syntax.parser.ParserException;
+
+import java.io.IOException;
+import java.io.PushbackReader;
+import java.io.Reader;
+import java.util.Arrays;
+
+/**
+ * Parses a JSilver text template into an abstract syntax tree (AST).
+ * <p/>
+ * Acts as a facade around SableCC generated code. The simplest way to process the resulting tree is
+ * to use a visitor by extending
+ * {@link com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter} and passing it to
+ * {@link Start#apply(com.google.clearsilver.jsilver.syntax.node.Switch)}.
+ * <p/>
+ * <h3>Example:</h3>
+ * 
+ * <pre>
+ * SyntaxTreeBuilder builder = new SyntaxTreeBuilder();
+ * Start tree = builder.parse(myTemplate, "some-template.cs");
+ * // Dump out the tree
+ * tree.apply(new SyntaxTreeDumper(System.out));
+ * </pre>
+ * 
+ */
+public class SyntaxTreeBuilder {
+
+  public SyntaxTreeBuilder() {}
+
+  /**
+   * Size of buffer in PushbackReader... needs to be large enough to parse CS opening tag and push
+   * back if it is not valid. e.g. "&lt;?csX" : not a tag, so pushback.
+   */
+  private static final int PUSHBACK_SIZE = "<?cs ".length();
+
+  /**
+   * Syntax tree optimizers, declared in the order they must be applied:
+   * <ol>
+   * <li>Type resultion makes the abstract tree concrete and must come first.
+   * <li>Sequence optimization simplifies the tree and should come before most other optimizations.
+   * <li>Inline rewriting to remove data nodes from 'inline' sections. This should come before any
+   * optimization of variables.
+   * <li>Var optimization simplifies complex var expressions and must come after both type
+   * resolution and sequence optimization.
+   * </ol>
+   */
+  protected final Switch typeResolver = new TypeResolver();
+  protected final Switch sequenceOptimizer = new SequenceOptimizer();
+  protected final Switch inlineRewriter = new InlineRewriter();
+  protected final Switch varOptimizer = new VarOptimizer(Arrays.asList("html", "js", "url"));
+
+  /**
+   * Perform any additional processing on the tree. EscapeMode and templateName are required by
+   * AutoEscaper.
+   * 
+   * @param root The AST to post process.
+   * @param escapeMode The escaping mode to apply to the given AST. If this is not
+   *        EscapeMode.ESCAPE_NONE, AutoEscaper will be called on the AST.
+   * @param templateName The name of template being processed. Passed to AutoEscaper, which uses it
+   *        when displaying error messages.
+   */
+  protected void process(Start root, EscapeMode escapeMode, String templateName) {
+    root.apply(typeResolver);
+    root.apply(sequenceOptimizer);
+    root.apply(inlineRewriter);
+    // Temporarily disabled ('cos it doesn't quite work)
+    // root.apply(varOptimizer);
+
+    if (!escapeMode.equals(EscapeMode.ESCAPE_NONE)) {
+      // AutoEscaper contains per-AST context like HTML parser object.
+      // Therefore, instantiating a new AutoEscaper each time.
+      root.apply(new AutoEscaper(escapeMode, templateName));
+    }
+  }
+
+  /**
+   * @param templateName Used for meaningful error messages.
+   * @param escapeMode Run {@link AutoEscaper} on the abstract syntax tree created from template.
+   */
+  public TemplateSyntaxTree parse(Reader input, String templateName, EscapeMode escapeMode)
+      throws JSilverIOException, JSilverBadSyntaxException {
+    try {
+      PushbackReader pushbackReader = new PushbackReader(input, PUSHBACK_SIZE);
+      Lexer lexer = new Lexer(pushbackReader);
+      Parser parser = new Parser(lexer);
+      Start root = parser.parse();
+      process(root, escapeMode, templateName);
+      return new TemplateSyntaxTree(root);
+    } catch (IOException exception) {
+      throw new JSilverIOException(exception);
+    } catch (ParserException exception) {
+      throw new JSilverBadSyntaxException(exception.getMessage(), exception.getToken().getText(),
+          templateName, exception.getToken().getLine(), exception.getToken().getPos(), exception);
+    } catch (LexerException exception) {
+      throw new JSilverBadSyntaxException(exception.getMessage(), null, templateName,
+          JSilverBadSyntaxException.UNKNOWN_POSITION, JSilverBadSyntaxException.UNKNOWN_POSITION,
+          exception);
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/SyntaxTreeDumper.java b/src/com/google/clearsilver/jsilver/syntax/SyntaxTreeDumper.java
new file mode 100644
index 0000000..9402b52
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/SyntaxTreeDumper.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.syntax;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.exceptions.JSilverIOException;
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.EOF;
+import com.google.clearsilver.jsilver.syntax.node.Node;
+import com.google.clearsilver.jsilver.syntax.node.Start;
+import com.google.clearsilver.jsilver.syntax.node.Token;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Dumps the syntax tree to text. Useful for debugging and understanding how the tree is structured.
+ */
+public class SyntaxTreeDumper extends DepthFirstAdapter {
+
+  private final Appendable out;
+
+  private final String newLine = System.getProperty("line.separator");
+
+  private int indent;
+
+  public SyntaxTreeDumper(Appendable out) {
+    this.out = out;
+  }
+
+  /**
+   * Dumps to System.out.
+   */
+  public SyntaxTreeDumper() {
+    this(System.out);
+  }
+
+  @Override
+  public void defaultIn(Node node) {
+    write(nodeName(node) + " {");
+    indent++;
+  }
+
+  @Override
+  public void defaultOut(Node node) {
+    indent--;
+    write("}");
+  }
+
+  @Override
+  public void defaultCase(Node node) {
+    write(nodeName(node));
+  }
+
+  private String nodeName(Node node) {
+    if (node instanceof Start || node instanceof EOF) {
+      return node.getClass().getSimpleName();
+    } else if (node instanceof Token) {
+      Token token = (Token) node;
+      String tokenType = token.getClass().getSimpleName().substring(1);
+      return tokenType + " [line:" + token.getLine() + ",pos:" + token.getPos() + "] \""
+          + escape(token.getText()) + "\"";
+    } else {
+      // Turn PSomeProduction, AConcreteSomeProduction
+      // Into SomeProduction, Concrete
+      String p = node.getClass().getSuperclass().getSimpleName().substring(1);
+      String a = node.getClass().getSimpleName().substring(1);
+      a = a.substring(0, a.length() - p.length());
+      return "<" + a + ">" + p;
+    }
+
+  }
+
+  private String escape(String text) {
+    StringBuilder result = new StringBuilder();
+    for (int i = 0; i < text.length(); i++) {
+      char c = text.charAt(i);
+      switch (c) {
+        case '\\':
+          result.append("\\\\");
+          break;
+        case '"':
+          result.append("\\\"");
+          break;
+        case '\n':
+          result.append("\\n");
+          break;
+        case '\r':
+          result.append("\\r");
+          break;
+        case '\t':
+          result.append("\\t");
+          break;
+        default:
+          result.append(c);
+      }
+    }
+    return result.toString();
+  }
+
+  private void write(String text) {
+    try {
+      // Write to temp string in case output isn't buffered.
+      StringBuilder line = new StringBuilder();
+      for (int i = 0; i < indent; i++) {
+        line.append("  ");
+      }
+      line.append(text);
+      line.append(newLine);
+      out.append(line);
+    } catch (IOException e) {
+      throw new JSilverIOException(e);
+    }
+  }
+
+  /**
+   * Simple command line tool for parsing a template and dumping out the AST.
+   */
+  public static void main(String[] args) throws IOException {
+    if (args.length == 0) {
+      System.err.println("Provide filename of template.");
+      return;
+    }
+    String filename = args[0];
+    Reader reader = new BufferedReader(new FileReader(filename));
+    try {
+      SyntaxTreeBuilder builder = new SyntaxTreeBuilder();
+      TemplateSyntaxTree tree = builder.parse(reader, filename, EscapeMode.ESCAPE_NONE);
+      tree.apply(new SyntaxTreeDumper(System.out));
+    } finally {
+      reader.close();
+    }
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/SyntaxTreeOptimizer.java b/src/com/google/clearsilver/jsilver/syntax/SyntaxTreeOptimizer.java
new file mode 100644
index 0000000..13cfed0
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/SyntaxTreeOptimizer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.syntax;
+
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.AMultipleCommand;
+import com.google.clearsilver.jsilver.syntax.node.AOptimizedMultipleCommand;
+
+/**
+ * Visitor that can be applied to the AST to optimize it by replacing nodes with more efficient
+ * implementations than the default SableCC generated versions.
+ */
+public class SyntaxTreeOptimizer extends DepthFirstAdapter {
+
+  /**
+   * Replace AMultipleCommand nodes with AOptimizedMultipleCommands, which iterates over children
+   * faster.
+   */
+  @Override
+  public void caseAMultipleCommand(AMultipleCommand originalNode) {
+    // Recurse through child nodes first. Because the optimised node doesn't
+    // handle replacement, go leaves-first.
+    super.caseAMultipleCommand(originalNode);
+    // Replace this node with the optimized version.
+    originalNode.replaceBy(new AOptimizedMultipleCommand(originalNode));
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/TemplateSyntaxTree.java b/src/com/google/clearsilver/jsilver/syntax/TemplateSyntaxTree.java
new file mode 100644
index 0000000..324a6a2
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/TemplateSyntaxTree.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.syntax;
+
+import com.google.clearsilver.jsilver.syntax.node.Start;
+import com.google.clearsilver.jsilver.syntax.node.Switch;
+import com.google.clearsilver.jsilver.syntax.node.Switchable;
+
+/**
+ * Simple wrapper class to encapsulate the root node of the AST and allow additional information to
+ * be associated with it.
+ */
+public class TemplateSyntaxTree implements Switchable {
+  private final Start root;
+
+  TemplateSyntaxTree(Start root) {
+    this.root = root;
+  }
+
+  public Start getRoot() {
+    return root;
+  }
+
+  @Override
+  public void apply(Switch sw) {
+    root.apply(sw);
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/TypeResolver.java b/src/com/google/clearsilver/jsilver/syntax/TypeResolver.java
new file mode 100644
index 0000000..6488d59
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/TypeResolver.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.syntax;
+
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.AAddExpression;
+import com.google.clearsilver.jsilver.syntax.node.ADecimalExpression;
+import com.google.clearsilver.jsilver.syntax.node.ADivideExpression;
+import com.google.clearsilver.jsilver.syntax.node.AEqExpression;
+import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression;
+import com.google.clearsilver.jsilver.syntax.node.AHexExpression;
+import com.google.clearsilver.jsilver.syntax.node.AModuloExpression;
+import com.google.clearsilver.jsilver.syntax.node.AMultiplyExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
+import com.google.clearsilver.jsilver.syntax.node.ANeExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANegativeExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericAddExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericEqExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericExpression;
+import com.google.clearsilver.jsilver.syntax.node.ANumericNeExpression;
+import com.google.clearsilver.jsilver.syntax.node.ASubtractExpression;
+import com.google.clearsilver.jsilver.syntax.node.PExpression;
+import com.google.clearsilver.jsilver.syntax.node.PVariable;
+
+/**
+ * AST visitor to add numeric expressions to the syntax tree.
+ * 
+ * <p>
+ * There are three types of expression we need to process; addition, equality and inequality. By
+ * default these are treated as string expressions unless one of the operands is numeric, in which
+ * case the original expression is replaced with its numeric equivalent. This behavior seems to
+ * exactly match Clearsilver's type inference system.
+ * 
+ * <p>
+ * Note how we preprocess our node before testing to see is it should be replaced. This is very
+ * important because it means that type inference is propagated correctly along compound
+ * expressions. Consider the expression:
+ * 
+ * <pre>#a + b + c</pre>
+ * 
+ * which is parsed (left-to-right) as:
+ * 
+ * <pre>(#a + b) + c</pre>
+ * 
+ * When we process the left-hand-side sub-expression {@code #a + b} it is turned into a numeric
+ * addition (due to the forced numeric value on the left). Then when we process the main expression
+ * we propagate the numeric type into it.
+ * 
+ * <p>
+ * This matches Clearsilver behavior but means that the expressions:
+ * 
+ * <pre>#a + b + c</pre>
+ * 
+ * and
+ * 
+ * <pre>c + b + #a</pre>
+ * 
+ * produce different results (the {@code c + b} subexpression in the latter is evaluated as string
+ * concatenation and not numeric addition).
+ */
+public class TypeResolver extends DepthFirstAdapter {
+
+  @Override
+  public void caseAAddExpression(AAddExpression node) {
+    super.caseAAddExpression(node);
+    PExpression lhs = node.getLeft();
+    PExpression rhs = node.getRight();
+    if (isNumeric(lhs) || isNumeric(rhs)) {
+      node.replaceBy(new ANumericAddExpression(lhs, rhs));
+    }
+  }
+
+  @Override
+  public void caseAEqExpression(AEqExpression node) {
+    super.caseAEqExpression(node);
+    PExpression lhs = node.getLeft();
+    PExpression rhs = node.getRight();
+    if (isNumeric(lhs) || isNumeric(rhs)) {
+      node.replaceBy(new ANumericEqExpression(lhs, rhs));
+    }
+  }
+
+  @Override
+  public void caseANeExpression(ANeExpression node) {
+    super.caseANeExpression(node);
+    PExpression lhs = node.getLeft();
+    PExpression rhs = node.getRight();
+    if (isNumeric(lhs) || isNumeric(rhs)) {
+      node.replaceBy(new ANumericNeExpression(lhs, rhs));
+    }
+  }
+
+  /**
+   * Determines whether the given (sub)expression is numeric, which in turn means that its parent
+   * expression should be treated as numeric if possible.
+   */
+  static boolean isNumeric(PExpression node) {
+    return node instanceof ANumericExpression // forced numeric (#a)
+        || node instanceof ANumericAddExpression // numeric addition (a + b)
+        || node instanceof ASubtractExpression // subtraction (a - b)
+        || node instanceof AMultiplyExpression // multiplication (a * b)
+        || node instanceof ADivideExpression // division (a / b)
+        || node instanceof AModuloExpression // modulu (x % b)
+        || node instanceof ADecimalExpression // literal decimal (213)
+        || node instanceof AHexExpression // literal hex (0xabc or 0XABC)
+        || node instanceof ANegativeExpression // negative expression (-a)
+        || isNumericFunction(node); // numeric function (subcount)
+  }
+
+  /**
+   * Determine if the given expression represents a numeric function.
+   */
+  static boolean isNumericFunction(PExpression node) {
+    if (!(node instanceof AFunctionExpression)) {
+      return false;
+    }
+    PVariable functionName = ((AFunctionExpression) node).getName();
+    if (functionName instanceof ANameVariable) {
+      String name = ((ANameVariable) functionName).getWord().getText();
+      if ("max".equals(name) || "min".equals(name) || "abs".equals(name) || "subcount".equals(name)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/VarOptimizer.java b/src/com/google/clearsilver/jsilver/syntax/VarOptimizer.java
new file mode 100644
index 0000000..2b6538b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/VarOptimizer.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.syntax;
+
+import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter;
+import com.google.clearsilver.jsilver.syntax.node.AAddExpression;
+import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand;
+import com.google.clearsilver.jsilver.syntax.node.AFunctionExpression;
+import com.google.clearsilver.jsilver.syntax.node.AMultipleCommand;
+import com.google.clearsilver.jsilver.syntax.node.ANameVariable;
+import com.google.clearsilver.jsilver.syntax.node.AStringExpression;
+import com.google.clearsilver.jsilver.syntax.node.AVarCommand;
+import com.google.clearsilver.jsilver.syntax.node.Node;
+import com.google.clearsilver.jsilver.syntax.node.PCommand;
+import com.google.clearsilver.jsilver.syntax.node.PExpression;
+import com.google.clearsilver.jsilver.syntax.node.PPosition;
+import com.google.clearsilver.jsilver.syntax.node.PVariable;
+import com.google.clearsilver.jsilver.syntax.node.TString;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+/**
+ * Recursively optimizes the syntax tree with a set of simple operations. This class currently
+ * optimizes:
+ * <ul>
+ * <li>String concatenation in var commands
+ * <li>Function calls to escaping functions
+ * </ul>
+ * <p>
+ * String add expressions in var commands are optimized by replacing something like:
+ * 
+ * <pre>
+ * &lt;cs? var:a + b ?&gt;
+ * </pre>
+ * with:
+ * 
+ * <pre>
+ * &lt;cs? var:a ?&gt;&lt;cs? var:b ?&gt;
+ * </pre>
+ * 
+ * This avoids having to construct the intermediate result {@code a + b} at runtime and reduces
+ * runtime heap allocations.
+ * <p>
+ * Functions call to escaping functions are optimized by replacing them with the equivalent escaping
+ * construct. This is faster because escapers are called with the strings themselves whereas general
+ * function calls require value objects to be created.
+ * <p>
+ * Expressions such as:
+ * 
+ * <pre>
+ * &lt;cs? var:html_escape(foo) ?&gt;
+ * </pre>
+ * are turned into:
+ * 
+ * <pre>
+ * &lt;cs? escape:&quot;html&quot; ?&gt;
+ * &lt;cs? var:foo ?&gt;
+ * &lt;?cs /escape ?&gt;
+ * </pre>
+ * 
+ * It also optimizes sequences of escaped expressions into a single escaped sequence.
+ * <p>
+ * It is important to note that these optimizations cannot be done in isolation if we want to
+ * optimize compound expressions such as:
+ * 
+ * <pre>
+ * &lt;cs? html_escape(foo + bar) + baz ?&gt;
+ * </pre>
+ * which is turned into:
+ * 
+ * <pre>
+ * &lt;cs? escape:&quot;html&quot; ?&gt;
+ * &lt;cs? var:foo ?&gt;
+ * &lt;cs? var:bar ?&gt;
+ * &lt;?cs /escape ?&gt;
+ * &lt;?cs var:baz ?&gt;
+ * </pre>
+ * 
+ * WARNING: This class isn't strictly just an optimization and its modification of the syntax tree
+ * actually improves JSilver's behavior, bringing it more in line with ClearSilver. Consider the
+ * sequence:
+ * 
+ * <pre>
+ * &lt;cs? escape:&quot;html&quot; ?&gt;
+ * &lt;cs? var:url_escape(foo) ?&gt;
+ * &lt;?cs /escape ?&gt;
+ * </pre>
+ * 
+ * In JSilver (without this optimizer being run) this would result in {@code foo} being escaped by
+ * both the html escaper and the url escaping function. However ClearSilver treats top-level escaper
+ * functions specially and {@code foo} is only escaped once by the url escaping function.
+ * 
+ * The good news is that this optimization rewrites the above example to:
+ * 
+ * <pre>
+ * &lt;cs? escape:&quot;html&quot; ?&gt;
+ * &lt;cs? escape:&quot;url&quot; ?&gt;
+ * &lt;cs? var:foo ?&gt;
+ * &lt;?cs /escape ?&gt;
+ * &lt;?cs /escape ?&gt;
+ * </pre>
+ * which fixes the problem because the new url escaper replaces the existing html escaper (rather
+ * than combining with it).
+ * 
+ * The only fly in the ointment here is the {@code url_validate} function which is treated like an
+ * escaper by ClearSilver but which does not (currently) have an escaper associated with it. This
+ * means that:
+ * 
+ * <pre>
+ * &lt;cs? escape:&quot;html&quot; ?&gt;
+ * &lt;cs? var:url_validate(foo) ?&gt;
+ * &lt;?cs /escape ?&gt;
+ * </pre>
+ * will not be rewritten by this class and will result in {@code foo} being escaped twice.
+ * 
+ */
+public class VarOptimizer extends DepthFirstAdapter {
+
+  /**
+   * A list of escaper names that are also exposed as escaping functions (eg, if the "foo" escaper
+   * is also exposed as "foo_escape" function then this collection should contain the string "foo").
+   */
+  private final Collection<String> escaperNames;
+
+  public VarOptimizer(Collection<String> escaperNames) {
+    this.escaperNames = escaperNames;
+  }
+
+  @Override
+  public void caseAMultipleCommand(AMultipleCommand multiCommand) {
+    super.caseAMultipleCommand(multiCommand);
+    multiCommand.replaceBy(optimizeEscapeSequences(multiCommand));
+  }
+
+  @Override
+  public void caseAVarCommand(AVarCommand varCommand) {
+    super.caseAVarCommand(varCommand);
+    varCommand.replaceBy(optimizeVarCommands(varCommand));
+  }
+
+  /**
+   * Optimizes a complex var command by recursively expanding its expression into a sequence of
+   * simpler var commands. Currently two expressions are targetted for expansion: string
+   * concatenation and escaping functions.
+   */
+  private PCommand optimizeVarCommands(AVarCommand varCommand) {
+    PExpression expression = varCommand.getExpression();
+    PPosition position = varCommand.getPosition();
+
+    // This test relies on the type optimizer having replaced add commands
+    // with numeric add commands.
+    if (expression instanceof AAddExpression) {
+      // Replace: <?cs var:a + b ?>
+      // with: <?cs var:a ?><?cs var:b ?>
+      AAddExpression addExpression = (AAddExpression) expression;
+      AMultipleCommand multiCommand = new AMultipleCommand();
+      addToContents(multiCommand, optimizedVarCommandOf(position, addExpression.getLeft()));
+      addToContents(multiCommand, optimizedVarCommandOf(position, addExpression.getRight()));
+      return optimizeEscapeSequences(multiCommand);
+    }
+
+    // This test relies on the sequence optimizer removing single element
+    // sequence commands.
+    if (expression instanceof AFunctionExpression) {
+      // Replace: <?cs var:foo_escape(x) ?>
+      // with: <?cs escape:"foo" ?><?cs var:x ?><?cs /escape ?>
+      AFunctionExpression functionExpression = (AFunctionExpression) expression;
+      String name = escapeNameOf(functionExpression);
+      if (escaperNames.contains(name)) {
+        LinkedList<PExpression> args = functionExpression.getArgs();
+        if (args.size() == 1) {
+          return new AEscapeCommand(position, quotedStringExpressionOf(name),
+              optimizedVarCommandOf(position, args.getFirst()));
+        }
+      }
+    }
+    return varCommand;
+  }
+
+  /**
+   * Create a var command from the given expression and recursively optimize it, returning the
+   * result.
+   */
+  private PCommand optimizedVarCommandOf(PPosition position, PExpression expression) {
+    return optimizeVarCommands(new AVarCommand(cloneOf(position), cloneOf(expression)));
+  }
+
+  /** Simple helper to clone nodes in a typesafe way */
+  @SuppressWarnings("unchecked")
+  private static <T extends Node> T cloneOf(T t) {
+    return (T) t.clone();
+  }
+
+  /**
+   * Helper to efficiently add commands to a multiple command (if the command to be added is a
+   * multiple command, we add its contents). This is used to implement a tail recursion optimization
+   * to flatten multiple commands.
+   */
+  private static void addToContents(AMultipleCommand multi, PCommand command) {
+    if (command instanceof AMultipleCommand) {
+      multi.getCommand().addAll(((AMultipleCommand) command).getCommand());
+    } else {
+      multi.getCommand().add(command);
+    }
+  }
+
+  /** When used as functions, escapers have the name 'foo_escape' */
+  private static final String ESCAPE_SUFFIX = "_escape";
+
+  /**
+   * Returns the name of the escaper which could replace this function (or null if this function
+   * cannot be replaced).
+   */
+  private static String escapeNameOf(AFunctionExpression function) {
+    PVariable nvar = function.getName();
+    if (!(nvar instanceof ANameVariable)) {
+      // We are not interested in dynamic function calls (such as "a.b(x)")
+      return null;
+    }
+    String name = ((ANameVariable) nvar).getWord().getText();
+    if (!name.endsWith(ESCAPE_SUFFIX)) {
+      return null;
+    }
+    return name.substring(0, name.length() - ESCAPE_SUFFIX.length());
+  }
+
+  /**
+   * Returns a quoted string expression of the given text.
+   * <p>
+   * This is used because when an escaper is called as a function we need to replace:
+   * 
+   * <pre>
+   * &lt;cs? var:foo_escape(bar) ?&gt;
+   * </pre>
+   * with:
+   * 
+   * <pre>
+   * &lt;cs? escape:&quot;foo&quot; ?&gt;&lt;cs? var:bar ?&gt;&lt;?cs /escape ?&gt;
+   * </pre>
+   * Using the quoted escaper name.
+   */
+  private static AStringExpression quotedStringExpressionOf(String text) {
+    assert text.indexOf('"') == -1;
+    return new AStringExpression(new TString('"' + text + '"'));
+  }
+
+  /**
+   * Returns a new command containing the contents of the given multiple command but with with
+   * multiple successive (matching) escape commands folded into one.
+   */
+  private static PCommand optimizeEscapeSequences(AMultipleCommand multiCommand) {
+    AEscapeCommand lastEscapeCommand = null;
+    LinkedList<PCommand> commands = new LinkedList<PCommand>();
+    for (PCommand command : multiCommand.getCommand()) {
+      AEscapeCommand escapeCommand = asSimpleEscapeCommand(command);
+      if (isSameEscaper(escapeCommand, lastEscapeCommand)) {
+        addToContents(contentsOf(lastEscapeCommand), escapeCommand.getCommand());
+      } else {
+        // Add the original command and set the escaper (possibly null)
+        commands.add(command);
+        lastEscapeCommand = escapeCommand;
+      }
+    }
+    assert !commands.isEmpty();
+    return (commands.size() > 1) ? new AMultipleCommand(commands) : commands.getFirst();
+  }
+
+  /**
+   * Returns the escaped command associated with the given escape function as a multiple command. If
+   * the command was already a multiple command, it is returned, otherwise a new multiple command is
+   * created to wrap the original escaped command. This helper facilitates merging multiple
+   * sequences of escapers.
+   */
+  private static AMultipleCommand contentsOf(AEscapeCommand escapeCommand) {
+    PCommand escapedCommand = escapeCommand.getCommand();
+    if (escapedCommand instanceof AMultipleCommand) {
+      return (AMultipleCommand) escapedCommand;
+    }
+    AMultipleCommand multiCommand = new AMultipleCommand();
+    multiCommand.getCommand().add(escapedCommand);
+    escapeCommand.setCommand(multiCommand);
+    return multiCommand;
+  }
+
+  /**
+   * Returns the given command only if it is an escape command with a simple, string literal, name;
+   * otherwise returns {@code null}.
+   */
+  private static AEscapeCommand asSimpleEscapeCommand(PCommand command) {
+    if (!(command instanceof AEscapeCommand)) {
+      return null;
+    }
+    AEscapeCommand escapeCommand = (AEscapeCommand) command;
+    if (!(escapeCommand.getExpression() instanceof AStringExpression)) {
+      return null;
+    }
+    return escapeCommand;
+  }
+
+  /**
+   * Compares two simple escape commands and returns true if they perform the same escaping
+   * function.
+   */
+  private static boolean isSameEscaper(AEscapeCommand newCommand, AEscapeCommand oldCommand) {
+    if (newCommand == null || oldCommand == null) {
+      return false;
+    }
+    return simpleNameOf(newCommand).equals(simpleNameOf(oldCommand));
+  }
+
+  /**
+   * Returns the name of the given simple escape command (as returned by
+   * {@link #asSimpleEscapeCommand(PCommand)}).
+   */
+  private static String simpleNameOf(AEscapeCommand escapeCommand) {
+    return ((AStringExpression) escapeCommand.getExpression()).getValue().getText();
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/analysis/Analysis.java b/src/com/google/clearsilver/jsilver/syntax/analysis/Analysis.java
new file mode 100644
index 0000000..30ea618
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/analysis/Analysis.java
@@ -0,0 +1,135 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.analysis;
+
+import com.google.clearsilver.jsilver.syntax.node.*;
+
+public interface Analysis extends Switch
+{
+    Object getIn(Node node);
+    void setIn(Node node, Object o);
+    Object getOut(Node node);
+    void setOut(Node node, Object o);
+
+    void caseStart(Start node);
+    void caseAMultipleCommand(AMultipleCommand node);
+    void caseACommentCommand(ACommentCommand node);
+    void caseADataCommand(ADataCommand node);
+    void caseAVarCommand(AVarCommand node);
+    void caseALvarCommand(ALvarCommand node);
+    void caseAEvarCommand(AEvarCommand node);
+    void caseAUvarCommand(AUvarCommand node);
+    void caseASetCommand(ASetCommand node);
+    void caseANameCommand(ANameCommand node);
+    void caseAEscapeCommand(AEscapeCommand node);
+    void caseAAutoescapeCommand(AAutoescapeCommand node);
+    void caseAWithCommand(AWithCommand node);
+    void caseALoopToCommand(ALoopToCommand node);
+    void caseALoopCommand(ALoopCommand node);
+    void caseALoopIncCommand(ALoopIncCommand node);
+    void caseAEachCommand(AEachCommand node);
+    void caseADefCommand(ADefCommand node);
+    void caseACallCommand(ACallCommand node);
+    void caseAIfCommand(AIfCommand node);
+    void caseAAltCommand(AAltCommand node);
+    void caseAIncludeCommand(AIncludeCommand node);
+    void caseAHardIncludeCommand(AHardIncludeCommand node);
+    void caseALincludeCommand(ALincludeCommand node);
+    void caseAHardLincludeCommand(AHardLincludeCommand node);
+    void caseAContentTypeCommand(AContentTypeCommand node);
+    void caseAInlineCommand(AInlineCommand node);
+    void caseANoopCommand(ANoopCommand node);
+    void caseACsOpenPosition(ACsOpenPosition node);
+    void caseAStringExpression(AStringExpression node);
+    void caseANumericExpression(ANumericExpression node);
+    void caseADecimalExpression(ADecimalExpression node);
+    void caseAHexExpression(AHexExpression node);
+    void caseAVariableExpression(AVariableExpression node);
+    void caseAFunctionExpression(AFunctionExpression node);
+    void caseASequenceExpression(ASequenceExpression node);
+    void caseANegativeExpression(ANegativeExpression node);
+    void caseANotExpression(ANotExpression node);
+    void caseAExistsExpression(AExistsExpression node);
+    void caseACommaExpression(ACommaExpression node);
+    void caseAEqExpression(AEqExpression node);
+    void caseANumericEqExpression(ANumericEqExpression node);
+    void caseANeExpression(ANeExpression node);
+    void caseANumericNeExpression(ANumericNeExpression node);
+    void caseALtExpression(ALtExpression node);
+    void caseAGtExpression(AGtExpression node);
+    void caseALteExpression(ALteExpression node);
+    void caseAGteExpression(AGteExpression node);
+    void caseAAndExpression(AAndExpression node);
+    void caseAOrExpression(AOrExpression node);
+    void caseAAddExpression(AAddExpression node);
+    void caseANumericAddExpression(ANumericAddExpression node);
+    void caseASubtractExpression(ASubtractExpression node);
+    void caseAMultiplyExpression(AMultiplyExpression node);
+    void caseADivideExpression(ADivideExpression node);
+    void caseAModuloExpression(AModuloExpression node);
+    void caseANoopExpression(ANoopExpression node);
+    void caseANameVariable(ANameVariable node);
+    void caseADecNumberVariable(ADecNumberVariable node);
+    void caseAHexNumberVariable(AHexNumberVariable node);
+    void caseADescendVariable(ADescendVariable node);
+    void caseAExpandVariable(AExpandVariable node);
+
+    void caseTData(TData node);
+    void caseTComment(TComment node);
+    void caseTVar(TVar node);
+    void caseTLvar(TLvar node);
+    void caseTEvar(TEvar node);
+    void caseTUvar(TUvar node);
+    void caseTSet(TSet node);
+    void caseTIf(TIf node);
+    void caseTElseIf(TElseIf node);
+    void caseTElse(TElse node);
+    void caseTWith(TWith node);
+    void caseTEscape(TEscape node);
+    void caseTAutoescape(TAutoescape node);
+    void caseTLoop(TLoop node);
+    void caseTEach(TEach node);
+    void caseTAlt(TAlt node);
+    void caseTName(TName node);
+    void caseTDef(TDef node);
+    void caseTCall(TCall node);
+    void caseTInclude(TInclude node);
+    void caseTLinclude(TLinclude node);
+    void caseTContentType(TContentType node);
+    void caseTInline(TInline node);
+    void caseTComma(TComma node);
+    void caseTBang(TBang node);
+    void caseTAssignment(TAssignment node);
+    void caseTEq(TEq node);
+    void caseTNe(TNe node);
+    void caseTLt(TLt node);
+    void caseTGt(TGt node);
+    void caseTLte(TLte node);
+    void caseTGte(TGte node);
+    void caseTAnd(TAnd node);
+    void caseTOr(TOr node);
+    void caseTString(TString node);
+    void caseTHash(THash node);
+    void caseTPlus(TPlus node);
+    void caseTMinus(TMinus node);
+    void caseTStar(TStar node);
+    void caseTPercent(TPercent node);
+    void caseTBracketOpen(TBracketOpen node);
+    void caseTBracketClose(TBracketClose node);
+    void caseTParenOpen(TParenOpen node);
+    void caseTParenClose(TParenClose node);
+    void caseTDot(TDot node);
+    void caseTDollar(TDollar node);
+    void caseTQuestion(TQuestion node);
+    void caseTDecNumber(TDecNumber node);
+    void caseTHexNumber(THexNumber node);
+    void caseTWord(TWord node);
+    void caseTArgWhitespace(TArgWhitespace node);
+    void caseTSlash(TSlash node);
+    void caseTCsOpen(TCsOpen node);
+    void caseTCommentStart(TCommentStart node);
+    void caseTCommandDelimiter(TCommandDelimiter node);
+    void caseTHardDelimiter(THardDelimiter node);
+    void caseTCsClose(TCsClose node);
+    void caseEOF(EOF node);
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/analysis/AnalysisAdapter.java b/src/com/google/clearsilver/jsilver/syntax/analysis/AnalysisAdapter.java
new file mode 100644
index 0000000..c583100
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/analysis/AnalysisAdapter.java
@@ -0,0 +1,671 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.analysis;
+
+import java.util.*;
+import com.google.clearsilver.jsilver.syntax.node.*;
+
+public class AnalysisAdapter implements Analysis
+{
+    private Hashtable<Node,Object> in;
+    private Hashtable<Node,Object> out;
+
+    public Object getIn(Node node)
+    {
+        if(this.in == null)
+        {
+            return null;
+        }
+
+        return this.in.get(node);
+    }
+
+    public void setIn(Node node, Object o)
+    {
+        if(this.in == null)
+        {
+            this.in = new Hashtable<Node,Object>(1);
+        }
+
+        if(o != null)
+        {
+            this.in.put(node, o);
+        }
+        else
+        {
+            this.in.remove(node);
+        }
+    }
+
+    public Object getOut(Node node)
+    {
+        if(this.out == null)
+        {
+            return null;
+        }
+
+        return this.out.get(node);
+    }
+
+    public void setOut(Node node, Object o)
+    {
+        if(this.out == null)
+        {
+            this.out = new Hashtable<Node,Object>(1);
+        }
+
+        if(o != null)
+        {
+            this.out.put(node, o);
+        }
+        else
+        {
+            this.out.remove(node);
+        }
+    }
+
+    public void caseStart(Start node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAMultipleCommand(AMultipleCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseACommentCommand(ACommentCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseADataCommand(ADataCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAVarCommand(AVarCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseALvarCommand(ALvarCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAEvarCommand(AEvarCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAUvarCommand(AUvarCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseASetCommand(ASetCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseANameCommand(ANameCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAEscapeCommand(AEscapeCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAAutoescapeCommand(AAutoescapeCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAWithCommand(AWithCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseALoopToCommand(ALoopToCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseALoopCommand(ALoopCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseALoopIncCommand(ALoopIncCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAEachCommand(AEachCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseADefCommand(ADefCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseACallCommand(ACallCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAIfCommand(AIfCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAAltCommand(AAltCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAIncludeCommand(AIncludeCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAHardIncludeCommand(AHardIncludeCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseALincludeCommand(ALincludeCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAHardLincludeCommand(AHardLincludeCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAContentTypeCommand(AContentTypeCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAInlineCommand(AInlineCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseANoopCommand(ANoopCommand node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseACsOpenPosition(ACsOpenPosition node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAStringExpression(AStringExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseANumericExpression(ANumericExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseADecimalExpression(ADecimalExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAHexExpression(AHexExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAVariableExpression(AVariableExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAFunctionExpression(AFunctionExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseASequenceExpression(ASequenceExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseANegativeExpression(ANegativeExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseANotExpression(ANotExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAExistsExpression(AExistsExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseACommaExpression(ACommaExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAEqExpression(AEqExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseANumericEqExpression(ANumericEqExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseANeExpression(ANeExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseANumericNeExpression(ANumericNeExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseALtExpression(ALtExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAGtExpression(AGtExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseALteExpression(ALteExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAGteExpression(AGteExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAAndExpression(AAndExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAOrExpression(AOrExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAAddExpression(AAddExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseANumericAddExpression(ANumericAddExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseASubtractExpression(ASubtractExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAMultiplyExpression(AMultiplyExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseADivideExpression(ADivideExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAModuloExpression(AModuloExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseANoopExpression(ANoopExpression node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseANameVariable(ANameVariable node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseADecNumberVariable(ADecNumberVariable node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAHexNumberVariable(AHexNumberVariable node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseADescendVariable(ADescendVariable node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseAExpandVariable(AExpandVariable node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTData(TData node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTComment(TComment node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTVar(TVar node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTLvar(TLvar node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTEvar(TEvar node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTUvar(TUvar node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTSet(TSet node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTIf(TIf node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTElseIf(TElseIf node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTElse(TElse node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTWith(TWith node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTEscape(TEscape node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTAutoescape(TAutoescape node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTLoop(TLoop node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTEach(TEach node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTAlt(TAlt node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTName(TName node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTDef(TDef node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTCall(TCall node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTInclude(TInclude node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTLinclude(TLinclude node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTContentType(TContentType node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTInline(TInline node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTComma(TComma node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTBang(TBang node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTAssignment(TAssignment node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTEq(TEq node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTNe(TNe node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTLt(TLt node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTGt(TGt node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTLte(TLte node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTGte(TGte node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTAnd(TAnd node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTOr(TOr node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTString(TString node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTHash(THash node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTPlus(TPlus node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTMinus(TMinus node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTStar(TStar node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTPercent(TPercent node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTBracketOpen(TBracketOpen node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTBracketClose(TBracketClose node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTParenOpen(TParenOpen node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTParenClose(TParenClose node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTDot(TDot node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTDollar(TDollar node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTQuestion(TQuestion node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTDecNumber(TDecNumber node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTHexNumber(THexNumber node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTWord(TWord node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTArgWhitespace(TArgWhitespace node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTSlash(TSlash node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTCsOpen(TCsOpen node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTCommentStart(TCommentStart node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTCommandDelimiter(TCommandDelimiter node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTHardDelimiter(THardDelimiter node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseTCsClose(TCsClose node)
+    {
+        defaultCase(node);
+    }
+
+    public void caseEOF(EOF node)
+    {
+        defaultCase(node);
+    }
+
+    public void defaultCase(@SuppressWarnings("unused") Node node)
+    {
+        // do nothing
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/analysis/DepthFirstAdapter.java b/src/com/google/clearsilver/jsilver/syntax/analysis/DepthFirstAdapter.java
new file mode 100644
index 0000000..375b162
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/analysis/DepthFirstAdapter.java
@@ -0,0 +1,1596 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.analysis;
+
+import java.util.*;
+import com.google.clearsilver.jsilver.syntax.node.*;
+
+public class DepthFirstAdapter extends AnalysisAdapter
+{
+    public void inStart(Start node)
+    {
+        defaultIn(node);
+    }
+
+    public void outStart(Start node)
+    {
+        defaultOut(node);
+    }
+
+    public void defaultIn(@SuppressWarnings("unused") Node node)
+    {
+        // Do nothing
+    }
+
+    public void defaultOut(@SuppressWarnings("unused") Node node)
+    {
+        // Do nothing
+    }
+
+    @Override
+    public void caseStart(Start node)
+    {
+        inStart(node);
+        node.getPCommand().apply(this);
+        node.getEOF().apply(this);
+        outStart(node);
+    }
+
+    public void inAMultipleCommand(AMultipleCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAMultipleCommand(AMultipleCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAMultipleCommand(AMultipleCommand node)
+    {
+        inAMultipleCommand(node);
+        {
+            List<PCommand> copy = new ArrayList<PCommand>(node.getCommand());
+            for(PCommand e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        outAMultipleCommand(node);
+    }
+
+    public void inACommentCommand(ACommentCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outACommentCommand(ACommentCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseACommentCommand(ACommentCommand node)
+    {
+        inACommentCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getComment() != null)
+        {
+            node.getComment().apply(this);
+        }
+        outACommentCommand(node);
+    }
+
+    public void inADataCommand(ADataCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADataCommand(ADataCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADataCommand(ADataCommand node)
+    {
+        inADataCommand(node);
+        if(node.getData() != null)
+        {
+            node.getData().apply(this);
+        }
+        outADataCommand(node);
+    }
+
+    public void inAVarCommand(AVarCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAVarCommand(AVarCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAVarCommand(AVarCommand node)
+    {
+        inAVarCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outAVarCommand(node);
+    }
+
+    public void inALvarCommand(ALvarCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALvarCommand(ALvarCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALvarCommand(ALvarCommand node)
+    {
+        inALvarCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outALvarCommand(node);
+    }
+
+    public void inAEvarCommand(AEvarCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAEvarCommand(AEvarCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAEvarCommand(AEvarCommand node)
+    {
+        inAEvarCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outAEvarCommand(node);
+    }
+
+    public void inAUvarCommand(AUvarCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAUvarCommand(AUvarCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAUvarCommand(AUvarCommand node)
+    {
+        inAUvarCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outAUvarCommand(node);
+    }
+
+    public void inASetCommand(ASetCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outASetCommand(ASetCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseASetCommand(ASetCommand node)
+    {
+        inASetCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outASetCommand(node);
+    }
+
+    public void inANameCommand(ANameCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANameCommand(ANameCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANameCommand(ANameCommand node)
+    {
+        inANameCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        outANameCommand(node);
+    }
+
+    public void inAEscapeCommand(AEscapeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAEscapeCommand(AEscapeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAEscapeCommand(AEscapeCommand node)
+    {
+        inAEscapeCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        outAEscapeCommand(node);
+    }
+
+    public void inAAutoescapeCommand(AAutoescapeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAAutoescapeCommand(AAutoescapeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAAutoescapeCommand(AAutoescapeCommand node)
+    {
+        inAAutoescapeCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        outAAutoescapeCommand(node);
+    }
+
+    public void inAWithCommand(AWithCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAWithCommand(AWithCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAWithCommand(AWithCommand node)
+    {
+        inAWithCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        outAWithCommand(node);
+    }
+
+    public void inALoopToCommand(ALoopToCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALoopToCommand(ALoopToCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALoopToCommand(ALoopToCommand node)
+    {
+        inALoopToCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        outALoopToCommand(node);
+    }
+
+    public void inALoopCommand(ALoopCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALoopCommand(ALoopCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALoopCommand(ALoopCommand node)
+    {
+        inALoopCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getStart() != null)
+        {
+            node.getStart().apply(this);
+        }
+        if(node.getEnd() != null)
+        {
+            node.getEnd().apply(this);
+        }
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        outALoopCommand(node);
+    }
+
+    public void inALoopIncCommand(ALoopIncCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALoopIncCommand(ALoopIncCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALoopIncCommand(ALoopIncCommand node)
+    {
+        inALoopIncCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getStart() != null)
+        {
+            node.getStart().apply(this);
+        }
+        if(node.getEnd() != null)
+        {
+            node.getEnd().apply(this);
+        }
+        if(node.getIncrement() != null)
+        {
+            node.getIncrement().apply(this);
+        }
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        outALoopIncCommand(node);
+    }
+
+    public void inAEachCommand(AEachCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAEachCommand(AEachCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAEachCommand(AEachCommand node)
+    {
+        inAEachCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        outAEachCommand(node);
+    }
+
+    public void inADefCommand(ADefCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADefCommand(ADefCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADefCommand(ADefCommand node)
+    {
+        inADefCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        {
+            List<TWord> copy = new ArrayList<TWord>(node.getMacro());
+            for(TWord e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        {
+            List<PVariable> copy = new ArrayList<PVariable>(node.getArguments());
+            for(PVariable e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        outADefCommand(node);
+    }
+
+    public void inACallCommand(ACallCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outACallCommand(ACallCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseACallCommand(ACallCommand node)
+    {
+        inACallCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        {
+            List<TWord> copy = new ArrayList<TWord>(node.getMacro());
+            for(TWord e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        {
+            List<PExpression> copy = new ArrayList<PExpression>(node.getArguments());
+            for(PExpression e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        outACallCommand(node);
+    }
+
+    public void inAIfCommand(AIfCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAIfCommand(AIfCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAIfCommand(AIfCommand node)
+    {
+        inAIfCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getBlock() != null)
+        {
+            node.getBlock().apply(this);
+        }
+        if(node.getOtherwise() != null)
+        {
+            node.getOtherwise().apply(this);
+        }
+        outAIfCommand(node);
+    }
+
+    public void inAAltCommand(AAltCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAAltCommand(AAltCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAAltCommand(AAltCommand node)
+    {
+        inAAltCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        outAAltCommand(node);
+    }
+
+    public void inAIncludeCommand(AIncludeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAIncludeCommand(AIncludeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAIncludeCommand(AIncludeCommand node)
+    {
+        inAIncludeCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outAIncludeCommand(node);
+    }
+
+    public void inAHardIncludeCommand(AHardIncludeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAHardIncludeCommand(AHardIncludeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAHardIncludeCommand(AHardIncludeCommand node)
+    {
+        inAHardIncludeCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outAHardIncludeCommand(node);
+    }
+
+    public void inALincludeCommand(ALincludeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALincludeCommand(ALincludeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALincludeCommand(ALincludeCommand node)
+    {
+        inALincludeCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outALincludeCommand(node);
+    }
+
+    public void inAHardLincludeCommand(AHardLincludeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAHardLincludeCommand(AHardLincludeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAHardLincludeCommand(AHardLincludeCommand node)
+    {
+        inAHardLincludeCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outAHardLincludeCommand(node);
+    }
+
+    public void inAContentTypeCommand(AContentTypeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAContentTypeCommand(AContentTypeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAContentTypeCommand(AContentTypeCommand node)
+    {
+        inAContentTypeCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getString() != null)
+        {
+            node.getString().apply(this);
+        }
+        outAContentTypeCommand(node);
+    }
+
+    public void inAInlineCommand(AInlineCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAInlineCommand(AInlineCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAInlineCommand(AInlineCommand node)
+    {
+        inAInlineCommand(node);
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        outAInlineCommand(node);
+    }
+
+    public void inANoopCommand(ANoopCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANoopCommand(ANoopCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANoopCommand(ANoopCommand node)
+    {
+        inANoopCommand(node);
+        outANoopCommand(node);
+    }
+
+    public void inACsOpenPosition(ACsOpenPosition node)
+    {
+        defaultIn(node);
+    }
+
+    public void outACsOpenPosition(ACsOpenPosition node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseACsOpenPosition(ACsOpenPosition node)
+    {
+        inACsOpenPosition(node);
+        if(node.getCsOpen() != null)
+        {
+            node.getCsOpen().apply(this);
+        }
+        outACsOpenPosition(node);
+    }
+
+    public void inAStringExpression(AStringExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAStringExpression(AStringExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAStringExpression(AStringExpression node)
+    {
+        inAStringExpression(node);
+        if(node.getValue() != null)
+        {
+            node.getValue().apply(this);
+        }
+        outAStringExpression(node);
+    }
+
+    public void inANumericExpression(ANumericExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANumericExpression(ANumericExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANumericExpression(ANumericExpression node)
+    {
+        inANumericExpression(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outANumericExpression(node);
+    }
+
+    public void inADecimalExpression(ADecimalExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADecimalExpression(ADecimalExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADecimalExpression(ADecimalExpression node)
+    {
+        inADecimalExpression(node);
+        if(node.getValue() != null)
+        {
+            node.getValue().apply(this);
+        }
+        outADecimalExpression(node);
+    }
+
+    public void inAHexExpression(AHexExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAHexExpression(AHexExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAHexExpression(AHexExpression node)
+    {
+        inAHexExpression(node);
+        if(node.getValue() != null)
+        {
+            node.getValue().apply(this);
+        }
+        outAHexExpression(node);
+    }
+
+    public void inAVariableExpression(AVariableExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAVariableExpression(AVariableExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAVariableExpression(AVariableExpression node)
+    {
+        inAVariableExpression(node);
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        outAVariableExpression(node);
+    }
+
+    public void inAFunctionExpression(AFunctionExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAFunctionExpression(AFunctionExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAFunctionExpression(AFunctionExpression node)
+    {
+        inAFunctionExpression(node);
+        if(node.getName() != null)
+        {
+            node.getName().apply(this);
+        }
+        {
+            List<PExpression> copy = new ArrayList<PExpression>(node.getArgs());
+            for(PExpression e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        outAFunctionExpression(node);
+    }
+
+    public void inASequenceExpression(ASequenceExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outASequenceExpression(ASequenceExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseASequenceExpression(ASequenceExpression node)
+    {
+        inASequenceExpression(node);
+        {
+            List<PExpression> copy = new ArrayList<PExpression>(node.getArgs());
+            for(PExpression e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        outASequenceExpression(node);
+    }
+
+    public void inANegativeExpression(ANegativeExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANegativeExpression(ANegativeExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANegativeExpression(ANegativeExpression node)
+    {
+        inANegativeExpression(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outANegativeExpression(node);
+    }
+
+    public void inANotExpression(ANotExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANotExpression(ANotExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANotExpression(ANotExpression node)
+    {
+        inANotExpression(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outANotExpression(node);
+    }
+
+    public void inAExistsExpression(AExistsExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAExistsExpression(AExistsExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAExistsExpression(AExistsExpression node)
+    {
+        inAExistsExpression(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outAExistsExpression(node);
+    }
+
+    public void inACommaExpression(ACommaExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outACommaExpression(ACommaExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseACommaExpression(ACommaExpression node)
+    {
+        inACommaExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outACommaExpression(node);
+    }
+
+    public void inAEqExpression(AEqExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAEqExpression(AEqExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAEqExpression(AEqExpression node)
+    {
+        inAEqExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outAEqExpression(node);
+    }
+
+    public void inANumericEqExpression(ANumericEqExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANumericEqExpression(ANumericEqExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANumericEqExpression(ANumericEqExpression node)
+    {
+        inANumericEqExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outANumericEqExpression(node);
+    }
+
+    public void inANeExpression(ANeExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANeExpression(ANeExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANeExpression(ANeExpression node)
+    {
+        inANeExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outANeExpression(node);
+    }
+
+    public void inANumericNeExpression(ANumericNeExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANumericNeExpression(ANumericNeExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANumericNeExpression(ANumericNeExpression node)
+    {
+        inANumericNeExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outANumericNeExpression(node);
+    }
+
+    public void inALtExpression(ALtExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALtExpression(ALtExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALtExpression(ALtExpression node)
+    {
+        inALtExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outALtExpression(node);
+    }
+
+    public void inAGtExpression(AGtExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAGtExpression(AGtExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAGtExpression(AGtExpression node)
+    {
+        inAGtExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outAGtExpression(node);
+    }
+
+    public void inALteExpression(ALteExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALteExpression(ALteExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALteExpression(ALteExpression node)
+    {
+        inALteExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outALteExpression(node);
+    }
+
+    public void inAGteExpression(AGteExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAGteExpression(AGteExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAGteExpression(AGteExpression node)
+    {
+        inAGteExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outAGteExpression(node);
+    }
+
+    public void inAAndExpression(AAndExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAAndExpression(AAndExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAAndExpression(AAndExpression node)
+    {
+        inAAndExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outAAndExpression(node);
+    }
+
+    public void inAOrExpression(AOrExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAOrExpression(AOrExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAOrExpression(AOrExpression node)
+    {
+        inAOrExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outAOrExpression(node);
+    }
+
+    public void inAAddExpression(AAddExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAAddExpression(AAddExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAAddExpression(AAddExpression node)
+    {
+        inAAddExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outAAddExpression(node);
+    }
+
+    public void inANumericAddExpression(ANumericAddExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANumericAddExpression(ANumericAddExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANumericAddExpression(ANumericAddExpression node)
+    {
+        inANumericAddExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outANumericAddExpression(node);
+    }
+
+    public void inASubtractExpression(ASubtractExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outASubtractExpression(ASubtractExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseASubtractExpression(ASubtractExpression node)
+    {
+        inASubtractExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outASubtractExpression(node);
+    }
+
+    public void inAMultiplyExpression(AMultiplyExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAMultiplyExpression(AMultiplyExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAMultiplyExpression(AMultiplyExpression node)
+    {
+        inAMultiplyExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outAMultiplyExpression(node);
+    }
+
+    public void inADivideExpression(ADivideExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADivideExpression(ADivideExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADivideExpression(ADivideExpression node)
+    {
+        inADivideExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outADivideExpression(node);
+    }
+
+    public void inAModuloExpression(AModuloExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAModuloExpression(AModuloExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAModuloExpression(AModuloExpression node)
+    {
+        inAModuloExpression(node);
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        outAModuloExpression(node);
+    }
+
+    public void inANoopExpression(ANoopExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANoopExpression(ANoopExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANoopExpression(ANoopExpression node)
+    {
+        inANoopExpression(node);
+        outANoopExpression(node);
+    }
+
+    public void inANameVariable(ANameVariable node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANameVariable(ANameVariable node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANameVariable(ANameVariable node)
+    {
+        inANameVariable(node);
+        if(node.getWord() != null)
+        {
+            node.getWord().apply(this);
+        }
+        outANameVariable(node);
+    }
+
+    public void inADecNumberVariable(ADecNumberVariable node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADecNumberVariable(ADecNumberVariable node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADecNumberVariable(ADecNumberVariable node)
+    {
+        inADecNumberVariable(node);
+        if(node.getDecNumber() != null)
+        {
+            node.getDecNumber().apply(this);
+        }
+        outADecNumberVariable(node);
+    }
+
+    public void inAHexNumberVariable(AHexNumberVariable node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAHexNumberVariable(AHexNumberVariable node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAHexNumberVariable(AHexNumberVariable node)
+    {
+        inAHexNumberVariable(node);
+        if(node.getHexNumber() != null)
+        {
+            node.getHexNumber().apply(this);
+        }
+        outAHexNumberVariable(node);
+    }
+
+    public void inADescendVariable(ADescendVariable node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADescendVariable(ADescendVariable node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADescendVariable(ADescendVariable node)
+    {
+        inADescendVariable(node);
+        if(node.getParent() != null)
+        {
+            node.getParent().apply(this);
+        }
+        if(node.getChild() != null)
+        {
+            node.getChild().apply(this);
+        }
+        outADescendVariable(node);
+    }
+
+    public void inAExpandVariable(AExpandVariable node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAExpandVariable(AExpandVariable node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAExpandVariable(AExpandVariable node)
+    {
+        inAExpandVariable(node);
+        if(node.getParent() != null)
+        {
+            node.getParent().apply(this);
+        }
+        if(node.getChild() != null)
+        {
+            node.getChild().apply(this);
+        }
+        outAExpandVariable(node);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/analysis/ReversedDepthFirstAdapter.java b/src/com/google/clearsilver/jsilver/syntax/analysis/ReversedDepthFirstAdapter.java
new file mode 100644
index 0000000..bfca2b7
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/analysis/ReversedDepthFirstAdapter.java
@@ -0,0 +1,1603 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.analysis;
+
+import java.util.*;
+import com.google.clearsilver.jsilver.syntax.node.*;
+
+public class ReversedDepthFirstAdapter extends AnalysisAdapter
+{
+    public void inStart(Start node)
+    {
+        defaultIn(node);
+    }
+
+    public void outStart(Start node)
+    {
+        defaultOut(node);
+    }
+
+    public void defaultIn(@SuppressWarnings("unused") Node node)
+    {
+        // Do nothing
+    }
+
+    public void defaultOut(@SuppressWarnings("unused") Node node)
+    {
+        // Do nothing
+    }
+
+    @Override
+    public void caseStart(Start node)
+    {
+        inStart(node);
+        node.getEOF().apply(this);
+        node.getPCommand().apply(this);
+        outStart(node);
+    }
+
+    public void inAMultipleCommand(AMultipleCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAMultipleCommand(AMultipleCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAMultipleCommand(AMultipleCommand node)
+    {
+        inAMultipleCommand(node);
+        {
+            List<PCommand> copy = new ArrayList<PCommand>(node.getCommand());
+            Collections.reverse(copy);
+            for(PCommand e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        outAMultipleCommand(node);
+    }
+
+    public void inACommentCommand(ACommentCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outACommentCommand(ACommentCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseACommentCommand(ACommentCommand node)
+    {
+        inACommentCommand(node);
+        if(node.getComment() != null)
+        {
+            node.getComment().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outACommentCommand(node);
+    }
+
+    public void inADataCommand(ADataCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADataCommand(ADataCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADataCommand(ADataCommand node)
+    {
+        inADataCommand(node);
+        if(node.getData() != null)
+        {
+            node.getData().apply(this);
+        }
+        outADataCommand(node);
+    }
+
+    public void inAVarCommand(AVarCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAVarCommand(AVarCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAVarCommand(AVarCommand node)
+    {
+        inAVarCommand(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAVarCommand(node);
+    }
+
+    public void inALvarCommand(ALvarCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALvarCommand(ALvarCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALvarCommand(ALvarCommand node)
+    {
+        inALvarCommand(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outALvarCommand(node);
+    }
+
+    public void inAEvarCommand(AEvarCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAEvarCommand(AEvarCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAEvarCommand(AEvarCommand node)
+    {
+        inAEvarCommand(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAEvarCommand(node);
+    }
+
+    public void inAUvarCommand(AUvarCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAUvarCommand(AUvarCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAUvarCommand(AUvarCommand node)
+    {
+        inAUvarCommand(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAUvarCommand(node);
+    }
+
+    public void inASetCommand(ASetCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outASetCommand(ASetCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseASetCommand(ASetCommand node)
+    {
+        inASetCommand(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outASetCommand(node);
+    }
+
+    public void inANameCommand(ANameCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANameCommand(ANameCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANameCommand(ANameCommand node)
+    {
+        inANameCommand(node);
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outANameCommand(node);
+    }
+
+    public void inAEscapeCommand(AEscapeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAEscapeCommand(AEscapeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAEscapeCommand(AEscapeCommand node)
+    {
+        inAEscapeCommand(node);
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAEscapeCommand(node);
+    }
+
+    public void inAAutoescapeCommand(AAutoescapeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAAutoescapeCommand(AAutoescapeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAAutoescapeCommand(AAutoescapeCommand node)
+    {
+        inAAutoescapeCommand(node);
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAAutoescapeCommand(node);
+    }
+
+    public void inAWithCommand(AWithCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAWithCommand(AWithCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAWithCommand(AWithCommand node)
+    {
+        inAWithCommand(node);
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAWithCommand(node);
+    }
+
+    public void inALoopToCommand(ALoopToCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALoopToCommand(ALoopToCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALoopToCommand(ALoopToCommand node)
+    {
+        inALoopToCommand(node);
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outALoopToCommand(node);
+    }
+
+    public void inALoopCommand(ALoopCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALoopCommand(ALoopCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALoopCommand(ALoopCommand node)
+    {
+        inALoopCommand(node);
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        if(node.getEnd() != null)
+        {
+            node.getEnd().apply(this);
+        }
+        if(node.getStart() != null)
+        {
+            node.getStart().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outALoopCommand(node);
+    }
+
+    public void inALoopIncCommand(ALoopIncCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALoopIncCommand(ALoopIncCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALoopIncCommand(ALoopIncCommand node)
+    {
+        inALoopIncCommand(node);
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        if(node.getIncrement() != null)
+        {
+            node.getIncrement().apply(this);
+        }
+        if(node.getEnd() != null)
+        {
+            node.getEnd().apply(this);
+        }
+        if(node.getStart() != null)
+        {
+            node.getStart().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outALoopIncCommand(node);
+    }
+
+    public void inAEachCommand(AEachCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAEachCommand(AEachCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAEachCommand(AEachCommand node)
+    {
+        inAEachCommand(node);
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAEachCommand(node);
+    }
+
+    public void inADefCommand(ADefCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADefCommand(ADefCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADefCommand(ADefCommand node)
+    {
+        inADefCommand(node);
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        {
+            List<PVariable> copy = new ArrayList<PVariable>(node.getArguments());
+            Collections.reverse(copy);
+            for(PVariable e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        {
+            List<TWord> copy = new ArrayList<TWord>(node.getMacro());
+            Collections.reverse(copy);
+            for(TWord e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outADefCommand(node);
+    }
+
+    public void inACallCommand(ACallCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outACallCommand(ACallCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseACallCommand(ACallCommand node)
+    {
+        inACallCommand(node);
+        {
+            List<PExpression> copy = new ArrayList<PExpression>(node.getArguments());
+            Collections.reverse(copy);
+            for(PExpression e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        {
+            List<TWord> copy = new ArrayList<TWord>(node.getMacro());
+            Collections.reverse(copy);
+            for(TWord e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outACallCommand(node);
+    }
+
+    public void inAIfCommand(AIfCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAIfCommand(AIfCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAIfCommand(AIfCommand node)
+    {
+        inAIfCommand(node);
+        if(node.getOtherwise() != null)
+        {
+            node.getOtherwise().apply(this);
+        }
+        if(node.getBlock() != null)
+        {
+            node.getBlock().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAIfCommand(node);
+    }
+
+    public void inAAltCommand(AAltCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAAltCommand(AAltCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAAltCommand(AAltCommand node)
+    {
+        inAAltCommand(node);
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAAltCommand(node);
+    }
+
+    public void inAIncludeCommand(AIncludeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAIncludeCommand(AIncludeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAIncludeCommand(AIncludeCommand node)
+    {
+        inAIncludeCommand(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAIncludeCommand(node);
+    }
+
+    public void inAHardIncludeCommand(AHardIncludeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAHardIncludeCommand(AHardIncludeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAHardIncludeCommand(AHardIncludeCommand node)
+    {
+        inAHardIncludeCommand(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAHardIncludeCommand(node);
+    }
+
+    public void inALincludeCommand(ALincludeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALincludeCommand(ALincludeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALincludeCommand(ALincludeCommand node)
+    {
+        inALincludeCommand(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outALincludeCommand(node);
+    }
+
+    public void inAHardLincludeCommand(AHardLincludeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAHardLincludeCommand(AHardLincludeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAHardLincludeCommand(AHardLincludeCommand node)
+    {
+        inAHardLincludeCommand(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAHardLincludeCommand(node);
+    }
+
+    public void inAContentTypeCommand(AContentTypeCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAContentTypeCommand(AContentTypeCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAContentTypeCommand(AContentTypeCommand node)
+    {
+        inAContentTypeCommand(node);
+        if(node.getString() != null)
+        {
+            node.getString().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAContentTypeCommand(node);
+    }
+
+    public void inAInlineCommand(AInlineCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAInlineCommand(AInlineCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAInlineCommand(AInlineCommand node)
+    {
+        inAInlineCommand(node);
+        if(node.getCommand() != null)
+        {
+            node.getCommand().apply(this);
+        }
+        if(node.getPosition() != null)
+        {
+            node.getPosition().apply(this);
+        }
+        outAInlineCommand(node);
+    }
+
+    public void inANoopCommand(ANoopCommand node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANoopCommand(ANoopCommand node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANoopCommand(ANoopCommand node)
+    {
+        inANoopCommand(node);
+        outANoopCommand(node);
+    }
+
+    public void inACsOpenPosition(ACsOpenPosition node)
+    {
+        defaultIn(node);
+    }
+
+    public void outACsOpenPosition(ACsOpenPosition node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseACsOpenPosition(ACsOpenPosition node)
+    {
+        inACsOpenPosition(node);
+        if(node.getCsOpen() != null)
+        {
+            node.getCsOpen().apply(this);
+        }
+        outACsOpenPosition(node);
+    }
+
+    public void inAStringExpression(AStringExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAStringExpression(AStringExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAStringExpression(AStringExpression node)
+    {
+        inAStringExpression(node);
+        if(node.getValue() != null)
+        {
+            node.getValue().apply(this);
+        }
+        outAStringExpression(node);
+    }
+
+    public void inANumericExpression(ANumericExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANumericExpression(ANumericExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANumericExpression(ANumericExpression node)
+    {
+        inANumericExpression(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outANumericExpression(node);
+    }
+
+    public void inADecimalExpression(ADecimalExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADecimalExpression(ADecimalExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADecimalExpression(ADecimalExpression node)
+    {
+        inADecimalExpression(node);
+        if(node.getValue() != null)
+        {
+            node.getValue().apply(this);
+        }
+        outADecimalExpression(node);
+    }
+
+    public void inAHexExpression(AHexExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAHexExpression(AHexExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAHexExpression(AHexExpression node)
+    {
+        inAHexExpression(node);
+        if(node.getValue() != null)
+        {
+            node.getValue().apply(this);
+        }
+        outAHexExpression(node);
+    }
+
+    public void inAVariableExpression(AVariableExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAVariableExpression(AVariableExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAVariableExpression(AVariableExpression node)
+    {
+        inAVariableExpression(node);
+        if(node.getVariable() != null)
+        {
+            node.getVariable().apply(this);
+        }
+        outAVariableExpression(node);
+    }
+
+    public void inAFunctionExpression(AFunctionExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAFunctionExpression(AFunctionExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAFunctionExpression(AFunctionExpression node)
+    {
+        inAFunctionExpression(node);
+        {
+            List<PExpression> copy = new ArrayList<PExpression>(node.getArgs());
+            Collections.reverse(copy);
+            for(PExpression e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        if(node.getName() != null)
+        {
+            node.getName().apply(this);
+        }
+        outAFunctionExpression(node);
+    }
+
+    public void inASequenceExpression(ASequenceExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outASequenceExpression(ASequenceExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseASequenceExpression(ASequenceExpression node)
+    {
+        inASequenceExpression(node);
+        {
+            List<PExpression> copy = new ArrayList<PExpression>(node.getArgs());
+            Collections.reverse(copy);
+            for(PExpression e : copy)
+            {
+                e.apply(this);
+            }
+        }
+        outASequenceExpression(node);
+    }
+
+    public void inANegativeExpression(ANegativeExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANegativeExpression(ANegativeExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANegativeExpression(ANegativeExpression node)
+    {
+        inANegativeExpression(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outANegativeExpression(node);
+    }
+
+    public void inANotExpression(ANotExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANotExpression(ANotExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANotExpression(ANotExpression node)
+    {
+        inANotExpression(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outANotExpression(node);
+    }
+
+    public void inAExistsExpression(AExistsExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAExistsExpression(AExistsExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAExistsExpression(AExistsExpression node)
+    {
+        inAExistsExpression(node);
+        if(node.getExpression() != null)
+        {
+            node.getExpression().apply(this);
+        }
+        outAExistsExpression(node);
+    }
+
+    public void inACommaExpression(ACommaExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outACommaExpression(ACommaExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseACommaExpression(ACommaExpression node)
+    {
+        inACommaExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outACommaExpression(node);
+    }
+
+    public void inAEqExpression(AEqExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAEqExpression(AEqExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAEqExpression(AEqExpression node)
+    {
+        inAEqExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outAEqExpression(node);
+    }
+
+    public void inANumericEqExpression(ANumericEqExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANumericEqExpression(ANumericEqExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANumericEqExpression(ANumericEqExpression node)
+    {
+        inANumericEqExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outANumericEqExpression(node);
+    }
+
+    public void inANeExpression(ANeExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANeExpression(ANeExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANeExpression(ANeExpression node)
+    {
+        inANeExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outANeExpression(node);
+    }
+
+    public void inANumericNeExpression(ANumericNeExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANumericNeExpression(ANumericNeExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANumericNeExpression(ANumericNeExpression node)
+    {
+        inANumericNeExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outANumericNeExpression(node);
+    }
+
+    public void inALtExpression(ALtExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALtExpression(ALtExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALtExpression(ALtExpression node)
+    {
+        inALtExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outALtExpression(node);
+    }
+
+    public void inAGtExpression(AGtExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAGtExpression(AGtExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAGtExpression(AGtExpression node)
+    {
+        inAGtExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outAGtExpression(node);
+    }
+
+    public void inALteExpression(ALteExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outALteExpression(ALteExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseALteExpression(ALteExpression node)
+    {
+        inALteExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outALteExpression(node);
+    }
+
+    public void inAGteExpression(AGteExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAGteExpression(AGteExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAGteExpression(AGteExpression node)
+    {
+        inAGteExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outAGteExpression(node);
+    }
+
+    public void inAAndExpression(AAndExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAAndExpression(AAndExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAAndExpression(AAndExpression node)
+    {
+        inAAndExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outAAndExpression(node);
+    }
+
+    public void inAOrExpression(AOrExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAOrExpression(AOrExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAOrExpression(AOrExpression node)
+    {
+        inAOrExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outAOrExpression(node);
+    }
+
+    public void inAAddExpression(AAddExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAAddExpression(AAddExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAAddExpression(AAddExpression node)
+    {
+        inAAddExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outAAddExpression(node);
+    }
+
+    public void inANumericAddExpression(ANumericAddExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANumericAddExpression(ANumericAddExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANumericAddExpression(ANumericAddExpression node)
+    {
+        inANumericAddExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outANumericAddExpression(node);
+    }
+
+    public void inASubtractExpression(ASubtractExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outASubtractExpression(ASubtractExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseASubtractExpression(ASubtractExpression node)
+    {
+        inASubtractExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outASubtractExpression(node);
+    }
+
+    public void inAMultiplyExpression(AMultiplyExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAMultiplyExpression(AMultiplyExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAMultiplyExpression(AMultiplyExpression node)
+    {
+        inAMultiplyExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outAMultiplyExpression(node);
+    }
+
+    public void inADivideExpression(ADivideExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADivideExpression(ADivideExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADivideExpression(ADivideExpression node)
+    {
+        inADivideExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outADivideExpression(node);
+    }
+
+    public void inAModuloExpression(AModuloExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAModuloExpression(AModuloExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAModuloExpression(AModuloExpression node)
+    {
+        inAModuloExpression(node);
+        if(node.getRight() != null)
+        {
+            node.getRight().apply(this);
+        }
+        if(node.getLeft() != null)
+        {
+            node.getLeft().apply(this);
+        }
+        outAModuloExpression(node);
+    }
+
+    public void inANoopExpression(ANoopExpression node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANoopExpression(ANoopExpression node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANoopExpression(ANoopExpression node)
+    {
+        inANoopExpression(node);
+        outANoopExpression(node);
+    }
+
+    public void inANameVariable(ANameVariable node)
+    {
+        defaultIn(node);
+    }
+
+    public void outANameVariable(ANameVariable node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseANameVariable(ANameVariable node)
+    {
+        inANameVariable(node);
+        if(node.getWord() != null)
+        {
+            node.getWord().apply(this);
+        }
+        outANameVariable(node);
+    }
+
+    public void inADecNumberVariable(ADecNumberVariable node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADecNumberVariable(ADecNumberVariable node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADecNumberVariable(ADecNumberVariable node)
+    {
+        inADecNumberVariable(node);
+        if(node.getDecNumber() != null)
+        {
+            node.getDecNumber().apply(this);
+        }
+        outADecNumberVariable(node);
+    }
+
+    public void inAHexNumberVariable(AHexNumberVariable node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAHexNumberVariable(AHexNumberVariable node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAHexNumberVariable(AHexNumberVariable node)
+    {
+        inAHexNumberVariable(node);
+        if(node.getHexNumber() != null)
+        {
+            node.getHexNumber().apply(this);
+        }
+        outAHexNumberVariable(node);
+    }
+
+    public void inADescendVariable(ADescendVariable node)
+    {
+        defaultIn(node);
+    }
+
+    public void outADescendVariable(ADescendVariable node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseADescendVariable(ADescendVariable node)
+    {
+        inADescendVariable(node);
+        if(node.getChild() != null)
+        {
+            node.getChild().apply(this);
+        }
+        if(node.getParent() != null)
+        {
+            node.getParent().apply(this);
+        }
+        outADescendVariable(node);
+    }
+
+    public void inAExpandVariable(AExpandVariable node)
+    {
+        defaultIn(node);
+    }
+
+    public void outAExpandVariable(AExpandVariable node)
+    {
+        defaultOut(node);
+    }
+
+    @Override
+    public void caseAExpandVariable(AExpandVariable node)
+    {
+        inAExpandVariable(node);
+        if(node.getChild() != null)
+        {
+            node.getChild().apply(this);
+        }
+        if(node.getParent() != null)
+        {
+            node.getParent().apply(this);
+        }
+        outAExpandVariable(node);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/jsilver.sablecc b/src/com/google/clearsilver/jsilver/syntax/jsilver.sablecc
new file mode 100644
index 0000000..780e6ac
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/jsilver.sablecc
@@ -0,0 +1,709 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.
+ */
+ 
+ /* This is the JSilver grammar fed into SableCC to generate the parser. */
+
+// Java package of generated code.
+Package
+  com.google.clearsilver.jsilver.syntax;
+
+/***** Stage 1: The Lexer
+ *
+ * The lexer breaks the inbound text stream in to a sequence of tokens.
+ *
+ * SableCC will generate a wrapper class for each type of token.
+ *
+ * Anything outside of <?cs and ?> will be returned as a TData token.
+ * Anything in between (including) <?cs and ?> will be broken down
+ * into a finer grained set of tokens.
+ *
+ * For example: "Hello <?cs var:person.name ?>!"
+ * Results in:
+ *  TData        "Hello "
+ *  TCsOpen      "<?cs"
+ *  TWhiteSpace  " "
+ *  TVar         "var"
+ *  TColon       ":"
+ *  TWord        "person.name"
+ *  TWhitspace   " "
+ *  TCsClose     "?>"
+ *  TData        "!"
+ */
+
+/* Constants to be used elsewhere. */
+Helpers
+
+  // These aren't actually tokens, but can be used by tokens. Avoids the
+  // need for magic numbers in the token definitions.
+  alphanumeric = (['a' .. 'z'] | ['A' .. 'Z'] | ['0' .. '9']);
+  alpha        = (['a' .. 'z'] | ['A' .. 'Z']);
+  all = [0 .. 0xFFFF];
+  tab = 9;
+  cr = 13;
+  lf = 10;
+  whitespace = (tab | cr | lf | ' ');
+
+/* States of the lexer. */
+States
+
+  content,     // Anything outside of <?cs and ?>.
+  command,     // ClearSilver command: "<?cs var:".
+  args,        // Args to command: "some.variable=3 ?>"
+  comment;     // Inside a <?cs # comment ?>.
+
+/* Tokens and state transitions. */
+Tokens
+
+  // In the 'content' state, treat everything as a chunk of data,
+  // up to the sequence '<?cs '.
+  // Note, because the lexer has to read 5 characters '<?cs ' before
+  // knowing the definite state, it needs to be supplied a
+  // PushbackReader() with a buffer of at least 5.
+  {content} data = (        [all - '<']
+                   | '<'    [all - '?']
+                   | '<?'   [all - 'c']
+                   | '<?c'  [all - 's']
+                   | '<?cs' [[[[all - ' '] - cr] - lf] - tab])+;
+
+  {comment} comment = (        [all - '?']
+                      | '?'    [all - '>'] )+; // All up to ?>
+
+  // In the 'clearsilver' state, break each keyword, operator
+  // and symbol into it's own token.
+  {command} var           = 'var';
+  {command} lvar          = 'lvar';
+  {command} evar          = 'evar';
+  {command} uvar          = 'uvar';
+  {command} set           = 'set';
+  {command} if            = 'if';
+  {command} else_if       = ('elif' | 'elseif');
+  {command} else          = 'else';
+  {command} with          = 'with';
+  {command} escape        = 'escape';
+  {command} autoescape    = 'autoescape';
+  {command} loop          = 'loop';
+  {command} each          = 'each';
+  {command} alt           = 'alt';
+  {command} name          = 'name';
+  {command} def           = 'def';
+  {command} call          = 'call';
+  {command} include       = 'include';
+  {command} linclude      = 'linclude';
+  {command} content_type  = 'content-type';
+  {command} inline        = 'inline';
+
+  {args} comma         = ',';
+  {args} bang          = '!';
+  {args} assignment    = '=';
+  {args} eq            = '==';
+  {args} ne            = '!=';
+  {args} lt            = '<';
+  {args} gt            = '>';
+  {args} lte           = '<=';
+  {args} gte           = '>=';
+  {args} and           = '&&';
+  {args} or            = '||';
+  {args} string        = ('"' [all - '"']* '"' | ''' [all - ''']* ''');
+  {args} hash          = '#';
+  {args} plus          = '+';
+  {args} minus         = '-';
+  {args} star          = '*';
+  {args} percent       = '%';
+  {args} bracket_open  = '[';
+  {args} bracket_close = ']';
+  {args} paren_open    = '(';
+  {args} paren_close   = ')';
+  {args} dot           = '.';
+  {args} dollar        = '$';
+  {args} question      = '?';
+  {args} dec_number    = (['0' .. '9'])+;
+  {args} hex_number    = ('0x' | '0X') (['0' .. '9'] | ['a' .. 'f'] | ['A' .. 'F'])+;
+  {args} word          = (alphanumeric | '_')+;
+  {args} arg_whitespace = whitespace+;
+
+  // Tokens that are valid in multiple states
+  {command, args} slash         = '/'; // means divide or end command.
+
+  // State transitions.
+  {content->command}                                  cs_open        = '<?cs' whitespace+;
+  {command->comment}                                  comment_start  = '#';
+  {command->args}                                     command_delimiter = ':' | whitespace;
+  {command->args}                                     hard_delimiter = '!' | whitespace;
+  {command->content, args->content, comment->content} cs_close       = whitespace* '?>';
+
+/***** Stage 2: The Parser
+ *
+ * Below is a BNF-like grammar of how the tokens are assembled.
+ * The generate parser will read the token stream and build a
+ * tree of nodes representing the structure.
+ *
+ * This is the Concrete Syntax Tree (CST).
+ *
+ * Though this provides access to the underlying syntax tree, the
+ * resulting tree would be quite tricky to work with from code as
+ * it's heavily loaded with syntax specifics and parser tricks...
+ *
+ * So, the CST also contains transformation rules ({->x}) to
+ * convert it into a much simpler Abstract Syntax Tree (AST),
+ * which is defined in stage 3.
+ */
+
+/* Tokens from the lexer that the parser doesn't care about. */
+Ignored Tokens
+
+  arg_whitespace;
+
+/* Concrete syntax tree. */
+Productions
+
+  // Overall template structure...
+
+  grammar {->command}
+    = commands
+              {->commands.command}
+    ;
+
+  commands {->command}
+    = {none}
+              {->New command.noop()}
+    | {one}   command
+              {->command.command}
+    | {many}  command [more]:command+
+              {->New command.multiple([command.command, more.command])}
+    ;
+
+  command {->command}
+
+    = {data}  data
+              // Anything outside of <?cs ?> tag
+              {->New command.data(data)}
+
+    | {comment} cs_open comment_start comment? cs_close
+              // <?cs # comment ?>
+              {->New command.comment(New position.cs_open(cs_open),comment)}
+
+    | {var}   cs_open var command_delimiter expression_list cs_close
+              // <?cs var:x ?>
+              {->New command.var(
+                   New position.cs_open(cs_open),
+                   New expression.sequence([expression_list.expression]))}
+
+    | {lvar}  cs_open lvar command_delimiter expression_list cs_close
+              // <?cs lvar:x ?>
+              {->New command.lvar(
+                   New position.cs_open(cs_open),
+                   New expression.sequence([expression_list.expression]))}
+
+    | {evar}  cs_open evar command_delimiter expression_list cs_close
+              // <?cs evar:x ?>
+              {->New command.evar(
+                   New position.cs_open(cs_open),
+                   New expression.sequence([expression_list.expression]))}
+
+    | {uvar}  cs_open uvar command_delimiter expression_list cs_close
+              // <?cs uvar:x ?>
+              {->New command.uvar(
+                   New position.cs_open(cs_open),
+                   New expression.sequence([expression_list.expression]))}
+
+    | {set}   cs_open set command_delimiter variable assignment expression cs_close
+              // <?cs set:x = y ?>
+              {->New command.set(
+                   New position.cs_open(cs_open),
+                   variable.variable,
+                   expression.expression)}
+
+    | {name}  cs_open name command_delimiter variable cs_close
+              // <?cs name:x ?>
+              {->New command.name(
+                   New position.cs_open(cs_open),
+                   variable.variable)}
+
+    | {escape} cs_open escape command_delimiter expression cs_close
+              commands
+              [i1]:cs_open slash [i3]:escape [i2]:cs_close
+              // <?cs escape:"html" ?>...<?cs /escape?>
+              {->New command.escape(
+                   New position.cs_open(cs_open),
+                   expression.expression,
+                   commands.command)}
+
+    | {autoescape} cs_open autoescape command_delimiter expression cs_close
+              commands
+              [i1]:cs_open slash [i3]:autoescape [i2]:cs_close
+              // <?cs autoescape:"html" ?>...<?cs /autoescape?>
+              {->New command.autoescape(
+                   New position.cs_open(cs_open),
+                   expression.expression,
+                   commands.command)}
+
+    | {with}  cs_open with command_delimiter variable assignment expression cs_close
+              commands
+              [i1]:cs_open slash [i3]:with [i2]:cs_close
+              // <?cs with:x=y ?>...<?cs /with?>
+              {->New command.with(
+                  New position.cs_open(cs_open),
+                  variable.variable,
+                  expression.expression,
+                  commands.command)}
+
+    | {loop_to}  cs_open loop command_delimiter variable assignment expression cs_close
+              commands
+              [i1]:cs_open slash [i3]:loop [i2]:cs_close
+              // <?cs loop:x=20 ?>...<?cs /loop ?>
+              {->New command.loop_to(
+                  New position.cs_open(cs_open),
+                  variable.variable,
+                  expression.expression,
+                  commands.command)}
+
+    | {loop}  cs_open loop command_delimiter variable assignment
+              [start]:expression comma [end]:expression cs_close
+              commands
+              [i1]:cs_open slash [i3]:loop [i2]:cs_close
+              // <?cs loop:x=1,20 ?>...<?cs /loop ?>
+              {->New command.loop(
+                  New position.cs_open(cs_open),
+                  variable.variable,
+                  start.expression,
+                  end.expression,
+                  commands.command)}
+
+    | {loop_inc} cs_open loop command_delimiter variable assignment
+              [start]:expression comma
+              [end]:expression [i3]:comma [increment]:expression cs_close
+              commands [i1]:cs_open slash [i4]:loop [i2]:cs_close
+              // <?cs loop:x=1,20,5 ?>...<?cs /loop ?>
+              {->New command.loop_inc(
+                  New position.cs_open(cs_open),
+                  variable.variable,
+                  start.expression,
+                  end.expression,
+                  increment.expression,
+                  commands.command)}
+
+    | {each}  cs_open each command_delimiter variable assignment expression cs_close
+              commands
+              [i1]:cs_open slash [i3]:each [i2]:cs_close
+              // <?cs each:x=some.thing ?>...<?cs /each ?>
+              {->New command.each(
+                  New position.cs_open(cs_open),
+                  variable.variable,
+                  expression.expression,
+                  commands.command)}
+
+    | {alt}   cs_open alt command_delimiter expression cs_close
+              commands
+              [i1]:cs_open slash [i3]:alt [i2]:cs_close
+              // <?cs alt:some.thing ?>...<?cs /alt ?>
+              {->New command.alt(
+                  New position.cs_open(cs_open),
+                  expression.expression,
+                  commands.command)}
+
+    | {def}   cs_open def command_delimiter multipart_word paren_open variable_list?
+              paren_close cs_close commands
+              [i1]:cs_open slash [i3]:def [i2]:cs_close
+              // <?cs def:some.macro(arg,arg) ?>...<?cs /def ?>
+              {->New command.def(
+                  New position.cs_open(cs_open),
+                  [multipart_word.word],
+                  [variable_list.variable],
+                  commands.command)}
+
+    | {call}  cs_open call command_delimiter multipart_word paren_open expression_list?
+              paren_close cs_close
+              // <?cs call:some.macro(arg,arg) ?>
+              {->New command.call(
+                  New position.cs_open(cs_open),
+                  [multipart_word.word],
+                  [expression_list.expression])}
+
+    | {if}    if_block
+              {->if_block.command}
+
+    | {include} cs_open include command_delimiter expression cs_close
+              // <?cs include:x ?>
+              {->New command.include(
+                  New position.cs_open(cs_open),
+                  expression.expression)}
+
+    | {hard_include} cs_open include hard_delimiter expression cs_close
+              // <?cs include!x ?>
+              {->New command.hard_include(
+                  New position.cs_open(cs_open),
+                  expression.expression)}
+
+    | {linclude} cs_open linclude command_delimiter expression cs_close
+              // <?cs linclude:x ?>
+              {->New command.linclude(
+                  New position.cs_open(cs_open),
+                  expression.expression)}
+
+    | {hard_linclude} cs_open linclude hard_delimiter expression cs_close
+              // <?cs linclude!x ?>
+              {->New command.hard_linclude(
+                  New position.cs_open(cs_open),
+                  expression.expression)}
+
+    | {content_type} cs_open content_type command_delimiter string cs_close
+              // <?cs content-type:"html" ?>
+              {->New command.content_type(
+                  New position.cs_open(cs_open),
+                  string)}
+
+    | {inline} cs_open inline cs_close
+              commands
+              [i1]:cs_open slash [i3]:inline [i2]:cs_close
+              // <?cs inline ?>...<?cs /inline?>
+              {->New command.inline(
+                   New position.cs_open(cs_open),
+                   commands.command)}
+
+    ;
+
+  multipart_word {->word*}
+    = {bit} word
+              {->[word]}
+    | {m} multipart_word dot word
+              {->[multipart_word.word, word]}
+    ;
+
+  variable_list {->variable*}
+    = {single} variable
+              {->[variable.variable]}
+    | {multiple} variable_list comma variable
+              {->[variable_list.variable, variable.variable]}
+    ;
+
+  expression_list {->expression*}
+    = {single} expression
+              {->[expression.expression]}
+    | {multiple} expression_list comma expression
+              {->[expression_list.expression, expression.expression]}
+    ;
+
+  // If/ElseIf/Else block...
+
+  if_block {->command}
+    =           cs_open if command_delimiter expression cs_close
+                commands
+                else_if_block
+                // <?cs if:x.y ?> (commands, then optional else_if_block)
+                {->New command.if(
+                    New position.cs_open(cs_open),
+                    expression.expression,
+                    commands.command,
+                    else_if_block.command)}
+    ;
+
+  // ElseIf statements get transformed into nested if/else blocks to simplify
+  // final AST.
+  else_if_block {->command}
+    = {present} cs_open else_if command_delimiter expression cs_close
+                commands
+                else_if_block
+                // <?cs elif:x.y ?> (recurses)
+                {->New command.if(
+                    New position.cs_open(cs_open),
+                    expression.expression,
+                    commands.command,
+                    else_if_block.command)}
+    | {missing} else_block
+                {->else_block.command}
+    ;
+
+  else_block {->command}
+    = {present} cs_open else cs_close
+                commands
+                end_if_block
+                // <?cs else ?> (followed by end_if_block)
+                {->commands.command}
+    | {skip}    end_if_block
+                {->New command.noop()}
+    ;
+
+  end_if_block
+    =           cs_open slash if cs_close
+                // <?cs /if ?>
+    ;
+
+  // Expression language...
+
+  // The multiple levels allow the parser to build a tree based on operator
+  // precedence. The higher the level in the tree, the lower the precedence.
+
+  expression {->expression}
+    = {or} [left]:expression or [right]:and_expression    // x.y || a.b
+              {->New expression.or(left.expression, right.expression)}
+    | {and_expression} [value]:and_expression             // x.y
+              {->value.expression}
+    ;
+
+  and_expression {->expression}
+    = {and}  [left]:and_expression and [right]:equality   // x.y && a.b
+              {->New expression.and(left.expression, right.expression)}
+    | {equality} [value]:equality                         // x.y
+              {->value.expression}
+    ;
+
+  equality {->expression}
+    = {eq}  [left]:equality eq [right]:comparison         // x.y == a.b
+              {->New expression.eq(left.expression, right.expression)}
+    | {ne}  [left]:equality ne [right]:comparison         // x.y != a.b
+              {->New expression.ne(left.expression, right.expression)}
+    | {comparison} [value]:comparison                  // x.y
+              {->value.expression}
+    ;
+
+  comparison {->expression}
+    = {lt}  [left]:comparison lt  [right]:add_subtract         // x.y < a.b
+              {->New expression.lt(left.expression, right.expression)}
+    | {gt}  [left]:comparison gt  [right]:add_subtract         // x.y > a.b
+              {->New expression.gt(left.expression, right.expression)}
+    | {lte} [left]:comparison lte [right]:add_subtract         // x.y <= a.b
+              {->New expression.lte(left.expression, right.expression)}
+    | {gte} [left]:comparison gte [right]:add_subtract         // x.y >= a.b
+              {->New expression.gte(left.expression, right.expression)}
+    | {add_subtract} [value]:add_subtract                      // x.y
+              {->value.expression}
+    ;
+
+  add_subtract {->expression}
+    = {add} [left]:add_subtract plus [right]:factor        // x.y + a.b
+              {->New expression.add(left.expression, right.expression)}
+    | {subtract} [left]:add_subtract minus [right]:factor  // x.y - a.b
+              {->New expression.subtract(left.expression, right.expression)}
+    | {factor} [value]:factor                              // x.y
+              {->value.expression}
+    ;
+
+  factor {->expression}
+    = {multiply} [left]:factor star [right]:value   // x.y * a.b
+              {->New expression.multiply(left.expression, right.expression)}
+    | {divide} [left]:factor slash [right]:value    // x.y / a.b
+              {->New expression.divide(left.expression, right.expression)}
+    | {modulo} [left]:factor percent [right]:value  // x.y % a.b
+              {->New expression.modulo(left.expression, right.expression)}
+    | {value} value                                 // x.y
+              {->value.expression}
+    ;
+
+  value {->expression}
+    = {variable} variable          // x.y
+              {->New expression.variable(variable.variable)}
+    | {string} string              // "hello"
+              {->New expression.string(string)}
+    | {number} number              // 123
+              {->number.expression}
+    | {forced_number} hash value   // #123 or #some.var
+              {->New expression.numeric(value.expression)}
+    | {not} bang value                              // !x.y
+              {->New expression.not(value.expression)}
+    | {exists} question value      // ?x.y
+              {->New expression.exists(value.expression)}
+    | {parens} paren_open expression_list paren_close        // (x.y, a.b, d.e)
+              {->New expression.sequence([expression_list.expression])}
+    | {function} [name]:variable paren_open
+                  expression_list? paren_close // a.b(x, y)
+              {->New expression.function(
+                  name.variable,[expression_list.expression])}
+    ;
+
+  variable {->variable}
+    = {name}   dollar? word
+              {->New variable.name(word)}
+    | {dec_number} dollar dec_number
+              {->New variable.dec_number(dec_number)}
+    | {hex_number} dollar hex_number
+              {->New variable.hex_number(hex_number)}
+    | {descend_name} variable dot word
+              {->New variable.descend(
+                  variable.variable, New variable.name(word))}
+    | {descend_dec_number} variable dot dec_number
+              {->New variable.descend(
+                  variable.variable, New variable.dec_number(dec_number))}
+    | {descend_hex_number} variable dot hex_number
+              {->New variable.descend(
+                  variable.variable, New variable.hex_number(hex_number))}
+    | {expand} variable bracket_open expression bracket_close
+              {->New variable.expand(
+                  variable.variable, expression.expression)}
+    ;
+
+  number {->expression}
+    = {unsigned} digits
+              {->digits.expression}
+    | {positive} plus digits
+              {->digits.expression}
+    | {negative} minus digits
+              {->New expression.negative(digits.expression)}
+    ;
+
+  digits {->expression}
+    = {decimal} dec_number
+              {->New expression.decimal(dec_number)}
+    | {hex}     hex_number
+              {->New expression.hex(hex_number)}
+    ;
+
+
+/***** Stage 3: The Abstract Syntax Tree
+ *
+ * This is the resulting model that will be generated by the parser.
+ *
+ * It is represented in Java by a strongly typed node tree (the
+ * classes are code generated by SableCC). These can be interrogated
+ * using getter methods, or processed by passing a visitor to the
+ * tree.
+ *
+ * The Abstract Syntax Tree definition below is the only thing
+ * the Java application need to worry about. All previous definitions
+ * in this file are taken care of by the generated parser.
+ *
+ * Example input:
+ *   Hello <?cs var:user.name ?>!
+ *   <?cs if:user.age >= 90 ?>
+ *   You're way old.
+ *   <?cs elif:user.age >= 21 ?>
+ *   You're about the right age.
+ *   <?cs else ?>
+ *   You're too young.
+ *   <?cs /if ?>
+ *   Seeya!
+ *
+ * Results in the tree:
+ *   AMultipleCommand (start)
+ *     ADataCommand (command)
+ *       TData (data) "Hello "
+ *     AVarCommand (command)
+ *       AVariableExpression (expression)
+ *         TWord "user.name" (name)
+ *     ADataCommand (command)
+ *       TData "!\n" (data)
+ *     AIfCommand (command)
+ *       AGteExpression (expression)
+ *         AVariableExpression (left)
+ *           TWord "user.age" (name)
+ *         ADecimalExpresion (right)
+ *           TDecNumber "90" (value)
+ *       ADataCommand (block)
+ *         TData (data) "\nYou're way old.\n"
+ *       AGteCommand (otherwise)
+ *         AEqExpression (expression)
+ *           AVariableExpression (left)
+ *             TWord "user.age" (name)
+ *           ADecimalExpresion (right)
+ *             TDecNumber "21" (value)
+ *         ADataCommand (block)
+ *           TData (data) "\nYou're about the right age.\n"
+ *         ADataCommand (otherwise)
+ *           TData (data) "\nYou're too young.\n"
+ *     ADataCommand (command)
+ *       TData (data) "\nSeeya!\n"
+ *
+ * Although not strictly necessary, tokens are prefixed with 'T.' in
+ * the grammar so they stand out from the rest of the other rules.
+ */
+Abstract Syntax Tree
+
+  command    = {multiple} command*             // Sequence of commands
+             | {comment}  position T.comment?  // Contents of <?cs # comment ?>
+             | {data}     T.data               // Any data outside of <?cs ?>
+             | {var}      position expression           // var:x statement
+             | {lvar}     position expression           // lvar:x statement
+             | {evar}     position expression           // evar:x statement
+             | {uvar}     position expression           // uvar:x statement
+             | {set}      position variable expression  // set:x=y statement
+             | {name}     position variable             // name:x statement
+             | {escape}   position expression  // escape:x statement
+                          command              // ... commands in context
+             | {autoescape}   position expression  // autoescape:x statement
+                          command              // ... commands in context
+             | {with}     position variable expression  // with:x=y statement
+                          command              // ... commands in context
+             | {loop_to}  position variable    // loop:x=10 statement
+                          expression           // ... value to end at
+                          command              // ... commands in loop
+             | {loop}     position variable    // loop:x=1,10 statement
+                          [start]:expression   // ... value to start at
+                          [end]:expression     // ... value to end at
+                          command              // ... commands in loop
+             | {loop_inc} position variable    // loop:x=1,10,2 statement
+                          [start]:expression   // ... value to start at
+                          [end]:expression     // ... value to end at
+                          [increment]:expression // . value to increment by
+                          command              // ... commands in loop
+             | {each}     position variable expression  // each:x=y statement
+                          command              // ... commands in loop
+             | {def}      position [macro]:T.word* // def:some_macro statement
+                          [arguments]:variable* // ... arguments
+                          command              // ... commands to execute
+             | {call}     position [macro]:T.word*  // call:some_macro statement
+                          [arguments]:expression* // ... arguments
+             | {if}       position expression  // if:x statement
+                          [block]:command      // ... commands if true
+                          [otherwise]:command  // ... commands if false
+             | {alt}      position expression command   // alt:x statement
+             | {include}  position expression           // include:x statement
+             | {hard_include} position expression       // include!x statement
+             | {linclude} position expression           // linclude:x statement
+             | {hard_linclude} position expression      // linclude!x statement
+             | {content_type}  position string          // content-type:x statement
+             | {inline}   position command              // inline commands
+             | {noop}                                   // No operation
+             ;
+
+  position   = {cs_open}  T.cs_open   // We retain the <?cs token in the AST
+             ;                        // as a convenient way to get the position
+                                      // of the start of the command.
+
+  expression = {string}      [value]:T.string                     // "hello"
+             | {numeric}     expression                           // #something
+             | {decimal}     [value]:T.dec_number                 // 123
+             | {hex}         [value]:T.hex_number                 // 0x1BF
+             | {variable}    variable                             // some.thing[1]
+             | {function}    [name]:variable [args]:expression*   // a.b(x, y)
+             | {sequence}    [args]:expression*                   // x, y, z
+             | {negative}    expression                           // -x
+             | {not}         expression                           // !x
+             | {exists}      expression                           // ?x
+             | {comma}       [left]:expression [right]:expression // x, y
+             | {eq}          [left]:expression [right]:expression // x == y
+             | {numeric_eq}  [left]:expression [right]:expression // x == y (numeric)
+             | {ne}          [left]:expression [right]:expression // x != y
+             | {numeric_ne}  [left]:expression [right]:expression // x != y (numeric)
+             | {lt}          [left]:expression [right]:expression // x < y
+             | {gt}          [left]:expression [right]:expression // x > y
+             | {lte}         [left]:expression [right]:expression // x <= y
+             | {gte}         [left]:expression [right]:expression // x >= y
+             | {and}         [left]:expression [right]:expression // x && y
+             | {or}          [left]:expression [right]:expression // x || y
+             | {add}         [left]:expression [right]:expression // x + y
+             | {numeric_add} [left]:expression [right]:expression // x + y (numeric)
+             | {subtract}    [left]:expression [right]:expression // x - y
+             | {multiply}    [left]:expression [right]:expression // x * y
+             | {divide}      [left]:expression [right]:expression // x / y
+             | {modulo}      [left]:expression [right]:expression // x % y
+             | {noop}                                             // No operation
+             ;
+
+  variable   = {name}       T.word                             // something
+             | {dec_number} T.dec_number                       // 2
+             | {hex_number} T.hex_number                       // 0xA1
+             | {descend}  [parent]:variable [child]:variable   // foo.bar
+             | {expand}   [parent]:variable [child]:expression // foo["bar"]
+             ;
diff --git a/src/com/google/clearsilver/jsilver/syntax/lexer/Lexer.java b/src/com/google/clearsilver/jsilver/syntax/lexer/Lexer.java
new file mode 100644
index 0000000..ddec62e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/lexer/Lexer.java
@@ -0,0 +1,1384 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.lexer;
+
+import java.io.*;
+import com.google.clearsilver.jsilver.syntax.node.*;
+
+@SuppressWarnings("nls")
+public class Lexer
+{
+    protected Token token;
+    protected State state = State.CONTENT;
+
+    private PushbackReader in;
+    private int line;
+    private int pos;
+    private boolean cr;
+    private boolean eof;
+    private final StringBuffer text = new StringBuffer();
+
+    @SuppressWarnings("unused")
+    protected void filter() throws LexerException, IOException
+    {
+        // Do nothing
+    }
+
+    public Lexer(@SuppressWarnings("hiding") PushbackReader in)
+    {
+        this.in = in;
+    }
+ 
+    public Token peek() throws LexerException, IOException
+    {
+        while(this.token == null)
+        {
+            this.token = getToken();
+            filter();
+        }
+
+        return this.token;
+    }
+
+    public Token next() throws LexerException, IOException
+    {
+        while(this.token == null)
+        {
+            this.token = getToken();
+            filter();
+        }
+
+        Token result = this.token;
+        this.token = null;
+        return result;
+    }
+
+    protected Token getToken() throws IOException, LexerException
+    {
+        int dfa_state = 0;
+
+        int start_pos = this.pos;
+        int start_line = this.line;
+
+        int accept_state = -1;
+        int accept_token = -1;
+        int accept_length = -1;
+        int accept_pos = -1;
+        int accept_line = -1;
+
+        @SuppressWarnings("hiding") int[][][] gotoTable = Lexer.gotoTable[this.state.id()];
+        @SuppressWarnings("hiding") int[] accept = Lexer.accept[this.state.id()];
+        this.text.setLength(0);
+
+        while(true)
+        {
+            int c = getChar();
+
+            if(c != -1)
+            {
+                switch(c)
+                {
+                case 10:
+                    if(this.cr)
+                    {
+                        this.cr = false;
+                    }
+                    else
+                    {
+                        this.line++;
+                        this.pos = 0;
+                    }
+                    break;
+                case 13:
+                    this.line++;
+                    this.pos = 0;
+                    this.cr = true;
+                    break;
+                default:
+                    this.pos++;
+                    this.cr = false;
+                    break;
+                }
+
+                this.text.append((char) c);
+
+                do
+                {
+                    int oldState = (dfa_state < -1) ? (-2 -dfa_state) : dfa_state;
+
+                    dfa_state = -1;
+
+                    int[][] tmp1 =  gotoTable[oldState];
+                    int low = 0;
+                    int high = tmp1.length - 1;
+
+                    while(low <= high)
+                    {
+                        int middle = (low + high) / 2;
+                        int[] tmp2 = tmp1[middle];
+
+                        if(c < tmp2[0])
+                        {
+                            high = middle - 1;
+                        }
+                        else if(c > tmp2[1])
+                        {
+                            low = middle + 1;
+                        }
+                        else
+                        {
+                            dfa_state = tmp2[2];
+                            break;
+                        }
+                    }
+                }while(dfa_state < -1);
+            }
+            else
+            {
+                dfa_state = -1;
+            }
+
+            if(dfa_state >= 0)
+            {
+                if(accept[dfa_state] != -1)
+                {
+                    accept_state = dfa_state;
+                    accept_token = accept[dfa_state];
+                    accept_length = this.text.length();
+                    accept_pos = this.pos;
+                    accept_line = this.line;
+                }
+            }
+            else
+            {
+                if(accept_state != -1)
+                {
+                    switch(accept_token)
+                    {
+                    case 0:
+                        {
+                            @SuppressWarnings("hiding") Token token = new0(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 0: state = State.CONTENT; break;
+                            }
+                            return token;
+                        }
+                    case 1:
+                        {
+                            @SuppressWarnings("hiding") Token token = new1(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 3: state = State.COMMENT; break;
+                            }
+                            return token;
+                        }
+                    case 2:
+                        {
+                            @SuppressWarnings("hiding") Token token = new2(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 3:
+                        {
+                            @SuppressWarnings("hiding") Token token = new3(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 4:
+                        {
+                            @SuppressWarnings("hiding") Token token = new4(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 5:
+                        {
+                            @SuppressWarnings("hiding") Token token = new5(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 6:
+                        {
+                            @SuppressWarnings("hiding") Token token = new6(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 7:
+                        {
+                            @SuppressWarnings("hiding") Token token = new7(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 8:
+                        {
+                            @SuppressWarnings("hiding") Token token = new8(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 9:
+                        {
+                            @SuppressWarnings("hiding") Token token = new9(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 10:
+                        {
+                            @SuppressWarnings("hiding") Token token = new10(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 11:
+                        {
+                            @SuppressWarnings("hiding") Token token = new11(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 12:
+                        {
+                            @SuppressWarnings("hiding") Token token = new12(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 13:
+                        {
+                            @SuppressWarnings("hiding") Token token = new13(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 14:
+                        {
+                            @SuppressWarnings("hiding") Token token = new14(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 15:
+                        {
+                            @SuppressWarnings("hiding") Token token = new15(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 16:
+                        {
+                            @SuppressWarnings("hiding") Token token = new16(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 17:
+                        {
+                            @SuppressWarnings("hiding") Token token = new17(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 18:
+                        {
+                            @SuppressWarnings("hiding") Token token = new18(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 19:
+                        {
+                            @SuppressWarnings("hiding") Token token = new19(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 20:
+                        {
+                            @SuppressWarnings("hiding") Token token = new20(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 21:
+                        {
+                            @SuppressWarnings("hiding") Token token = new21(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 22:
+                        {
+                            @SuppressWarnings("hiding") Token token = new22(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 23:
+                        {
+                            @SuppressWarnings("hiding") Token token = new23(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 24:
+                        {
+                            @SuppressWarnings("hiding") Token token = new24(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 25:
+                        {
+                            @SuppressWarnings("hiding") Token token = new25(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 26:
+                        {
+                            @SuppressWarnings("hiding") Token token = new26(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 27:
+                        {
+                            @SuppressWarnings("hiding") Token token = new27(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 28:
+                        {
+                            @SuppressWarnings("hiding") Token token = new28(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 29:
+                        {
+                            @SuppressWarnings("hiding") Token token = new29(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 30:
+                        {
+                            @SuppressWarnings("hiding") Token token = new30(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 31:
+                        {
+                            @SuppressWarnings("hiding") Token token = new31(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 32:
+                        {
+                            @SuppressWarnings("hiding") Token token = new32(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 33:
+                        {
+                            @SuppressWarnings("hiding") Token token = new33(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 34:
+                        {
+                            @SuppressWarnings("hiding") Token token = new34(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 35:
+                        {
+                            @SuppressWarnings("hiding") Token token = new35(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 36:
+                        {
+                            @SuppressWarnings("hiding") Token token = new36(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 37:
+                        {
+                            @SuppressWarnings("hiding") Token token = new37(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 38:
+                        {
+                            @SuppressWarnings("hiding") Token token = new38(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 39:
+                        {
+                            @SuppressWarnings("hiding") Token token = new39(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 40:
+                        {
+                            @SuppressWarnings("hiding") Token token = new40(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 41:
+                        {
+                            @SuppressWarnings("hiding") Token token = new41(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 42:
+                        {
+                            @SuppressWarnings("hiding") Token token = new42(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 43:
+                        {
+                            @SuppressWarnings("hiding") Token token = new43(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 44:
+                        {
+                            @SuppressWarnings("hiding") Token token = new44(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 45:
+                        {
+                            @SuppressWarnings("hiding") Token token = new45(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 46:
+                        {
+                            @SuppressWarnings("hiding") Token token = new46(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 47:
+                        {
+                            @SuppressWarnings("hiding") Token token = new47(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 48:
+                        {
+                            @SuppressWarnings("hiding") Token token = new48(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 49:
+                        {
+                            @SuppressWarnings("hiding") Token token = new49(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 50:
+                        {
+                            @SuppressWarnings("hiding") Token token = new50(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 51:
+                        {
+                            @SuppressWarnings("hiding") Token token = new51(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.ARGS; break;
+                                case 1: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 52:
+                        {
+                            @SuppressWarnings("hiding") Token token = new52(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 0: state = State.COMMAND; break;
+                            }
+                            return token;
+                        }
+                    case 53:
+                        {
+                            @SuppressWarnings("hiding") Token token = new53(
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.COMMENT; break;
+                            }
+                            return token;
+                        }
+                    case 54:
+                        {
+                            @SuppressWarnings("hiding") Token token = new54(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 55:
+                        {
+                            @SuppressWarnings("hiding") Token token = new55(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 1: state = State.ARGS; break;
+                            }
+                            return token;
+                        }
+                    case 56:
+                        {
+                            @SuppressWarnings("hiding") Token token = new56(
+                                getText(accept_length),
+                                start_line + 1,
+                                start_pos + 1);
+                            pushBack(accept_length);
+                            this.pos = accept_pos;
+                            this.line = accept_line;
+                            switch(state.id())
+                            {
+                                case 2: state = State.CONTENT; break;
+                                case 1: state = State.CONTENT; break;
+                                case 3: state = State.CONTENT; break;
+                            }
+                            return token;
+                        }
+                    }
+                }
+                else
+                {
+                    if(this.text.length() > 0)
+                    {
+                        throw new LexerException(
+                            "[" + (start_line + 1) + "," + (start_pos + 1) + "]" +
+                            " Unknown token: " + this.text);
+                    }
+
+                    @SuppressWarnings("hiding") EOF token = new EOF(
+                        start_line + 1,
+                        start_pos + 1);
+                    return token;
+                }
+            }
+        }
+    }
+
+    Token new0(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TData(text, line, pos); }
+    Token new1(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TComment(text, line, pos); }
+    Token new2(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TVar(line, pos); }
+    Token new3(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TLvar(line, pos); }
+    Token new4(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TEvar(line, pos); }
+    Token new5(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TUvar(line, pos); }
+    Token new6(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TSet(line, pos); }
+    Token new7(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TIf(line, pos); }
+    Token new8(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TElseIf(text, line, pos); }
+    Token new9(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TElse(line, pos); }
+    Token new10(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TWith(line, pos); }
+    Token new11(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TEscape(line, pos); }
+    Token new12(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TAutoescape(line, pos); }
+    Token new13(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TLoop(line, pos); }
+    Token new14(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TEach(line, pos); }
+    Token new15(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TAlt(line, pos); }
+    Token new16(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TName(line, pos); }
+    Token new17(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TDef(line, pos); }
+    Token new18(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TCall(line, pos); }
+    Token new19(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TInclude(line, pos); }
+    Token new20(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TLinclude(line, pos); }
+    Token new21(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TContentType(line, pos); }
+    Token new22(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TInline(line, pos); }
+    Token new23(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TComma(line, pos); }
+    Token new24(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TBang(line, pos); }
+    Token new25(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TAssignment(line, pos); }
+    Token new26(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TEq(line, pos); }
+    Token new27(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TNe(line, pos); }
+    Token new28(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TLt(line, pos); }
+    Token new29(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TGt(line, pos); }
+    Token new30(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TLte(line, pos); }
+    Token new31(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TGte(line, pos); }
+    Token new32(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TAnd(line, pos); }
+    Token new33(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TOr(line, pos); }
+    Token new34(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TString(text, line, pos); }
+    Token new35(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new THash(line, pos); }
+    Token new36(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TPlus(line, pos); }
+    Token new37(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TMinus(line, pos); }
+    Token new38(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TStar(line, pos); }
+    Token new39(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TPercent(line, pos); }
+    Token new40(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TBracketOpen(line, pos); }
+    Token new41(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TBracketClose(line, pos); }
+    Token new42(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TParenOpen(line, pos); }
+    Token new43(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TParenClose(line, pos); }
+    Token new44(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TDot(line, pos); }
+    Token new45(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TDollar(line, pos); }
+    Token new46(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TQuestion(line, pos); }
+    Token new47(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TDecNumber(text, line, pos); }
+    Token new48(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new THexNumber(text, line, pos); }
+    Token new49(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TWord(text, line, pos); }
+    Token new50(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TArgWhitespace(text, line, pos); }
+    Token new51(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TSlash(line, pos); }
+    Token new52(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TCsOpen(text, line, pos); }
+    Token new53(@SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TCommentStart(line, pos); }
+    Token new54(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TCommandDelimiter(text, line, pos); }
+    Token new55(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new THardDelimiter(text, line, pos); }
+    Token new56(@SuppressWarnings("hiding") String text, @SuppressWarnings("hiding") int line, @SuppressWarnings("hiding") int pos) { return new TCsClose(text, line, pos); }
+
+    private int getChar() throws IOException
+    {
+        if(this.eof)
+        {
+            return -1;
+        }
+
+        int result = this.in.read();
+
+        if(result == -1)
+        {
+            this.eof = true;
+        }
+
+        return result;
+    }
+
+    private void pushBack(int acceptLength) throws IOException
+    {
+        int length = this.text.length();
+        for(int i = length - 1; i >= acceptLength; i--)
+        {
+            this.eof = false;
+
+            this.in.unread(this.text.charAt(i));
+        }
+    }
+
+    protected void unread(@SuppressWarnings("hiding") Token token) throws IOException
+    {
+        @SuppressWarnings("hiding") String text = token.getText();
+        int length = text.length();
+
+        for(int i = length - 1; i >= 0; i--)
+        {
+            this.eof = false;
+
+            this.in.unread(text.charAt(i));
+        }
+
+        this.pos = token.getPos() - 1;
+        this.line = token.getLine() - 1;
+    }
+
+    private String getText(int acceptLength)
+    {
+        StringBuffer s = new StringBuffer(acceptLength);
+        for(int i = 0; i < acceptLength; i++)
+        {
+            s.append(this.text.charAt(i));
+        }
+
+        return s.toString();
+    }
+
+    private static int[][][][] gotoTable;
+/*  {
+        { // CONTENT
+            {{0, 59, 1}, {60, 60, 2}, {61, 65535, 1}, },
+            {{0, 59, 1}, {60, 60, 3}, {61, 65535, 1}, },
+            {{0, 62, 4}, {63, 63, 5}, {64, 65535, 4}, },
+            {{0, 62, 4}, {63, 63, 6}, {64, 65535, 4}, },
+            {{0, 65535, -3}, },
+            {{0, 98, 7}, {99, 99, 8}, {100, 65535, 7}, },
+            {{0, 98, 7}, {99, 99, 9}, {100, 65535, 7}, },
+            {{0, 65535, -3}, },
+            {{0, 114, 10}, {115, 115, 11}, {116, 65535, 10}, },
+            {{0, 114, 10}, {115, 115, 12}, {116, 65535, 10}, },
+            {{0, 65535, -3}, },
+            {{0, 8, 13}, {9, 9, 14}, {10, 10, 15}, {11, 12, 13}, {13, 13, 16}, {14, 31, 13}, {32, 32, 17}, {33, 65535, 13}, },
+            {{0, 8, 13}, {11, 12, 13}, {14, 31, 13}, {33, 65535, 13}, },
+            {{0, 65535, -3}, },
+            {{9, 10, -13}, {13, 13, 16}, {32, 32, 17}, },
+            {{9, 32, -16}, },
+            {{9, 32, -16}, },
+            {{9, 32, -16}, },
+        }
+        { // COMMAND
+            {{9, 9, 1}, {10, 10, 2}, {13, 13, 3}, {32, 32, 4}, {33, 33, 5}, {35, 35, 6}, {47, 47, 7}, {58, 58, 8}, {63, 63, 9}, {97, 97, 10}, {99, 99, 11}, {100, 100, 12}, {101, 101, 13}, {105, 105, 14}, {108, 108, 15}, {110, 110, 16}, {115, 115, 17}, {117, 117, 18}, {118, 118, 19}, {119, 119, 20}, },
+            {{9, 9, 21}, {10, 10, 22}, {13, 13, 23}, {32, 32, 24}, {63, 63, 9}, },
+            {{9, 63, -3}, },
+            {{9, 63, -3}, },
+            {{9, 63, -3}, },
+            {},
+            {},
+            {},
+            {},
+            {{62, 62, 25}, },
+            {{108, 108, 26}, {117, 117, 27}, },
+            {{97, 97, 28}, {111, 111, 29}, },
+            {{101, 101, 30}, },
+            {{97, 97, 31}, {108, 108, 32}, {115, 115, 33}, {118, 118, 34}, },
+            {{102, 102, 35}, {110, 110, 36}, },
+            {{105, 105, 37}, {111, 111, 38}, {118, 118, 39}, },
+            {{97, 97, 40}, },
+            {{101, 101, 41}, },
+            {{118, 118, 42}, },
+            {{97, 97, 43}, },
+            {{105, 105, 44}, },
+            {{9, 63, -3}, },
+            {{9, 63, -3}, },
+            {{9, 63, -3}, },
+            {{9, 63, -3}, },
+            {},
+            {{116, 116, 45}, },
+            {{116, 116, 46}, },
+            {{108, 108, 47}, },
+            {{110, 110, 48}, },
+            {{102, 102, 49}, },
+            {{99, 99, 50}, },
+            {{105, 105, 51}, {115, 115, 52}, },
+            {{99, 99, 53}, },
+            {{97, 97, 54}, },
+            {},
+            {{99, 99, 55}, {108, 108, 56}, },
+            {{110, 110, 57}, },
+            {{111, 111, 58}, },
+            {{97, 97, 59}, },
+            {{109, 109, 60}, },
+            {{116, 116, 61}, },
+            {{97, 97, 62}, },
+            {{114, 114, 63}, },
+            {{116, 116, 64}, },
+            {},
+            {{111, 111, 65}, },
+            {{108, 108, 66}, },
+            {{116, 116, 67}, },
+            {},
+            {{104, 104, 68}, },
+            {{102, 102, 69}, },
+            {{101, 101, 70}, },
+            {{97, 97, 71}, },
+            {{114, 114, 72}, },
+            {{108, 108, 73}, },
+            {{105, 105, 74}, },
+            {{99, 99, 75}, },
+            {{112, 112, 76}, },
+            {{114, 114, 77}, },
+            {{101, 101, 78}, },
+            {},
+            {{114, 114, 79}, },
+            {},
+            {{104, 104, 80}, },
+            {{101, 101, 81}, },
+            {},
+            {{101, 101, 82}, },
+            {},
+            {},
+            {{105, 105, 83}, },
+            {{112, 112, 84}, },
+            {},
+            {{117, 117, 85}, },
+            {{110, 110, 86}, },
+            {{108, 108, 87}, },
+            {},
+            {},
+            {},
+            {},
+            {},
+            {{115, 115, 88}, },
+            {{110, 110, 89}, },
+            {{102, 102, 90}, },
+            {{101, 101, 91}, },
+            {{100, 100, 92}, },
+            {{101, 101, 93}, },
+            {{117, 117, 94}, },
+            {{99, 99, 95}, },
+            {{116, 116, 96}, },
+            {},
+            {},
+            {{101, 101, 97}, },
+            {},
+            {{100, 100, 98}, },
+            {{97, 97, 99}, },
+            {{45, 45, 100}, },
+            {},
+            {{101, 101, 101}, },
+            {{112, 112, 102}, },
+            {{116, 116, 103}, },
+            {},
+            {{101, 101, 104}, },
+            {{121, 121, 105}, },
+            {},
+            {{112, 112, 106}, },
+            {{101, 101, 107}, },
+            {},
+        }
+        { // ARGS
+            {{9, 9, 1}, {10, 10, 2}, {13, 13, 3}, {32, 32, 4}, {33, 33, 5}, {34, 34, 6}, {35, 35, 7}, {36, 36, 8}, {37, 37, 9}, {38, 38, 10}, {39, 39, 11}, {40, 40, 12}, {41, 41, 13}, {42, 42, 14}, {43, 43, 15}, {44, 44, 16}, {45, 45, 17}, {46, 46, 18}, {47, 47, 19}, {48, 48, 20}, {49, 57, 21}, {60, 60, 22}, {61, 61, 23}, {62, 62, 24}, {63, 63, 25}, {65, 90, 26}, {91, 91, 27}, {93, 93, 28}, {95, 95, 29}, {97, 122, 30}, {124, 124, 31}, },
+            {{9, 32, -2}, {63, 63, 32}, },
+            {{9, 63, -3}, },
+            {{9, 63, -3}, },
+            {{9, 63, -3}, },
+            {{61, 61, 33}, },
+            {{0, 33, 34}, {34, 34, 35}, {35, 65535, 34}, },
+            {},
+            {},
+            {},
+            {{38, 38, 36}, },
+            {{0, 38, 37}, {39, 39, 38}, {40, 65535, 37}, },
+            {},
+            {},
+            {},
+            {},
+            {},
+            {},
+            {},
+            {},
+            {{48, 57, 21}, {65, 87, 26}, {88, 88, 39}, {89, 90, 26}, {95, 95, 29}, {97, 119, 30}, {120, 120, 40}, {121, 122, 30}, },
+            {{48, 57, 21}, {65, 90, 26}, {95, 122, -2}, },
+            {{61, 61, 41}, },
+            {{61, 61, 42}, },
+            {{61, 61, 43}, },
+            {{62, 62, 44}, },
+            {{48, 57, 45}, {65, 122, -23}, },
+            {},
+            {},
+            {{48, 122, -28}, },
+            {{48, 122, -28}, },
+            {{124, 124, 46}, },
+            {{62, 62, 44}, },
+            {},
+            {{0, 65535, -8}, },
+            {},
+            {},
+            {{0, 65535, -13}, },
+            {},
+            {{48, 57, 47}, {65, 70, 48}, {71, 90, 26}, {95, 95, 29}, {97, 102, 49}, {103, 122, 30}, },
+            {{48, 122, -41}, },
+            {},
+            {},
+            {},
+            {},
+            {{48, 122, -28}, },
+            {},
+            {{48, 122, -41}, },
+            {{48, 122, -41}, },
+            {{48, 122, -41}, },
+        }
+        { // COMMENT
+            {{0, 8, 1}, {9, 9, 2}, {10, 10, 3}, {11, 12, 1}, {13, 13, 4}, {14, 31, 1}, {32, 32, 5}, {33, 62, 1}, {63, 63, 6}, {64, 65535, 1}, },
+            {{0, 62, 1}, {63, 63, 7}, {64, 65535, 1}, },
+            {{0, 65535, -2}, },
+            {{0, 65535, -2}, },
+            {{0, 65535, -2}, },
+            {{0, 65535, -2}, },
+            {{0, 61, 8}, {62, 62, 9}, {63, 65535, 8}, },
+            {{0, 61, 8}, {63, 65535, 8}, },
+            {{0, 65535, -3}, },
+            {},
+        }
+    };*/
+
+    private static int[][] accept;
+/*  {
+        // CONTENT
+        {-1, 0, -1, -1, 0, -1, -1, 0, -1, -1, 0, -1, -1, 0, 52, 52, 52, 52, },
+        // COMMAND
+        {-1, 54, 54, 54, 54, 55, 53, 51, 54, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 56, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, -1, -1, -1, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, 2, -1, -1, 18, -1, 14, 8, 9, -1, 4, -1, -1, -1, 13, 3, 16, 5, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, 11, -1, 22, -1, -1, -1, 19, -1, -1, -1, 20, -1, -1, 12, -1, -1, 21, },
+        // ARGS
+        {-1, 50, 50, 50, 50, 24, -1, 35, 45, 39, -1, -1, 42, 43, 38, 36, 23, 37, 44, 51, 47, 47, 28, 25, 29, 46, 49, 40, 41, 49, 49, -1, -1, 27, -1, 34, 32, -1, 34, 49, 49, 30, 26, 31, 56, 49, 33, 48, 48, 48, },
+        // COMMENT
+        {-1, 1, 1, 1, 1, 1, -1, -1, 1, 56, },
+
+    };*/
+
+    public static class State
+    {
+        public final static State CONTENT = new State(0);
+        public final static State COMMAND = new State(1);
+        public final static State ARGS = new State(2);
+        public final static State COMMENT = new State(3);
+
+        private int id;
+
+        private State(@SuppressWarnings("hiding") int id)
+        {
+            this.id = id;
+        }
+
+        public int id()
+        {
+            return this.id;
+        }
+    }
+
+    static 
+    {
+        try
+        {
+            DataInputStream s = new DataInputStream(
+                new BufferedInputStream(
+                Lexer.class.getResourceAsStream("lexer.dat")));
+
+            // read gotoTable
+            int length = s.readInt();
+            gotoTable = new int[length][][][];
+            for(int i = 0; i < gotoTable.length; i++)
+            {
+                length = s.readInt();
+                gotoTable[i] = new int[length][][];
+                for(int j = 0; j < gotoTable[i].length; j++)
+                {
+                    length = s.readInt();
+                    gotoTable[i][j] = new int[length][3];
+                    for(int k = 0; k < gotoTable[i][j].length; k++)
+                    {
+                        for(int l = 0; l < 3; l++)
+                        {
+                            gotoTable[i][j][k][l] = s.readInt();
+                        }
+                    }
+                }
+            }
+
+            // read accept
+            length = s.readInt();
+            accept = new int[length][];
+            for(int i = 0; i < accept.length; i++)
+            {
+                length = s.readInt();
+                accept[i] = new int[length];
+                for(int j = 0; j < accept[i].length; j++)
+                {
+                    accept[i][j] = s.readInt();
+                }
+            }
+
+            s.close();
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException("The file \"lexer.dat\" is either missing or corrupted.");
+        }
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/lexer/LexerException.java b/src/com/google/clearsilver/jsilver/syntax/lexer/LexerException.java
new file mode 100644
index 0000000..cf31e3b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/lexer/LexerException.java
@@ -0,0 +1,12 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.lexer;
+
+@SuppressWarnings("serial")
+public class LexerException extends Exception
+{
+    public LexerException(String message)
+    {
+        super(message);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/lexer/lexer.dat b/src/com/google/clearsilver/jsilver/syntax/lexer/lexer.dat
new file mode 100644
index 0000000..469c2d5
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/lexer/lexer.dat
Binary files differ
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AAddExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AAddExpression.java
new file mode 100644
index 0000000..a5cc148
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AAddExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AAddExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public AAddExpression()
+    {
+        // Constructor
+    }
+
+    public AAddExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AAddExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAAddExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AAltCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AAltCommand.java
new file mode 100644
index 0000000..b09fc5b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AAltCommand.java
@@ -0,0 +1,180 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AAltCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+    private PCommand _command_;
+
+    public AAltCommand()
+    {
+        // Constructor
+    }
+
+    public AAltCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_,
+        @SuppressWarnings("hiding") PCommand _command_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+        setCommand(_command_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AAltCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_),
+            cloneNode(this._command_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAAltCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    public PCommand getCommand()
+    {
+        return this._command_;
+    }
+
+    public void setCommand(PCommand node)
+    {
+        if(this._command_ != null)
+        {
+            this._command_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._command_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_)
+            + toString(this._command_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        if(this._command_ == child)
+        {
+            this._command_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        if(this._command_ == oldChild)
+        {
+            setCommand((PCommand) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AAndExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AAndExpression.java
new file mode 100644
index 0000000..4ff2eab
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AAndExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AAndExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public AAndExpression()
+    {
+        // Constructor
+    }
+
+    public AAndExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AAndExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAAndExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AAutoescapeCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AAutoescapeCommand.java
new file mode 100644
index 0000000..b68e6ea
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AAutoescapeCommand.java
@@ -0,0 +1,180 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AAutoescapeCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+    private PCommand _command_;
+
+    public AAutoescapeCommand()
+    {
+        // Constructor
+    }
+
+    public AAutoescapeCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_,
+        @SuppressWarnings("hiding") PCommand _command_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+        setCommand(_command_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AAutoescapeCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_),
+            cloneNode(this._command_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAAutoescapeCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    public PCommand getCommand()
+    {
+        return this._command_;
+    }
+
+    public void setCommand(PCommand node)
+    {
+        if(this._command_ != null)
+        {
+            this._command_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._command_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_)
+            + toString(this._command_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        if(this._command_ == child)
+        {
+            this._command_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        if(this._command_ == oldChild)
+        {
+            setCommand((PCommand) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ACallCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ACallCommand.java
new file mode 100644
index 0000000..e876b56
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ACallCommand.java
@@ -0,0 +1,193 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import java.util.*;
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ACallCommand extends PCommand
+{
+    private PPosition _position_;
+    private final LinkedList<TWord> _macro_ = new LinkedList<TWord>();
+    private final LinkedList<PExpression> _arguments_ = new LinkedList<PExpression>();
+
+    public ACallCommand()
+    {
+        // Constructor
+    }
+
+    public ACallCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") List<TWord> _macro_,
+        @SuppressWarnings("hiding") List<PExpression> _arguments_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setMacro(_macro_);
+
+        setArguments(_arguments_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ACallCommand(
+            cloneNode(this._position_),
+            cloneList(this._macro_),
+            cloneList(this._arguments_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseACallCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public LinkedList<TWord> getMacro()
+    {
+        return this._macro_;
+    }
+
+    public void setMacro(List<TWord> list)
+    {
+        this._macro_.clear();
+        this._macro_.addAll(list);
+        for(TWord e : list)
+        {
+            if(e.parent() != null)
+            {
+                e.parent().removeChild(e);
+            }
+
+            e.parent(this);
+        }
+    }
+
+    public LinkedList<PExpression> getArguments()
+    {
+        return this._arguments_;
+    }
+
+    public void setArguments(List<PExpression> list)
+    {
+        this._arguments_.clear();
+        this._arguments_.addAll(list);
+        for(PExpression e : list)
+        {
+            if(e.parent() != null)
+            {
+                e.parent().removeChild(e);
+            }
+
+            e.parent(this);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._macro_)
+            + toString(this._arguments_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._macro_.remove(child))
+        {
+            return;
+        }
+
+        if(this._arguments_.remove(child))
+        {
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        for(ListIterator<TWord> i = this._macro_.listIterator(); i.hasNext();)
+        {
+            if(i.next() == oldChild)
+            {
+                if(newChild != null)
+                {
+                    i.set((TWord) newChild);
+                    newChild.parent(this);
+                    oldChild.parent(null);
+                    return;
+                }
+
+                i.remove();
+                oldChild.parent(null);
+                return;
+            }
+        }
+
+        for(ListIterator<PExpression> i = this._arguments_.listIterator(); i.hasNext();)
+        {
+            if(i.next() == oldChild)
+            {
+                if(newChild != null)
+                {
+                    i.set((PExpression) newChild);
+                    newChild.parent(this);
+                    oldChild.parent(null);
+                    return;
+                }
+
+                i.remove();
+                oldChild.parent(null);
+                return;
+            }
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ACommaExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ACommaExpression.java
new file mode 100644
index 0000000..6152a6b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ACommaExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ACommaExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public ACommaExpression()
+    {
+        // Constructor
+    }
+
+    public ACommaExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ACommaExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseACommaExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ACommentCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ACommentCommand.java
new file mode 100644
index 0000000..613c558
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ACommentCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ACommentCommand extends PCommand
+{
+    private PPosition _position_;
+    private TComment _comment_;
+
+    public ACommentCommand()
+    {
+        // Constructor
+    }
+
+    public ACommentCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") TComment _comment_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setComment(_comment_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ACommentCommand(
+            cloneNode(this._position_),
+            cloneNode(this._comment_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseACommentCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public TComment getComment()
+    {
+        return this._comment_;
+    }
+
+    public void setComment(TComment node)
+    {
+        if(this._comment_ != null)
+        {
+            this._comment_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._comment_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._comment_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._comment_ == child)
+        {
+            this._comment_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._comment_ == oldChild)
+        {
+            setComment((TComment) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AContentTypeCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AContentTypeCommand.java
new file mode 100644
index 0000000..38ebfa3
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AContentTypeCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AContentTypeCommand extends PCommand
+{
+    private PPosition _position_;
+    private TString _string_;
+
+    public AContentTypeCommand()
+    {
+        // Constructor
+    }
+
+    public AContentTypeCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") TString _string_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setString(_string_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AContentTypeCommand(
+            cloneNode(this._position_),
+            cloneNode(this._string_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAContentTypeCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public TString getString()
+    {
+        return this._string_;
+    }
+
+    public void setString(TString node)
+    {
+        if(this._string_ != null)
+        {
+            this._string_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._string_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._string_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._string_ == child)
+        {
+            this._string_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._string_ == oldChild)
+        {
+            setString((TString) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ACsOpenPosition.java b/src/com/google/clearsilver/jsilver/syntax/node/ACsOpenPosition.java
new file mode 100644
index 0000000..e5398cb
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ACsOpenPosition.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ACsOpenPosition extends PPosition
+{
+    private TCsOpen _csOpen_;
+
+    public ACsOpenPosition()
+    {
+        // Constructor
+    }
+
+    public ACsOpenPosition(
+        @SuppressWarnings("hiding") TCsOpen _csOpen_)
+    {
+        // Constructor
+        setCsOpen(_csOpen_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ACsOpenPosition(
+            cloneNode(this._csOpen_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseACsOpenPosition(this);
+    }
+
+    public TCsOpen getCsOpen()
+    {
+        return this._csOpen_;
+    }
+
+    public void setCsOpen(TCsOpen node)
+    {
+        if(this._csOpen_ != null)
+        {
+            this._csOpen_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._csOpen_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._csOpen_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._csOpen_ == child)
+        {
+            this._csOpen_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._csOpen_ == oldChild)
+        {
+            setCsOpen((TCsOpen) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ADataCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ADataCommand.java
new file mode 100644
index 0000000..87ef509
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ADataCommand.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ADataCommand extends PCommand
+{
+    private TData _data_;
+
+    public ADataCommand()
+    {
+        // Constructor
+    }
+
+    public ADataCommand(
+        @SuppressWarnings("hiding") TData _data_)
+    {
+        // Constructor
+        setData(_data_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ADataCommand(
+            cloneNode(this._data_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseADataCommand(this);
+    }
+
+    public TData getData()
+    {
+        return this._data_;
+    }
+
+    public void setData(TData node)
+    {
+        if(this._data_ != null)
+        {
+            this._data_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._data_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._data_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._data_ == child)
+        {
+            this._data_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._data_ == oldChild)
+        {
+            setData((TData) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ADecNumberVariable.java b/src/com/google/clearsilver/jsilver/syntax/node/ADecNumberVariable.java
new file mode 100644
index 0000000..d47b29f
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ADecNumberVariable.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ADecNumberVariable extends PVariable
+{
+    private TDecNumber _decNumber_;
+
+    public ADecNumberVariable()
+    {
+        // Constructor
+    }
+
+    public ADecNumberVariable(
+        @SuppressWarnings("hiding") TDecNumber _decNumber_)
+    {
+        // Constructor
+        setDecNumber(_decNumber_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ADecNumberVariable(
+            cloneNode(this._decNumber_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseADecNumberVariable(this);
+    }
+
+    public TDecNumber getDecNumber()
+    {
+        return this._decNumber_;
+    }
+
+    public void setDecNumber(TDecNumber node)
+    {
+        if(this._decNumber_ != null)
+        {
+            this._decNumber_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._decNumber_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._decNumber_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._decNumber_ == child)
+        {
+            this._decNumber_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._decNumber_ == oldChild)
+        {
+            setDecNumber((TDecNumber) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ADecimalExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ADecimalExpression.java
new file mode 100644
index 0000000..15a20d0
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ADecimalExpression.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ADecimalExpression extends PExpression
+{
+    private TDecNumber _value_;
+
+    public ADecimalExpression()
+    {
+        // Constructor
+    }
+
+    public ADecimalExpression(
+        @SuppressWarnings("hiding") TDecNumber _value_)
+    {
+        // Constructor
+        setValue(_value_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ADecimalExpression(
+            cloneNode(this._value_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseADecimalExpression(this);
+    }
+
+    public TDecNumber getValue()
+    {
+        return this._value_;
+    }
+
+    public void setValue(TDecNumber node)
+    {
+        if(this._value_ != null)
+        {
+            this._value_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._value_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._value_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._value_ == child)
+        {
+            this._value_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._value_ == oldChild)
+        {
+            setValue((TDecNumber) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ADefCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ADefCommand.java
new file mode 100644
index 0000000..8cf21dd
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ADefCommand.java
@@ -0,0 +1,236 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import java.util.*;
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ADefCommand extends PCommand
+{
+    private PPosition _position_;
+    private final LinkedList<TWord> _macro_ = new LinkedList<TWord>();
+    private final LinkedList<PVariable> _arguments_ = new LinkedList<PVariable>();
+    private PCommand _command_;
+
+    public ADefCommand()
+    {
+        // Constructor
+    }
+
+    public ADefCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") List<TWord> _macro_,
+        @SuppressWarnings("hiding") List<PVariable> _arguments_,
+        @SuppressWarnings("hiding") PCommand _command_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setMacro(_macro_);
+
+        setArguments(_arguments_);
+
+        setCommand(_command_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ADefCommand(
+            cloneNode(this._position_),
+            cloneList(this._macro_),
+            cloneList(this._arguments_),
+            cloneNode(this._command_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseADefCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public LinkedList<TWord> getMacro()
+    {
+        return this._macro_;
+    }
+
+    public void setMacro(List<TWord> list)
+    {
+        this._macro_.clear();
+        this._macro_.addAll(list);
+        for(TWord e : list)
+        {
+            if(e.parent() != null)
+            {
+                e.parent().removeChild(e);
+            }
+
+            e.parent(this);
+        }
+    }
+
+    public LinkedList<PVariable> getArguments()
+    {
+        return this._arguments_;
+    }
+
+    public void setArguments(List<PVariable> list)
+    {
+        this._arguments_.clear();
+        this._arguments_.addAll(list);
+        for(PVariable e : list)
+        {
+            if(e.parent() != null)
+            {
+                e.parent().removeChild(e);
+            }
+
+            e.parent(this);
+        }
+    }
+
+    public PCommand getCommand()
+    {
+        return this._command_;
+    }
+
+    public void setCommand(PCommand node)
+    {
+        if(this._command_ != null)
+        {
+            this._command_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._command_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._macro_)
+            + toString(this._arguments_)
+            + toString(this._command_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._macro_.remove(child))
+        {
+            return;
+        }
+
+        if(this._arguments_.remove(child))
+        {
+            return;
+        }
+
+        if(this._command_ == child)
+        {
+            this._command_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        for(ListIterator<TWord> i = this._macro_.listIterator(); i.hasNext();)
+        {
+            if(i.next() == oldChild)
+            {
+                if(newChild != null)
+                {
+                    i.set((TWord) newChild);
+                    newChild.parent(this);
+                    oldChild.parent(null);
+                    return;
+                }
+
+                i.remove();
+                oldChild.parent(null);
+                return;
+            }
+        }
+
+        for(ListIterator<PVariable> i = this._arguments_.listIterator(); i.hasNext();)
+        {
+            if(i.next() == oldChild)
+            {
+                if(newChild != null)
+                {
+                    i.set((PVariable) newChild);
+                    newChild.parent(this);
+                    oldChild.parent(null);
+                    return;
+                }
+
+                i.remove();
+                oldChild.parent(null);
+                return;
+            }
+        }
+
+        if(this._command_ == oldChild)
+        {
+            setCommand((PCommand) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ADescendVariable.java b/src/com/google/clearsilver/jsilver/syntax/node/ADescendVariable.java
new file mode 100644
index 0000000..c87beef
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ADescendVariable.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ADescendVariable extends PVariable
+{
+    private PVariable _parent_;
+    private PVariable _child_;
+
+    public ADescendVariable()
+    {
+        // Constructor
+    }
+
+    public ADescendVariable(
+        @SuppressWarnings("hiding") PVariable _parent_,
+        @SuppressWarnings("hiding") PVariable _child_)
+    {
+        // Constructor
+        setParent(_parent_);
+
+        setChild(_child_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ADescendVariable(
+            cloneNode(this._parent_),
+            cloneNode(this._child_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseADescendVariable(this);
+    }
+
+    public PVariable getParent()
+    {
+        return this._parent_;
+    }
+
+    public void setParent(PVariable node)
+    {
+        if(this._parent_ != null)
+        {
+            this._parent_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._parent_ = node;
+    }
+
+    public PVariable getChild()
+    {
+        return this._child_;
+    }
+
+    public void setChild(PVariable node)
+    {
+        if(this._child_ != null)
+        {
+            this._child_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._child_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._parent_)
+            + toString(this._child_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._parent_ == child)
+        {
+            this._parent_ = null;
+            return;
+        }
+
+        if(this._child_ == child)
+        {
+            this._child_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._parent_ == oldChild)
+        {
+            setParent((PVariable) newChild);
+            return;
+        }
+
+        if(this._child_ == oldChild)
+        {
+            setChild((PVariable) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ADivideExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ADivideExpression.java
new file mode 100644
index 0000000..3cd3a93
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ADivideExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ADivideExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public ADivideExpression()
+    {
+        // Constructor
+    }
+
+    public ADivideExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ADivideExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseADivideExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AEachCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AEachCommand.java
new file mode 100644
index 0000000..fec3db7
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AEachCommand.java
@@ -0,0 +1,223 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AEachCommand extends PCommand
+{
+    private PPosition _position_;
+    private PVariable _variable_;
+    private PExpression _expression_;
+    private PCommand _command_;
+
+    public AEachCommand()
+    {
+        // Constructor
+    }
+
+    public AEachCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PVariable _variable_,
+        @SuppressWarnings("hiding") PExpression _expression_,
+        @SuppressWarnings("hiding") PCommand _command_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setVariable(_variable_);
+
+        setExpression(_expression_);
+
+        setCommand(_command_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AEachCommand(
+            cloneNode(this._position_),
+            cloneNode(this._variable_),
+            cloneNode(this._expression_),
+            cloneNode(this._command_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAEachCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PVariable getVariable()
+    {
+        return this._variable_;
+    }
+
+    public void setVariable(PVariable node)
+    {
+        if(this._variable_ != null)
+        {
+            this._variable_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._variable_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    public PCommand getCommand()
+    {
+        return this._command_;
+    }
+
+    public void setCommand(PCommand node)
+    {
+        if(this._command_ != null)
+        {
+            this._command_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._command_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._variable_)
+            + toString(this._expression_)
+            + toString(this._command_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._variable_ == child)
+        {
+            this._variable_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        if(this._command_ == child)
+        {
+            this._command_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._variable_ == oldChild)
+        {
+            setVariable((PVariable) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        if(this._command_ == oldChild)
+        {
+            setCommand((PCommand) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AEqExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AEqExpression.java
new file mode 100644
index 0000000..f68f2ca
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AEqExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AEqExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public AEqExpression()
+    {
+        // Constructor
+    }
+
+    public AEqExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AEqExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAEqExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AEscapeCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AEscapeCommand.java
new file mode 100644
index 0000000..aadf6fb
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AEscapeCommand.java
@@ -0,0 +1,180 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AEscapeCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+    private PCommand _command_;
+
+    public AEscapeCommand()
+    {
+        // Constructor
+    }
+
+    public AEscapeCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_,
+        @SuppressWarnings("hiding") PCommand _command_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+        setCommand(_command_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AEscapeCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_),
+            cloneNode(this._command_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAEscapeCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    public PCommand getCommand()
+    {
+        return this._command_;
+    }
+
+    public void setCommand(PCommand node)
+    {
+        if(this._command_ != null)
+        {
+            this._command_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._command_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_)
+            + toString(this._command_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        if(this._command_ == child)
+        {
+            this._command_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        if(this._command_ == oldChild)
+        {
+            setCommand((PCommand) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AEvarCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AEvarCommand.java
new file mode 100644
index 0000000..778af03
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AEvarCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AEvarCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+
+    public AEvarCommand()
+    {
+        // Constructor
+    }
+
+    public AEvarCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AEvarCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAEvarCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AExistsExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AExistsExpression.java
new file mode 100644
index 0000000..ba44624
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AExistsExpression.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AExistsExpression extends PExpression
+{
+    private PExpression _expression_;
+
+    public AExistsExpression()
+    {
+        // Constructor
+    }
+
+    public AExistsExpression(
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AExistsExpression(
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAExistsExpression(this);
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AExpandVariable.java b/src/com/google/clearsilver/jsilver/syntax/node/AExpandVariable.java
new file mode 100644
index 0000000..bfb6996
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AExpandVariable.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AExpandVariable extends PVariable
+{
+    private PVariable _parent_;
+    private PExpression _child_;
+
+    public AExpandVariable()
+    {
+        // Constructor
+    }
+
+    public AExpandVariable(
+        @SuppressWarnings("hiding") PVariable _parent_,
+        @SuppressWarnings("hiding") PExpression _child_)
+    {
+        // Constructor
+        setParent(_parent_);
+
+        setChild(_child_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AExpandVariable(
+            cloneNode(this._parent_),
+            cloneNode(this._child_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAExpandVariable(this);
+    }
+
+    public PVariable getParent()
+    {
+        return this._parent_;
+    }
+
+    public void setParent(PVariable node)
+    {
+        if(this._parent_ != null)
+        {
+            this._parent_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._parent_ = node;
+    }
+
+    public PExpression getChild()
+    {
+        return this._child_;
+    }
+
+    public void setChild(PExpression node)
+    {
+        if(this._child_ != null)
+        {
+            this._child_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._child_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._parent_)
+            + toString(this._child_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._parent_ == child)
+        {
+            this._parent_ = null;
+            return;
+        }
+
+        if(this._child_ == child)
+        {
+            this._child_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._parent_ == oldChild)
+        {
+            setParent((PVariable) newChild);
+            return;
+        }
+
+        if(this._child_ == oldChild)
+        {
+            setChild((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AFunctionExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AFunctionExpression.java
new file mode 100644
index 0000000..0d091f2
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AFunctionExpression.java
@@ -0,0 +1,144 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import java.util.*;
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AFunctionExpression extends PExpression
+{
+    private PVariable _name_;
+    private final LinkedList<PExpression> _args_ = new LinkedList<PExpression>();
+
+    public AFunctionExpression()
+    {
+        // Constructor
+    }
+
+    public AFunctionExpression(
+        @SuppressWarnings("hiding") PVariable _name_,
+        @SuppressWarnings("hiding") List<PExpression> _args_)
+    {
+        // Constructor
+        setName(_name_);
+
+        setArgs(_args_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AFunctionExpression(
+            cloneNode(this._name_),
+            cloneList(this._args_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAFunctionExpression(this);
+    }
+
+    public PVariable getName()
+    {
+        return this._name_;
+    }
+
+    public void setName(PVariable node)
+    {
+        if(this._name_ != null)
+        {
+            this._name_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._name_ = node;
+    }
+
+    public LinkedList<PExpression> getArgs()
+    {
+        return this._args_;
+    }
+
+    public void setArgs(List<PExpression> list)
+    {
+        this._args_.clear();
+        this._args_.addAll(list);
+        for(PExpression e : list)
+        {
+            if(e.parent() != null)
+            {
+                e.parent().removeChild(e);
+            }
+
+            e.parent(this);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._name_)
+            + toString(this._args_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._name_ == child)
+        {
+            this._name_ = null;
+            return;
+        }
+
+        if(this._args_.remove(child))
+        {
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._name_ == oldChild)
+        {
+            setName((PVariable) newChild);
+            return;
+        }
+
+        for(ListIterator<PExpression> i = this._args_.listIterator(); i.hasNext();)
+        {
+            if(i.next() == oldChild)
+            {
+                if(newChild != null)
+                {
+                    i.set((PExpression) newChild);
+                    newChild.parent(this);
+                    oldChild.parent(null);
+                    return;
+                }
+
+                i.remove();
+                oldChild.parent(null);
+                return;
+            }
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AGtExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AGtExpression.java
new file mode 100644
index 0000000..0c7e399
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AGtExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AGtExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public AGtExpression()
+    {
+        // Constructor
+    }
+
+    public AGtExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AGtExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAGtExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AGteExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AGteExpression.java
new file mode 100644
index 0000000..800a3af
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AGteExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AGteExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public AGteExpression()
+    {
+        // Constructor
+    }
+
+    public AGteExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AGteExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAGteExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AHardIncludeCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AHardIncludeCommand.java
new file mode 100644
index 0000000..2e734a3
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AHardIncludeCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AHardIncludeCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+
+    public AHardIncludeCommand()
+    {
+        // Constructor
+    }
+
+    public AHardIncludeCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AHardIncludeCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAHardIncludeCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AHardLincludeCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AHardLincludeCommand.java
new file mode 100644
index 0000000..7a9203b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AHardLincludeCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AHardLincludeCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+
+    public AHardLincludeCommand()
+    {
+        // Constructor
+    }
+
+    public AHardLincludeCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AHardLincludeCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAHardLincludeCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AHexExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AHexExpression.java
new file mode 100644
index 0000000..5bdc21b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AHexExpression.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AHexExpression extends PExpression
+{
+    private THexNumber _value_;
+
+    public AHexExpression()
+    {
+        // Constructor
+    }
+
+    public AHexExpression(
+        @SuppressWarnings("hiding") THexNumber _value_)
+    {
+        // Constructor
+        setValue(_value_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AHexExpression(
+            cloneNode(this._value_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAHexExpression(this);
+    }
+
+    public THexNumber getValue()
+    {
+        return this._value_;
+    }
+
+    public void setValue(THexNumber node)
+    {
+        if(this._value_ != null)
+        {
+            this._value_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._value_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._value_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._value_ == child)
+        {
+            this._value_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._value_ == oldChild)
+        {
+            setValue((THexNumber) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AHexNumberVariable.java b/src/com/google/clearsilver/jsilver/syntax/node/AHexNumberVariable.java
new file mode 100644
index 0000000..3b7d3f2
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AHexNumberVariable.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AHexNumberVariable extends PVariable
+{
+    private THexNumber _hexNumber_;
+
+    public AHexNumberVariable()
+    {
+        // Constructor
+    }
+
+    public AHexNumberVariable(
+        @SuppressWarnings("hiding") THexNumber _hexNumber_)
+    {
+        // Constructor
+        setHexNumber(_hexNumber_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AHexNumberVariable(
+            cloneNode(this._hexNumber_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAHexNumberVariable(this);
+    }
+
+    public THexNumber getHexNumber()
+    {
+        return this._hexNumber_;
+    }
+
+    public void setHexNumber(THexNumber node)
+    {
+        if(this._hexNumber_ != null)
+        {
+            this._hexNumber_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._hexNumber_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._hexNumber_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._hexNumber_ == child)
+        {
+            this._hexNumber_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._hexNumber_ == oldChild)
+        {
+            setHexNumber((THexNumber) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AIfCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AIfCommand.java
new file mode 100644
index 0000000..bdf5c01
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AIfCommand.java
@@ -0,0 +1,223 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AIfCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+    private PCommand _block_;
+    private PCommand _otherwise_;
+
+    public AIfCommand()
+    {
+        // Constructor
+    }
+
+    public AIfCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_,
+        @SuppressWarnings("hiding") PCommand _block_,
+        @SuppressWarnings("hiding") PCommand _otherwise_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+        setBlock(_block_);
+
+        setOtherwise(_otherwise_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AIfCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_),
+            cloneNode(this._block_),
+            cloneNode(this._otherwise_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAIfCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    public PCommand getBlock()
+    {
+        return this._block_;
+    }
+
+    public void setBlock(PCommand node)
+    {
+        if(this._block_ != null)
+        {
+            this._block_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._block_ = node;
+    }
+
+    public PCommand getOtherwise()
+    {
+        return this._otherwise_;
+    }
+
+    public void setOtherwise(PCommand node)
+    {
+        if(this._otherwise_ != null)
+        {
+            this._otherwise_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._otherwise_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_)
+            + toString(this._block_)
+            + toString(this._otherwise_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        if(this._block_ == child)
+        {
+            this._block_ = null;
+            return;
+        }
+
+        if(this._otherwise_ == child)
+        {
+            this._otherwise_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        if(this._block_ == oldChild)
+        {
+            setBlock((PCommand) newChild);
+            return;
+        }
+
+        if(this._otherwise_ == oldChild)
+        {
+            setOtherwise((PCommand) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AIncludeCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AIncludeCommand.java
new file mode 100644
index 0000000..25b8e4e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AIncludeCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AIncludeCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+
+    public AIncludeCommand()
+    {
+        // Constructor
+    }
+
+    public AIncludeCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AIncludeCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAIncludeCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AInlineCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AInlineCommand.java
new file mode 100644
index 0000000..2486ec3
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AInlineCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AInlineCommand extends PCommand
+{
+    private PPosition _position_;
+    private PCommand _command_;
+
+    public AInlineCommand()
+    {
+        // Constructor
+    }
+
+    public AInlineCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PCommand _command_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setCommand(_command_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AInlineCommand(
+            cloneNode(this._position_),
+            cloneNode(this._command_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAInlineCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PCommand getCommand()
+    {
+        return this._command_;
+    }
+
+    public void setCommand(PCommand node)
+    {
+        if(this._command_ != null)
+        {
+            this._command_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._command_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._command_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._command_ == child)
+        {
+            this._command_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._command_ == oldChild)
+        {
+            setCommand((PCommand) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ALincludeCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ALincludeCommand.java
new file mode 100644
index 0000000..cd9c56e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ALincludeCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ALincludeCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+
+    public ALincludeCommand()
+    {
+        // Constructor
+    }
+
+    public ALincludeCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ALincludeCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseALincludeCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ALoopCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ALoopCommand.java
new file mode 100644
index 0000000..5213002
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ALoopCommand.java
@@ -0,0 +1,266 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ALoopCommand extends PCommand
+{
+    private PPosition _position_;
+    private PVariable _variable_;
+    private PExpression _start_;
+    private PExpression _end_;
+    private PCommand _command_;
+
+    public ALoopCommand()
+    {
+        // Constructor
+    }
+
+    public ALoopCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PVariable _variable_,
+        @SuppressWarnings("hiding") PExpression _start_,
+        @SuppressWarnings("hiding") PExpression _end_,
+        @SuppressWarnings("hiding") PCommand _command_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setVariable(_variable_);
+
+        setStart(_start_);
+
+        setEnd(_end_);
+
+        setCommand(_command_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ALoopCommand(
+            cloneNode(this._position_),
+            cloneNode(this._variable_),
+            cloneNode(this._start_),
+            cloneNode(this._end_),
+            cloneNode(this._command_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseALoopCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PVariable getVariable()
+    {
+        return this._variable_;
+    }
+
+    public void setVariable(PVariable node)
+    {
+        if(this._variable_ != null)
+        {
+            this._variable_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._variable_ = node;
+    }
+
+    public PExpression getStart()
+    {
+        return this._start_;
+    }
+
+    public void setStart(PExpression node)
+    {
+        if(this._start_ != null)
+        {
+            this._start_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._start_ = node;
+    }
+
+    public PExpression getEnd()
+    {
+        return this._end_;
+    }
+
+    public void setEnd(PExpression node)
+    {
+        if(this._end_ != null)
+        {
+            this._end_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._end_ = node;
+    }
+
+    public PCommand getCommand()
+    {
+        return this._command_;
+    }
+
+    public void setCommand(PCommand node)
+    {
+        if(this._command_ != null)
+        {
+            this._command_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._command_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._variable_)
+            + toString(this._start_)
+            + toString(this._end_)
+            + toString(this._command_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._variable_ == child)
+        {
+            this._variable_ = null;
+            return;
+        }
+
+        if(this._start_ == child)
+        {
+            this._start_ = null;
+            return;
+        }
+
+        if(this._end_ == child)
+        {
+            this._end_ = null;
+            return;
+        }
+
+        if(this._command_ == child)
+        {
+            this._command_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._variable_ == oldChild)
+        {
+            setVariable((PVariable) newChild);
+            return;
+        }
+
+        if(this._start_ == oldChild)
+        {
+            setStart((PExpression) newChild);
+            return;
+        }
+
+        if(this._end_ == oldChild)
+        {
+            setEnd((PExpression) newChild);
+            return;
+        }
+
+        if(this._command_ == oldChild)
+        {
+            setCommand((PCommand) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ALoopIncCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ALoopIncCommand.java
new file mode 100644
index 0000000..85ae6f7
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ALoopIncCommand.java
@@ -0,0 +1,309 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ALoopIncCommand extends PCommand
+{
+    private PPosition _position_;
+    private PVariable _variable_;
+    private PExpression _start_;
+    private PExpression _end_;
+    private PExpression _increment_;
+    private PCommand _command_;
+
+    public ALoopIncCommand()
+    {
+        // Constructor
+    }
+
+    public ALoopIncCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PVariable _variable_,
+        @SuppressWarnings("hiding") PExpression _start_,
+        @SuppressWarnings("hiding") PExpression _end_,
+        @SuppressWarnings("hiding") PExpression _increment_,
+        @SuppressWarnings("hiding") PCommand _command_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setVariable(_variable_);
+
+        setStart(_start_);
+
+        setEnd(_end_);
+
+        setIncrement(_increment_);
+
+        setCommand(_command_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ALoopIncCommand(
+            cloneNode(this._position_),
+            cloneNode(this._variable_),
+            cloneNode(this._start_),
+            cloneNode(this._end_),
+            cloneNode(this._increment_),
+            cloneNode(this._command_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseALoopIncCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PVariable getVariable()
+    {
+        return this._variable_;
+    }
+
+    public void setVariable(PVariable node)
+    {
+        if(this._variable_ != null)
+        {
+            this._variable_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._variable_ = node;
+    }
+
+    public PExpression getStart()
+    {
+        return this._start_;
+    }
+
+    public void setStart(PExpression node)
+    {
+        if(this._start_ != null)
+        {
+            this._start_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._start_ = node;
+    }
+
+    public PExpression getEnd()
+    {
+        return this._end_;
+    }
+
+    public void setEnd(PExpression node)
+    {
+        if(this._end_ != null)
+        {
+            this._end_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._end_ = node;
+    }
+
+    public PExpression getIncrement()
+    {
+        return this._increment_;
+    }
+
+    public void setIncrement(PExpression node)
+    {
+        if(this._increment_ != null)
+        {
+            this._increment_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._increment_ = node;
+    }
+
+    public PCommand getCommand()
+    {
+        return this._command_;
+    }
+
+    public void setCommand(PCommand node)
+    {
+        if(this._command_ != null)
+        {
+            this._command_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._command_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._variable_)
+            + toString(this._start_)
+            + toString(this._end_)
+            + toString(this._increment_)
+            + toString(this._command_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._variable_ == child)
+        {
+            this._variable_ = null;
+            return;
+        }
+
+        if(this._start_ == child)
+        {
+            this._start_ = null;
+            return;
+        }
+
+        if(this._end_ == child)
+        {
+            this._end_ = null;
+            return;
+        }
+
+        if(this._increment_ == child)
+        {
+            this._increment_ = null;
+            return;
+        }
+
+        if(this._command_ == child)
+        {
+            this._command_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._variable_ == oldChild)
+        {
+            setVariable((PVariable) newChild);
+            return;
+        }
+
+        if(this._start_ == oldChild)
+        {
+            setStart((PExpression) newChild);
+            return;
+        }
+
+        if(this._end_ == oldChild)
+        {
+            setEnd((PExpression) newChild);
+            return;
+        }
+
+        if(this._increment_ == oldChild)
+        {
+            setIncrement((PExpression) newChild);
+            return;
+        }
+
+        if(this._command_ == oldChild)
+        {
+            setCommand((PCommand) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ALoopToCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ALoopToCommand.java
new file mode 100644
index 0000000..d2a3d49
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ALoopToCommand.java
@@ -0,0 +1,223 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ALoopToCommand extends PCommand
+{
+    private PPosition _position_;
+    private PVariable _variable_;
+    private PExpression _expression_;
+    private PCommand _command_;
+
+    public ALoopToCommand()
+    {
+        // Constructor
+    }
+
+    public ALoopToCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PVariable _variable_,
+        @SuppressWarnings("hiding") PExpression _expression_,
+        @SuppressWarnings("hiding") PCommand _command_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setVariable(_variable_);
+
+        setExpression(_expression_);
+
+        setCommand(_command_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ALoopToCommand(
+            cloneNode(this._position_),
+            cloneNode(this._variable_),
+            cloneNode(this._expression_),
+            cloneNode(this._command_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseALoopToCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PVariable getVariable()
+    {
+        return this._variable_;
+    }
+
+    public void setVariable(PVariable node)
+    {
+        if(this._variable_ != null)
+        {
+            this._variable_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._variable_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    public PCommand getCommand()
+    {
+        return this._command_;
+    }
+
+    public void setCommand(PCommand node)
+    {
+        if(this._command_ != null)
+        {
+            this._command_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._command_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._variable_)
+            + toString(this._expression_)
+            + toString(this._command_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._variable_ == child)
+        {
+            this._variable_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        if(this._command_ == child)
+        {
+            this._command_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._variable_ == oldChild)
+        {
+            setVariable((PVariable) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        if(this._command_ == oldChild)
+        {
+            setCommand((PCommand) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ALtExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ALtExpression.java
new file mode 100644
index 0000000..85d6bcf
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ALtExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ALtExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public ALtExpression()
+    {
+        // Constructor
+    }
+
+    public ALtExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ALtExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseALtExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ALteExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ALteExpression.java
new file mode 100644
index 0000000..f5fbffe
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ALteExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ALteExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public ALteExpression()
+    {
+        // Constructor
+    }
+
+    public ALteExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ALteExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseALteExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ALvarCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ALvarCommand.java
new file mode 100644
index 0000000..09a5a42
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ALvarCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ALvarCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+
+    public ALvarCommand()
+    {
+        // Constructor
+    }
+
+    public ALvarCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ALvarCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseALvarCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AModuloExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AModuloExpression.java
new file mode 100644
index 0000000..be8fc13
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AModuloExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AModuloExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public AModuloExpression()
+    {
+        // Constructor
+    }
+
+    public AModuloExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AModuloExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAModuloExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AMultipleCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AMultipleCommand.java
new file mode 100644
index 0000000..625a78b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AMultipleCommand.java
@@ -0,0 +1,101 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import java.util.*;
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AMultipleCommand extends PCommand
+{
+    private final LinkedList<PCommand> _command_ = new LinkedList<PCommand>();
+
+    public AMultipleCommand()
+    {
+        // Constructor
+    }
+
+    public AMultipleCommand(
+        @SuppressWarnings("hiding") List<PCommand> _command_)
+    {
+        // Constructor
+        setCommand(_command_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AMultipleCommand(
+            cloneList(this._command_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAMultipleCommand(this);
+    }
+
+    public LinkedList<PCommand> getCommand()
+    {
+        return this._command_;
+    }
+
+    public void setCommand(List<PCommand> list)
+    {
+        this._command_.clear();
+        this._command_.addAll(list);
+        for(PCommand e : list)
+        {
+            if(e.parent() != null)
+            {
+                e.parent().removeChild(e);
+            }
+
+            e.parent(this);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._command_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._command_.remove(child))
+        {
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        for(ListIterator<PCommand> i = this._command_.listIterator(); i.hasNext();)
+        {
+            if(i.next() == oldChild)
+            {
+                if(newChild != null)
+                {
+                    i.set((PCommand) newChild);
+                    newChild.parent(this);
+                    oldChild.parent(null);
+                    return;
+                }
+
+                i.remove();
+                oldChild.parent(null);
+                return;
+            }
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AMultiplyExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AMultiplyExpression.java
new file mode 100644
index 0000000..602ea56
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AMultiplyExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AMultiplyExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public AMultiplyExpression()
+    {
+        // Constructor
+    }
+
+    public AMultiplyExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AMultiplyExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAMultiplyExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ANameCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ANameCommand.java
new file mode 100644
index 0000000..824743b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ANameCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ANameCommand extends PCommand
+{
+    private PPosition _position_;
+    private PVariable _variable_;
+
+    public ANameCommand()
+    {
+        // Constructor
+    }
+
+    public ANameCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PVariable _variable_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setVariable(_variable_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ANameCommand(
+            cloneNode(this._position_),
+            cloneNode(this._variable_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseANameCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PVariable getVariable()
+    {
+        return this._variable_;
+    }
+
+    public void setVariable(PVariable node)
+    {
+        if(this._variable_ != null)
+        {
+            this._variable_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._variable_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._variable_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._variable_ == child)
+        {
+            this._variable_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._variable_ == oldChild)
+        {
+            setVariable((PVariable) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ANameVariable.java b/src/com/google/clearsilver/jsilver/syntax/node/ANameVariable.java
new file mode 100644
index 0000000..9795849
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ANameVariable.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ANameVariable extends PVariable
+{
+    private TWord _word_;
+
+    public ANameVariable()
+    {
+        // Constructor
+    }
+
+    public ANameVariable(
+        @SuppressWarnings("hiding") TWord _word_)
+    {
+        // Constructor
+        setWord(_word_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ANameVariable(
+            cloneNode(this._word_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseANameVariable(this);
+    }
+
+    public TWord getWord()
+    {
+        return this._word_;
+    }
+
+    public void setWord(TWord node)
+    {
+        if(this._word_ != null)
+        {
+            this._word_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._word_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._word_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._word_ == child)
+        {
+            this._word_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._word_ == oldChild)
+        {
+            setWord((TWord) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ANeExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ANeExpression.java
new file mode 100644
index 0000000..633e407
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ANeExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ANeExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public ANeExpression()
+    {
+        // Constructor
+    }
+
+    public ANeExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ANeExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseANeExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ANegativeExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ANegativeExpression.java
new file mode 100644
index 0000000..10fd541
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ANegativeExpression.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ANegativeExpression extends PExpression
+{
+    private PExpression _expression_;
+
+    public ANegativeExpression()
+    {
+        // Constructor
+    }
+
+    public ANegativeExpression(
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ANegativeExpression(
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseANegativeExpression(this);
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ANoopCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ANoopCommand.java
new file mode 100644
index 0000000..030df3c
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ANoopCommand.java
@@ -0,0 +1,46 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ANoopCommand extends PCommand
+{
+
+    public ANoopCommand()
+    {
+        // Constructor
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ANoopCommand();
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseANoopCommand(this);
+    }
+
+    @Override
+    public String toString()
+    {
+        return "";
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ANoopExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ANoopExpression.java
new file mode 100644
index 0000000..ddee503
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ANoopExpression.java
@@ -0,0 +1,46 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ANoopExpression extends PExpression
+{
+
+    public ANoopExpression()
+    {
+        // Constructor
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ANoopExpression();
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseANoopExpression(this);
+    }
+
+    @Override
+    public String toString()
+    {
+        return "";
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ANotExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ANotExpression.java
new file mode 100644
index 0000000..9216757
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ANotExpression.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ANotExpression extends PExpression
+{
+    private PExpression _expression_;
+
+    public ANotExpression()
+    {
+        // Constructor
+    }
+
+    public ANotExpression(
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ANotExpression(
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseANotExpression(this);
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ANumericAddExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ANumericAddExpression.java
new file mode 100644
index 0000000..4e5ed3c
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ANumericAddExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ANumericAddExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public ANumericAddExpression()
+    {
+        // Constructor
+    }
+
+    public ANumericAddExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ANumericAddExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseANumericAddExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ANumericEqExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ANumericEqExpression.java
new file mode 100644
index 0000000..50c2f35
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ANumericEqExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ANumericEqExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public ANumericEqExpression()
+    {
+        // Constructor
+    }
+
+    public ANumericEqExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ANumericEqExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseANumericEqExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ANumericExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ANumericExpression.java
new file mode 100644
index 0000000..c600c86
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ANumericExpression.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ANumericExpression extends PExpression
+{
+    private PExpression _expression_;
+
+    public ANumericExpression()
+    {
+        // Constructor
+    }
+
+    public ANumericExpression(
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ANumericExpression(
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseANumericExpression(this);
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ANumericNeExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ANumericNeExpression.java
new file mode 100644
index 0000000..7bd0f32
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ANumericNeExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ANumericNeExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public ANumericNeExpression()
+    {
+        // Constructor
+    }
+
+    public ANumericNeExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ANumericNeExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseANumericNeExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AOptimizedMultipleCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AOptimizedMultipleCommand.java
new file mode 100644
index 0000000..ae9ad6c
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AOptimizedMultipleCommand.java
@@ -0,0 +1,62 @@
+// Copyright 2008 Google Inc. All Rights Reserved.
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import java.util.LinkedList;
+
+/**
+ * Replacement for SableCC generated AMultipleCommand. Iterates much faster. Important because this
+ * iteration is called a lot.
+ * 
+ * NOTE: Because the SableCC generated code contains methods of package visibility that need to be
+ * overriden, this class needs to reside in the same package.
+ * 
+ * @see com.google.clearsilver.jsilver.syntax.SyntaxTreeOptimizer
+ */
+public class AOptimizedMultipleCommand extends PCommand {
+
+  private final PCommand[] commands;
+
+  public AOptimizedMultipleCommand(AMultipleCommand originalNode) {
+    LinkedList<PCommand> originalChildCommands = originalNode.getCommand();
+    commands = new PCommand[originalChildCommands.size()];
+    originalChildCommands.toArray(commands);
+    for (int i = 0; i < commands.length; i++) {
+      commands[i].parent(this); // set parent.
+    }
+  }
+
+  @Override
+  public Object clone() {
+    return this; // Immutable object. Clone not necessary.
+  }
+
+  @Override
+  void removeChild(Node child) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  void replaceChild(Node oldChild, Node newChild) {
+    if (newChild == null) {
+      throw new IllegalArgumentException("newChild cannot be null.");
+    }
+    // Replace child
+    for (int i = 0; i < commands.length; i++) {
+      if (commands[i] == oldChild) {
+        commands[i] = (PCommand) newChild;
+        newChild.parent(this);
+        oldChild.parent(null);
+        return;
+      }
+    }
+    throw new RuntimeException("Not a child.");
+  }
+
+  @Override
+  public void apply(Switch sw) {
+    for (int i = 0; i < commands.length; i++) {
+      commands[i].apply(sw);
+    }
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AOrExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AOrExpression.java
new file mode 100644
index 0000000..1cad952
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AOrExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AOrExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public AOrExpression()
+    {
+        // Constructor
+    }
+
+    public AOrExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AOrExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAOrExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ASequenceExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ASequenceExpression.java
new file mode 100644
index 0000000..e99456e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ASequenceExpression.java
@@ -0,0 +1,101 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import java.util.*;
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ASequenceExpression extends PExpression
+{
+    private final LinkedList<PExpression> _args_ = new LinkedList<PExpression>();
+
+    public ASequenceExpression()
+    {
+        // Constructor
+    }
+
+    public ASequenceExpression(
+        @SuppressWarnings("hiding") List<PExpression> _args_)
+    {
+        // Constructor
+        setArgs(_args_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ASequenceExpression(
+            cloneList(this._args_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseASequenceExpression(this);
+    }
+
+    public LinkedList<PExpression> getArgs()
+    {
+        return this._args_;
+    }
+
+    public void setArgs(List<PExpression> list)
+    {
+        this._args_.clear();
+        this._args_.addAll(list);
+        for(PExpression e : list)
+        {
+            if(e.parent() != null)
+            {
+                e.parent().removeChild(e);
+            }
+
+            e.parent(this);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._args_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._args_.remove(child))
+        {
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        for(ListIterator<PExpression> i = this._args_.listIterator(); i.hasNext();)
+        {
+            if(i.next() == oldChild)
+            {
+                if(newChild != null)
+                {
+                    i.set((PExpression) newChild);
+                    newChild.parent(this);
+                    oldChild.parent(null);
+                    return;
+                }
+
+                i.remove();
+                oldChild.parent(null);
+                return;
+            }
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ASetCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/ASetCommand.java
new file mode 100644
index 0000000..be0d88b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ASetCommand.java
@@ -0,0 +1,180 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ASetCommand extends PCommand
+{
+    private PPosition _position_;
+    private PVariable _variable_;
+    private PExpression _expression_;
+
+    public ASetCommand()
+    {
+        // Constructor
+    }
+
+    public ASetCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PVariable _variable_,
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setVariable(_variable_);
+
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ASetCommand(
+            cloneNode(this._position_),
+            cloneNode(this._variable_),
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseASetCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PVariable getVariable()
+    {
+        return this._variable_;
+    }
+
+    public void setVariable(PVariable node)
+    {
+        if(this._variable_ != null)
+        {
+            this._variable_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._variable_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._variable_)
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._variable_ == child)
+        {
+            this._variable_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._variable_ == oldChild)
+        {
+            setVariable((PVariable) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AStringExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AStringExpression.java
new file mode 100644
index 0000000..16c8a98
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AStringExpression.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AStringExpression extends PExpression
+{
+    private TString _value_;
+
+    public AStringExpression()
+    {
+        // Constructor
+    }
+
+    public AStringExpression(
+        @SuppressWarnings("hiding") TString _value_)
+    {
+        // Constructor
+        setValue(_value_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AStringExpression(
+            cloneNode(this._value_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAStringExpression(this);
+    }
+
+    public TString getValue()
+    {
+        return this._value_;
+    }
+
+    public void setValue(TString node)
+    {
+        if(this._value_ != null)
+        {
+            this._value_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._value_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._value_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._value_ == child)
+        {
+            this._value_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._value_ == oldChild)
+        {
+            setValue((TString) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/ASubtractExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/ASubtractExpression.java
new file mode 100644
index 0000000..3cef04d
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/ASubtractExpression.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class ASubtractExpression extends PExpression
+{
+    private PExpression _left_;
+    private PExpression _right_;
+
+    public ASubtractExpression()
+    {
+        // Constructor
+    }
+
+    public ASubtractExpression(
+        @SuppressWarnings("hiding") PExpression _left_,
+        @SuppressWarnings("hiding") PExpression _right_)
+    {
+        // Constructor
+        setLeft(_left_);
+
+        setRight(_right_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new ASubtractExpression(
+            cloneNode(this._left_),
+            cloneNode(this._right_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseASubtractExpression(this);
+    }
+
+    public PExpression getLeft()
+    {
+        return this._left_;
+    }
+
+    public void setLeft(PExpression node)
+    {
+        if(this._left_ != null)
+        {
+            this._left_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._left_ = node;
+    }
+
+    public PExpression getRight()
+    {
+        return this._right_;
+    }
+
+    public void setRight(PExpression node)
+    {
+        if(this._right_ != null)
+        {
+            this._right_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._right_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._left_)
+            + toString(this._right_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._left_ == child)
+        {
+            this._left_ = null;
+            return;
+        }
+
+        if(this._right_ == child)
+        {
+            this._right_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._left_ == oldChild)
+        {
+            setLeft((PExpression) newChild);
+            return;
+        }
+
+        if(this._right_ == oldChild)
+        {
+            setRight((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AUvarCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AUvarCommand.java
new file mode 100644
index 0000000..4e096ae
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AUvarCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AUvarCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+
+    public AUvarCommand()
+    {
+        // Constructor
+    }
+
+    public AUvarCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AUvarCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAUvarCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AVarCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AVarCommand.java
new file mode 100644
index 0000000..7411148
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AVarCommand.java
@@ -0,0 +1,137 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AVarCommand extends PCommand
+{
+    private PPosition _position_;
+    private PExpression _expression_;
+
+    public AVarCommand()
+    {
+        // Constructor
+    }
+
+    public AVarCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PExpression _expression_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setExpression(_expression_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AVarCommand(
+            cloneNode(this._position_),
+            cloneNode(this._expression_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAVarCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._expression_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AVariableExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/AVariableExpression.java
new file mode 100644
index 0000000..5e9bc4d
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AVariableExpression.java
@@ -0,0 +1,94 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AVariableExpression extends PExpression
+{
+    private PVariable _variable_;
+
+    public AVariableExpression()
+    {
+        // Constructor
+    }
+
+    public AVariableExpression(
+        @SuppressWarnings("hiding") PVariable _variable_)
+    {
+        // Constructor
+        setVariable(_variable_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AVariableExpression(
+            cloneNode(this._variable_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAVariableExpression(this);
+    }
+
+    public PVariable getVariable()
+    {
+        return this._variable_;
+    }
+
+    public void setVariable(PVariable node)
+    {
+        if(this._variable_ != null)
+        {
+            this._variable_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._variable_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._variable_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._variable_ == child)
+        {
+            this._variable_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._variable_ == oldChild)
+        {
+            setVariable((PVariable) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/AWithCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/AWithCommand.java
new file mode 100644
index 0000000..759a5ca
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/AWithCommand.java
@@ -0,0 +1,223 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class AWithCommand extends PCommand
+{
+    private PPosition _position_;
+    private PVariable _variable_;
+    private PExpression _expression_;
+    private PCommand _command_;
+
+    public AWithCommand()
+    {
+        // Constructor
+    }
+
+    public AWithCommand(
+        @SuppressWarnings("hiding") PPosition _position_,
+        @SuppressWarnings("hiding") PVariable _variable_,
+        @SuppressWarnings("hiding") PExpression _expression_,
+        @SuppressWarnings("hiding") PCommand _command_)
+    {
+        // Constructor
+        setPosition(_position_);
+
+        setVariable(_variable_);
+
+        setExpression(_expression_);
+
+        setCommand(_command_);
+
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new AWithCommand(
+            cloneNode(this._position_),
+            cloneNode(this._variable_),
+            cloneNode(this._expression_),
+            cloneNode(this._command_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseAWithCommand(this);
+    }
+
+    public PPosition getPosition()
+    {
+        return this._position_;
+    }
+
+    public void setPosition(PPosition node)
+    {
+        if(this._position_ != null)
+        {
+            this._position_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._position_ = node;
+    }
+
+    public PVariable getVariable()
+    {
+        return this._variable_;
+    }
+
+    public void setVariable(PVariable node)
+    {
+        if(this._variable_ != null)
+        {
+            this._variable_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._variable_ = node;
+    }
+
+    public PExpression getExpression()
+    {
+        return this._expression_;
+    }
+
+    public void setExpression(PExpression node)
+    {
+        if(this._expression_ != null)
+        {
+            this._expression_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._expression_ = node;
+    }
+
+    public PCommand getCommand()
+    {
+        return this._command_;
+    }
+
+    public void setCommand(PCommand node)
+    {
+        if(this._command_ != null)
+        {
+            this._command_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._command_ = node;
+    }
+
+    @Override
+    public String toString()
+    {
+        return ""
+            + toString(this._position_)
+            + toString(this._variable_)
+            + toString(this._expression_)
+            + toString(this._command_);
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        // Remove child
+        if(this._position_ == child)
+        {
+            this._position_ = null;
+            return;
+        }
+
+        if(this._variable_ == child)
+        {
+            this._variable_ = null;
+            return;
+        }
+
+        if(this._expression_ == child)
+        {
+            this._expression_ = null;
+            return;
+        }
+
+        if(this._command_ == child)
+        {
+            this._command_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        // Replace child
+        if(this._position_ == oldChild)
+        {
+            setPosition((PPosition) newChild);
+            return;
+        }
+
+        if(this._variable_ == oldChild)
+        {
+            setVariable((PVariable) newChild);
+            return;
+        }
+
+        if(this._expression_ == oldChild)
+        {
+            setExpression((PExpression) newChild);
+            return;
+        }
+
+        if(this._command_ == oldChild)
+        {
+            setCommand((PCommand) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/EOF.java b/src/com/google/clearsilver/jsilver/syntax/node/EOF.java
new file mode 100644
index 0000000..d5d10a8
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/EOF.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class EOF extends Token
+{
+    public EOF()
+    {
+        setText("");
+    }
+
+    public EOF(int line, int pos)
+    {
+        setText("");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new EOF(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseEOF(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/Node.java b/src/com/google/clearsilver/jsilver/syntax/node/Node.java
new file mode 100644
index 0000000..a2e5e6e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/Node.java
@@ -0,0 +1,77 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import java.util.*;
+
+@SuppressWarnings("nls")
+public abstract class Node implements Switchable, Cloneable
+{
+    private Node parent;
+
+    @Override
+    public abstract Object clone();
+
+    public Node parent()
+    {
+        return this.parent;
+    }
+
+    void parent(@SuppressWarnings("hiding") Node parent)
+    {
+        this.parent = parent;
+    }
+
+    abstract void removeChild(Node child);
+    abstract void replaceChild(Node oldChild, Node newChild);
+
+    public void replaceBy(Node node)
+    {
+        this.parent.replaceChild(this, node);
+    }
+
+    protected String toString(Node node)
+    {
+        if(node != null)
+        {
+            return node.toString();
+        }
+
+        return "";
+    }
+
+    protected String toString(List list)
+    {
+        StringBuffer s = new StringBuffer();
+
+        for(Iterator i = list.iterator(); i.hasNext();)
+        {
+            s.append(i.next());
+        }
+
+        return s.toString();
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T extends Node> T cloneNode(T node)
+    {
+        if(node != null)
+        {
+            return (T) node.clone();
+        }
+
+        return null;
+    }
+
+    protected <T> List<T> cloneList(List<T> list)
+    {
+        List<T> clone = new LinkedList<T>();
+
+        for(T n : list)
+        {
+            clone.add(n);
+        }
+
+        return clone;
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/PCommand.java b/src/com/google/clearsilver/jsilver/syntax/node/PCommand.java
new file mode 100644
index 0000000..a8386a5
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/PCommand.java
@@ -0,0 +1,8 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+public abstract class PCommand extends Node
+{
+    // Empty body
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/PExpression.java b/src/com/google/clearsilver/jsilver/syntax/node/PExpression.java
new file mode 100644
index 0000000..7864d07
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/PExpression.java
@@ -0,0 +1,8 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+public abstract class PExpression extends Node
+{
+    // Empty body
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/PPosition.java b/src/com/google/clearsilver/jsilver/syntax/node/PPosition.java
new file mode 100644
index 0000000..c44c8b4
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/PPosition.java
@@ -0,0 +1,8 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+public abstract class PPosition extends Node
+{
+    // Empty body
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/PVariable.java b/src/com/google/clearsilver/jsilver/syntax/node/PVariable.java
new file mode 100644
index 0000000..56d3ee7
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/PVariable.java
@@ -0,0 +1,8 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+public abstract class PVariable extends Node
+{
+    // Empty body
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/Start.java b/src/com/google/clearsilver/jsilver/syntax/node/Start.java
new file mode 100644
index 0000000..e004de7
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/Start.java
@@ -0,0 +1,132 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class Start extends Node
+{
+    private PCommand _pCommand_;
+    private EOF _eof_;
+
+    public Start()
+    {
+        // Empty body
+    }
+
+    public Start(
+        @SuppressWarnings("hiding") PCommand _pCommand_,
+        @SuppressWarnings("hiding") EOF _eof_)
+    {
+        setPCommand(_pCommand_);
+        setEOF(_eof_);
+    }
+
+    @Override
+    public Object clone()
+    {
+        return new Start(
+            cloneNode(this._pCommand_),
+            cloneNode(this._eof_));
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseStart(this);
+    }
+
+    public PCommand getPCommand()
+    {
+        return this._pCommand_;
+    }
+
+    public void setPCommand(PCommand node)
+    {
+        if(this._pCommand_ != null)
+        {
+            this._pCommand_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._pCommand_ = node;
+    }
+
+    public EOF getEOF()
+    {
+        return this._eof_;
+    }
+
+    public void setEOF(EOF node)
+    {
+        if(this._eof_ != null)
+        {
+            this._eof_.parent(null);
+        }
+
+        if(node != null)
+        {
+            if(node.parent() != null)
+            {
+                node.parent().removeChild(node);
+            }
+
+            node.parent(this);
+        }
+
+        this._eof_ = node;
+    }
+
+    @Override
+    void removeChild(Node child)
+    {
+        if(this._pCommand_ == child)
+        {
+            this._pCommand_ = null;
+            return;
+        }
+
+        if(this._eof_ == child)
+        {
+            this._eof_ = null;
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(Node oldChild, Node newChild)
+    {
+        if(this._pCommand_ == oldChild)
+        {
+            setPCommand((PCommand) newChild);
+            return;
+        }
+
+        if(this._eof_ == oldChild)
+        {
+            setEOF((EOF) newChild);
+            return;
+        }
+
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    public String toString()
+    {
+        return "" +
+            toString(this._pCommand_) +
+            toString(this._eof_);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/Switch.java b/src/com/google/clearsilver/jsilver/syntax/node/Switch.java
new file mode 100644
index 0000000..274e5ec
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/Switch.java
@@ -0,0 +1,8 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+public interface Switch
+{
+        // Empty body
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/Switchable.java b/src/com/google/clearsilver/jsilver/syntax/node/Switchable.java
new file mode 100644
index 0000000..dfc6a46
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/Switchable.java
@@ -0,0 +1,8 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+public interface Switchable
+{
+    void apply(Switch sw);
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TAlt.java b/src/com/google/clearsilver/jsilver/syntax/node/TAlt.java
new file mode 100644
index 0000000..90d065d
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TAlt.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TAlt extends Token
+{
+    public TAlt()
+    {
+        super.setText("alt");
+    }
+
+    public TAlt(int line, int pos)
+    {
+        super.setText("alt");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TAlt(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTAlt(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TAlt text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TAnd.java b/src/com/google/clearsilver/jsilver/syntax/node/TAnd.java
new file mode 100644
index 0000000..988d2a6
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TAnd.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TAnd extends Token
+{
+    public TAnd()
+    {
+        super.setText("&&");
+    }
+
+    public TAnd(int line, int pos)
+    {
+        super.setText("&&");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TAnd(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTAnd(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TAnd text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TArgWhitespace.java b/src/com/google/clearsilver/jsilver/syntax/node/TArgWhitespace.java
new file mode 100644
index 0000000..c918d5c
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TArgWhitespace.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TArgWhitespace extends Token
+{
+    public TArgWhitespace(String text)
+    {
+        setText(text);
+    }
+
+    public TArgWhitespace(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TArgWhitespace(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTArgWhitespace(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TAssignment.java b/src/com/google/clearsilver/jsilver/syntax/node/TAssignment.java
new file mode 100644
index 0000000..a2cae2f
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TAssignment.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TAssignment extends Token
+{
+    public TAssignment()
+    {
+        super.setText("=");
+    }
+
+    public TAssignment(int line, int pos)
+    {
+        super.setText("=");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TAssignment(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTAssignment(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TAssignment text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TAutoescape.java b/src/com/google/clearsilver/jsilver/syntax/node/TAutoescape.java
new file mode 100644
index 0000000..17267f6
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TAutoescape.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TAutoescape extends Token
+{
+    public TAutoescape()
+    {
+        super.setText("autoescape");
+    }
+
+    public TAutoescape(int line, int pos)
+    {
+        super.setText("autoescape");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TAutoescape(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTAutoescape(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TAutoescape text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TBang.java b/src/com/google/clearsilver/jsilver/syntax/node/TBang.java
new file mode 100644
index 0000000..4b2c08c
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TBang.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TBang extends Token
+{
+    public TBang()
+    {
+        super.setText("!");
+    }
+
+    public TBang(int line, int pos)
+    {
+        super.setText("!");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TBang(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTBang(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TBang text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TBracketClose.java b/src/com/google/clearsilver/jsilver/syntax/node/TBracketClose.java
new file mode 100644
index 0000000..72b9450
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TBracketClose.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TBracketClose extends Token
+{
+    public TBracketClose()
+    {
+        super.setText("]");
+    }
+
+    public TBracketClose(int line, int pos)
+    {
+        super.setText("]");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TBracketClose(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTBracketClose(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TBracketClose text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TBracketOpen.java b/src/com/google/clearsilver/jsilver/syntax/node/TBracketOpen.java
new file mode 100644
index 0000000..1d35ab3
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TBracketOpen.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TBracketOpen extends Token
+{
+    public TBracketOpen()
+    {
+        super.setText("[");
+    }
+
+    public TBracketOpen(int line, int pos)
+    {
+        super.setText("[");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TBracketOpen(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTBracketOpen(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TBracketOpen text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TCall.java b/src/com/google/clearsilver/jsilver/syntax/node/TCall.java
new file mode 100644
index 0000000..f919938
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TCall.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TCall extends Token
+{
+    public TCall()
+    {
+        super.setText("call");
+    }
+
+    public TCall(int line, int pos)
+    {
+        super.setText("call");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TCall(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTCall(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TCall text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TComma.java b/src/com/google/clearsilver/jsilver/syntax/node/TComma.java
new file mode 100644
index 0000000..77b57c0
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TComma.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TComma extends Token
+{
+    public TComma()
+    {
+        super.setText(",");
+    }
+
+    public TComma(int line, int pos)
+    {
+        super.setText(",");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TComma(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTComma(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TComma text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TCommandDelimiter.java b/src/com/google/clearsilver/jsilver/syntax/node/TCommandDelimiter.java
new file mode 100644
index 0000000..bee852f
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TCommandDelimiter.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TCommandDelimiter extends Token
+{
+    public TCommandDelimiter(String text)
+    {
+        setText(text);
+    }
+
+    public TCommandDelimiter(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TCommandDelimiter(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTCommandDelimiter(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TComment.java b/src/com/google/clearsilver/jsilver/syntax/node/TComment.java
new file mode 100644
index 0000000..a960743
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TComment.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TComment extends Token
+{
+    public TComment(String text)
+    {
+        setText(text);
+    }
+
+    public TComment(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TComment(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTComment(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TCommentStart.java b/src/com/google/clearsilver/jsilver/syntax/node/TCommentStart.java
new file mode 100644
index 0000000..07ddc9d
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TCommentStart.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TCommentStart extends Token
+{
+    public TCommentStart()
+    {
+        super.setText("#");
+    }
+
+    public TCommentStart(int line, int pos)
+    {
+        super.setText("#");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TCommentStart(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTCommentStart(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TCommentStart text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TContentType.java b/src/com/google/clearsilver/jsilver/syntax/node/TContentType.java
new file mode 100644
index 0000000..a433b91
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TContentType.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TContentType extends Token
+{
+    public TContentType()
+    {
+        super.setText("content-type");
+    }
+
+    public TContentType(int line, int pos)
+    {
+        super.setText("content-type");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TContentType(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTContentType(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TContentType text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TCsClose.java b/src/com/google/clearsilver/jsilver/syntax/node/TCsClose.java
new file mode 100644
index 0000000..f1d859d
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TCsClose.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TCsClose extends Token
+{
+    public TCsClose(String text)
+    {
+        setText(text);
+    }
+
+    public TCsClose(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TCsClose(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTCsClose(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TCsOpen.java b/src/com/google/clearsilver/jsilver/syntax/node/TCsOpen.java
new file mode 100644
index 0000000..d66e64e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TCsOpen.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TCsOpen extends Token
+{
+    public TCsOpen(String text)
+    {
+        setText(text);
+    }
+
+    public TCsOpen(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TCsOpen(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTCsOpen(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TData.java b/src/com/google/clearsilver/jsilver/syntax/node/TData.java
new file mode 100644
index 0000000..ee0f62f
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TData.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TData extends Token
+{
+    public TData(String text)
+    {
+        setText(text);
+    }
+
+    public TData(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TData(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTData(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TDecNumber.java b/src/com/google/clearsilver/jsilver/syntax/node/TDecNumber.java
new file mode 100644
index 0000000..8c77e0e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TDecNumber.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TDecNumber extends Token
+{
+    public TDecNumber(String text)
+    {
+        setText(text);
+    }
+
+    public TDecNumber(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TDecNumber(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTDecNumber(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TDef.java b/src/com/google/clearsilver/jsilver/syntax/node/TDef.java
new file mode 100644
index 0000000..006b4f4
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TDef.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TDef extends Token
+{
+    public TDef()
+    {
+        super.setText("def");
+    }
+
+    public TDef(int line, int pos)
+    {
+        super.setText("def");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TDef(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTDef(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TDef text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TDollar.java b/src/com/google/clearsilver/jsilver/syntax/node/TDollar.java
new file mode 100644
index 0000000..8a4f71b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TDollar.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TDollar extends Token
+{
+    public TDollar()
+    {
+        super.setText("$");
+    }
+
+    public TDollar(int line, int pos)
+    {
+        super.setText("$");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TDollar(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTDollar(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TDollar text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TDot.java b/src/com/google/clearsilver/jsilver/syntax/node/TDot.java
new file mode 100644
index 0000000..3fa70f5
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TDot.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TDot extends Token
+{
+    public TDot()
+    {
+        super.setText(".");
+    }
+
+    public TDot(int line, int pos)
+    {
+        super.setText(".");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TDot(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTDot(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TDot text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TEach.java b/src/com/google/clearsilver/jsilver/syntax/node/TEach.java
new file mode 100644
index 0000000..8b4a3aa
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TEach.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TEach extends Token
+{
+    public TEach()
+    {
+        super.setText("each");
+    }
+
+    public TEach(int line, int pos)
+    {
+        super.setText("each");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TEach(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTEach(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TEach text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TElse.java b/src/com/google/clearsilver/jsilver/syntax/node/TElse.java
new file mode 100644
index 0000000..d72c353
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TElse.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TElse extends Token
+{
+    public TElse()
+    {
+        super.setText("else");
+    }
+
+    public TElse(int line, int pos)
+    {
+        super.setText("else");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TElse(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTElse(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TElse text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TElseIf.java b/src/com/google/clearsilver/jsilver/syntax/node/TElseIf.java
new file mode 100644
index 0000000..f73744d
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TElseIf.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TElseIf extends Token
+{
+    public TElseIf(String text)
+    {
+        setText(text);
+    }
+
+    public TElseIf(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TElseIf(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTElseIf(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TEq.java b/src/com/google/clearsilver/jsilver/syntax/node/TEq.java
new file mode 100644
index 0000000..9c54e43
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TEq.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TEq extends Token
+{
+    public TEq()
+    {
+        super.setText("==");
+    }
+
+    public TEq(int line, int pos)
+    {
+        super.setText("==");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TEq(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTEq(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TEq text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TEscape.java b/src/com/google/clearsilver/jsilver/syntax/node/TEscape.java
new file mode 100644
index 0000000..5345ec2
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TEscape.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TEscape extends Token
+{
+    public TEscape()
+    {
+        super.setText("escape");
+    }
+
+    public TEscape(int line, int pos)
+    {
+        super.setText("escape");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TEscape(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTEscape(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TEscape text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TEvar.java b/src/com/google/clearsilver/jsilver/syntax/node/TEvar.java
new file mode 100644
index 0000000..68e36c9
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TEvar.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TEvar extends Token
+{
+    public TEvar()
+    {
+        super.setText("evar");
+    }
+
+    public TEvar(int line, int pos)
+    {
+        super.setText("evar");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TEvar(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTEvar(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TEvar text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TGt.java b/src/com/google/clearsilver/jsilver/syntax/node/TGt.java
new file mode 100644
index 0000000..e187f90
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TGt.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TGt extends Token
+{
+    public TGt()
+    {
+        super.setText(">");
+    }
+
+    public TGt(int line, int pos)
+    {
+        super.setText(">");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TGt(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTGt(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TGt text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TGte.java b/src/com/google/clearsilver/jsilver/syntax/node/TGte.java
new file mode 100644
index 0000000..0a81bd2
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TGte.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TGte extends Token
+{
+    public TGte()
+    {
+        super.setText(">=");
+    }
+
+    public TGte(int line, int pos)
+    {
+        super.setText(">=");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TGte(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTGte(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TGte text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/THardDelimiter.java b/src/com/google/clearsilver/jsilver/syntax/node/THardDelimiter.java
new file mode 100644
index 0000000..d997115
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/THardDelimiter.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class THardDelimiter extends Token
+{
+    public THardDelimiter(String text)
+    {
+        setText(text);
+    }
+
+    public THardDelimiter(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new THardDelimiter(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTHardDelimiter(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/THash.java b/src/com/google/clearsilver/jsilver/syntax/node/THash.java
new file mode 100644
index 0000000..ec4a415
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/THash.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class THash extends Token
+{
+    public THash()
+    {
+        super.setText("#");
+    }
+
+    public THash(int line, int pos)
+    {
+        super.setText("#");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new THash(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTHash(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change THash text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/THexNumber.java b/src/com/google/clearsilver/jsilver/syntax/node/THexNumber.java
new file mode 100644
index 0000000..df74a27
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/THexNumber.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class THexNumber extends Token
+{
+    public THexNumber(String text)
+    {
+        setText(text);
+    }
+
+    public THexNumber(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new THexNumber(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTHexNumber(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TIf.java b/src/com/google/clearsilver/jsilver/syntax/node/TIf.java
new file mode 100644
index 0000000..7f95510
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TIf.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TIf extends Token
+{
+    public TIf()
+    {
+        super.setText("if");
+    }
+
+    public TIf(int line, int pos)
+    {
+        super.setText("if");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TIf(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTIf(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TIf text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TInclude.java b/src/com/google/clearsilver/jsilver/syntax/node/TInclude.java
new file mode 100644
index 0000000..0e57341
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TInclude.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TInclude extends Token
+{
+    public TInclude()
+    {
+        super.setText("include");
+    }
+
+    public TInclude(int line, int pos)
+    {
+        super.setText("include");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TInclude(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTInclude(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TInclude text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TInline.java b/src/com/google/clearsilver/jsilver/syntax/node/TInline.java
new file mode 100644
index 0000000..5a7be69
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TInline.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TInline extends Token
+{
+    public TInline()
+    {
+        super.setText("inline");
+    }
+
+    public TInline(int line, int pos)
+    {
+        super.setText("inline");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TInline(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTInline(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TInline text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TLinclude.java b/src/com/google/clearsilver/jsilver/syntax/node/TLinclude.java
new file mode 100644
index 0000000..ca730a2
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TLinclude.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TLinclude extends Token
+{
+    public TLinclude()
+    {
+        super.setText("linclude");
+    }
+
+    public TLinclude(int line, int pos)
+    {
+        super.setText("linclude");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TLinclude(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTLinclude(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TLinclude text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TLoop.java b/src/com/google/clearsilver/jsilver/syntax/node/TLoop.java
new file mode 100644
index 0000000..7eb3743
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TLoop.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TLoop extends Token
+{
+    public TLoop()
+    {
+        super.setText("loop");
+    }
+
+    public TLoop(int line, int pos)
+    {
+        super.setText("loop");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TLoop(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTLoop(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TLoop text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TLt.java b/src/com/google/clearsilver/jsilver/syntax/node/TLt.java
new file mode 100644
index 0000000..d297b48
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TLt.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TLt extends Token
+{
+    public TLt()
+    {
+        super.setText("<");
+    }
+
+    public TLt(int line, int pos)
+    {
+        super.setText("<");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TLt(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTLt(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TLt text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TLte.java b/src/com/google/clearsilver/jsilver/syntax/node/TLte.java
new file mode 100644
index 0000000..16fc7b0
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TLte.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TLte extends Token
+{
+    public TLte()
+    {
+        super.setText("<=");
+    }
+
+    public TLte(int line, int pos)
+    {
+        super.setText("<=");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TLte(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTLte(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TLte text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TLvar.java b/src/com/google/clearsilver/jsilver/syntax/node/TLvar.java
new file mode 100644
index 0000000..3bd7d61
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TLvar.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TLvar extends Token
+{
+    public TLvar()
+    {
+        super.setText("lvar");
+    }
+
+    public TLvar(int line, int pos)
+    {
+        super.setText("lvar");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TLvar(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTLvar(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TLvar text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TMinus.java b/src/com/google/clearsilver/jsilver/syntax/node/TMinus.java
new file mode 100644
index 0000000..fffeea0
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TMinus.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TMinus extends Token
+{
+    public TMinus()
+    {
+        super.setText("-");
+    }
+
+    public TMinus(int line, int pos)
+    {
+        super.setText("-");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TMinus(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTMinus(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TMinus text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TName.java b/src/com/google/clearsilver/jsilver/syntax/node/TName.java
new file mode 100644
index 0000000..86af7a6
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TName.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TName extends Token
+{
+    public TName()
+    {
+        super.setText("name");
+    }
+
+    public TName(int line, int pos)
+    {
+        super.setText("name");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TName(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTName(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TName text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TNe.java b/src/com/google/clearsilver/jsilver/syntax/node/TNe.java
new file mode 100644
index 0000000..6de956f
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TNe.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TNe extends Token
+{
+    public TNe()
+    {
+        super.setText("!=");
+    }
+
+    public TNe(int line, int pos)
+    {
+        super.setText("!=");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TNe(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTNe(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TNe text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TOr.java b/src/com/google/clearsilver/jsilver/syntax/node/TOr.java
new file mode 100644
index 0000000..83d51ee
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TOr.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TOr extends Token
+{
+    public TOr()
+    {
+        super.setText("||");
+    }
+
+    public TOr(int line, int pos)
+    {
+        super.setText("||");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TOr(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTOr(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TOr text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TParenClose.java b/src/com/google/clearsilver/jsilver/syntax/node/TParenClose.java
new file mode 100644
index 0000000..298418c
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TParenClose.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TParenClose extends Token
+{
+    public TParenClose()
+    {
+        super.setText(")");
+    }
+
+    public TParenClose(int line, int pos)
+    {
+        super.setText(")");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TParenClose(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTParenClose(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TParenClose text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TParenOpen.java b/src/com/google/clearsilver/jsilver/syntax/node/TParenOpen.java
new file mode 100644
index 0000000..44832a1
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TParenOpen.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TParenOpen extends Token
+{
+    public TParenOpen()
+    {
+        super.setText("(");
+    }
+
+    public TParenOpen(int line, int pos)
+    {
+        super.setText("(");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TParenOpen(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTParenOpen(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TParenOpen text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TPercent.java b/src/com/google/clearsilver/jsilver/syntax/node/TPercent.java
new file mode 100644
index 0000000..292e3cc
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TPercent.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TPercent extends Token
+{
+    public TPercent()
+    {
+        super.setText("%");
+    }
+
+    public TPercent(int line, int pos)
+    {
+        super.setText("%");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TPercent(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTPercent(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TPercent text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TPlus.java b/src/com/google/clearsilver/jsilver/syntax/node/TPlus.java
new file mode 100644
index 0000000..ec32825
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TPlus.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TPlus extends Token
+{
+    public TPlus()
+    {
+        super.setText("+");
+    }
+
+    public TPlus(int line, int pos)
+    {
+        super.setText("+");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TPlus(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTPlus(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TPlus text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TQuestion.java b/src/com/google/clearsilver/jsilver/syntax/node/TQuestion.java
new file mode 100644
index 0000000..0dfcb76
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TQuestion.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TQuestion extends Token
+{
+    public TQuestion()
+    {
+        super.setText("?");
+    }
+
+    public TQuestion(int line, int pos)
+    {
+        super.setText("?");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TQuestion(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTQuestion(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TQuestion text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TSet.java b/src/com/google/clearsilver/jsilver/syntax/node/TSet.java
new file mode 100644
index 0000000..5758c83
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TSet.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TSet extends Token
+{
+    public TSet()
+    {
+        super.setText("set");
+    }
+
+    public TSet(int line, int pos)
+    {
+        super.setText("set");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TSet(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTSet(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TSet text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TSlash.java b/src/com/google/clearsilver/jsilver/syntax/node/TSlash.java
new file mode 100644
index 0000000..57d3576
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TSlash.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TSlash extends Token
+{
+    public TSlash()
+    {
+        super.setText("/");
+    }
+
+    public TSlash(int line, int pos)
+    {
+        super.setText("/");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TSlash(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTSlash(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TSlash text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TStar.java b/src/com/google/clearsilver/jsilver/syntax/node/TStar.java
new file mode 100644
index 0000000..80b5448
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TStar.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TStar extends Token
+{
+    public TStar()
+    {
+        super.setText("*");
+    }
+
+    public TStar(int line, int pos)
+    {
+        super.setText("*");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TStar(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTStar(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TStar text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TString.java b/src/com/google/clearsilver/jsilver/syntax/node/TString.java
new file mode 100644
index 0000000..8852840
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TString.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TString extends Token
+{
+    public TString(String text)
+    {
+        setText(text);
+    }
+
+    public TString(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TString(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTString(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TUvar.java b/src/com/google/clearsilver/jsilver/syntax/node/TUvar.java
new file mode 100644
index 0000000..e4ec94b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TUvar.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TUvar extends Token
+{
+    public TUvar()
+    {
+        super.setText("uvar");
+    }
+
+    public TUvar(int line, int pos)
+    {
+        super.setText("uvar");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TUvar(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTUvar(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TUvar text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TVar.java b/src/com/google/clearsilver/jsilver/syntax/node/TVar.java
new file mode 100644
index 0000000..9f509aa
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TVar.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TVar extends Token
+{
+    public TVar()
+    {
+        super.setText("var");
+    }
+
+    public TVar(int line, int pos)
+    {
+        super.setText("var");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TVar(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTVar(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TVar text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TWith.java b/src/com/google/clearsilver/jsilver/syntax/node/TWith.java
new file mode 100644
index 0000000..67ece66
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TWith.java
@@ -0,0 +1,38 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TWith extends Token
+{
+    public TWith()
+    {
+        super.setText("with");
+    }
+
+    public TWith(int line, int pos)
+    {
+        super.setText("with");
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TWith(getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTWith(this);
+    }
+
+    @Override
+    public void setText(@SuppressWarnings("unused") String text)
+    {
+        throw new RuntimeException("Cannot change TWith text.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/TWord.java b/src/com/google/clearsilver/jsilver/syntax/node/TWord.java
new file mode 100644
index 0000000..79c7711
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/TWord.java
@@ -0,0 +1,32 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+@SuppressWarnings("nls")
+public final class TWord extends Token
+{
+    public TWord(String text)
+    {
+        setText(text);
+    }
+
+    public TWord(String text, int line, int pos)
+    {
+        setText(text);
+        setLine(line);
+        setPos(pos);
+    }
+
+    @Override
+    public Object clone()
+    {
+      return new TWord(getText(), getLine(), getPos());
+    }
+
+    public void apply(Switch sw)
+    {
+        ((Analysis) sw).caseTWord(this);
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/node/Token.java b/src/com/google/clearsilver/jsilver/syntax/node/Token.java
new file mode 100644
index 0000000..9ef39bb
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/node/Token.java
@@ -0,0 +1,59 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.node;
+
+@SuppressWarnings("nls")
+public abstract class Token extends Node
+{
+    private String text;
+    private int line;
+    private int pos;
+
+    public String getText()
+    {
+        return this.text;
+    }
+
+    public void setText(@SuppressWarnings("hiding") String text)
+    {
+        this.text = text;
+    }
+
+    public int getLine()
+    {
+        return this.line;
+    }
+
+    public void setLine(@SuppressWarnings("hiding") int line)
+    {
+        this.line = line;
+    }
+
+    public int getPos()
+    {
+        return this.pos;
+    }
+
+    public void setPos(@SuppressWarnings("hiding") int pos)
+    {
+        this.pos = pos;
+    }
+
+    @Override
+    public String toString()
+    {
+        return this.text + " ";
+    }
+
+    @Override
+    void removeChild(@SuppressWarnings("unused") Node child)
+    {
+        throw new RuntimeException("Not a child.");
+    }
+
+    @Override
+    void replaceChild(@SuppressWarnings("unused") Node oldChild, @SuppressWarnings("unused") Node newChild)
+    {
+        throw new RuntimeException("Not a child.");
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/parser/Parser.java b/src/com/google/clearsilver/jsilver/syntax/parser/Parser.java
new file mode 100644
index 0000000..5c5b2fa
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/parser/Parser.java
@@ -0,0 +1,5303 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.parser;
+
+import com.google.clearsilver.jsilver.syntax.lexer.*;
+import com.google.clearsilver.jsilver.syntax.node.*;
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+import java.util.*;
+
+import java.io.DataInputStream;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+
+@SuppressWarnings("nls")
+public class Parser
+{
+    public final Analysis ignoredTokens = new AnalysisAdapter();
+
+    protected ArrayList nodeList;
+
+    private final Lexer lexer;
+    private final ListIterator stack = new LinkedList().listIterator();
+    private int last_pos;
+    private int last_line;
+    private Token last_token;
+    private final TokenIndex converter = new TokenIndex();
+    private final int[] action = new int[2];
+
+    private final static int SHIFT = 0;
+    private final static int REDUCE = 1;
+    private final static int ACCEPT = 2;
+    private final static int ERROR = 3;
+
+    public Parser(@SuppressWarnings("hiding") Lexer lexer)
+    {
+        this.lexer = lexer;
+    }
+
+    @SuppressWarnings({"unchecked","unused"})
+    private void push(int numstate, ArrayList listNode) throws ParserException, LexerException, IOException
+    {
+        this.nodeList = listNode;
+
+        if(!this.stack.hasNext())
+        {
+            this.stack.add(new State(numstate, this.nodeList));
+            return;
+        }
+
+        State s = (State) this.stack.next();
+        s.state = numstate;
+        s.nodes = this.nodeList;
+    }
+
+    private int goTo(int index)
+    {
+        int state = state();
+        int low = 1;
+        int high = gotoTable[index].length - 1;
+        int value = gotoTable[index][0][1];
+
+        while(low <= high)
+        {
+            int middle = (low + high) / 2;
+
+            if(state < gotoTable[index][middle][0])
+            {
+                high = middle - 1;
+            }
+            else if(state > gotoTable[index][middle][0])
+            {
+                low = middle + 1;
+            }
+            else
+            {
+                value = gotoTable[index][middle][1];
+                break;
+            }
+        }
+
+        return value;
+    }
+
+    private int state()
+    {
+        State s = (State) this.stack.previous();
+        this.stack.next();
+        return s.state;
+    }
+
+    private ArrayList pop()
+    {
+        return ((State) this.stack.previous()).nodes;
+    }
+
+    private int index(Switchable token)
+    {
+        this.converter.index = -1;
+        token.apply(this.converter);
+        return this.converter.index;
+    }
+
+    @SuppressWarnings("unchecked")
+    public Start parse() throws ParserException, LexerException, IOException
+    {
+        push(0, null);
+        List<Node> ign = null;
+        while(true)
+        {
+            while(index(this.lexer.peek()) == -1)
+            {
+                if(ign == null)
+                {
+                    ign = new LinkedList<Node>();
+                }
+
+                ign.add(this.lexer.next());
+            }
+
+            if(ign != null)
+            {
+                this.ignoredTokens.setIn(this.lexer.peek(), ign);
+                ign = null;
+            }
+
+            this.last_pos = this.lexer.peek().getPos();
+            this.last_line = this.lexer.peek().getLine();
+            this.last_token = this.lexer.peek();
+
+            int index = index(this.lexer.peek());
+            this.action[0] = Parser.actionTable[state()][0][1];
+            this.action[1] = Parser.actionTable[state()][0][2];
+
+            int low = 1;
+            int high = Parser.actionTable[state()].length - 1;
+
+            while(low <= high)
+            {
+                int middle = (low + high) / 2;
+
+                if(index < Parser.actionTable[state()][middle][0])
+                {
+                    high = middle - 1;
+                }
+                else if(index > Parser.actionTable[state()][middle][0])
+                {
+                    low = middle + 1;
+                }
+                else
+                {
+                    this.action[0] = Parser.actionTable[state()][middle][1];
+                    this.action[1] = Parser.actionTable[state()][middle][2];
+                    break;
+                }
+            }
+
+            switch(this.action[0])
+            {
+                case SHIFT:
+		    {
+		        ArrayList list = new ArrayList();
+		        list.add(this.lexer.next());
+                        push(this.action[1], list);
+                    }
+		    break;
+                case REDUCE:
+                    switch(this.action[1])
+                    {
+                    case 0: /* reduce ANone1Grammar */
+		    {
+			ArrayList list = new0();
+			push(goTo(0), list);
+		    }
+		    break;
+                    case 1: /* reduce AOne1Grammar */
+		    {
+			ArrayList list = new1();
+			push(goTo(0), list);
+		    }
+		    break;
+                    case 2: /* reduce AMany1Grammar */
+		    {
+			ArrayList list = new2();
+			push(goTo(0), list);
+		    }
+		    break;
+                    case 3: /* reduce ADataCommand */
+		    {
+			ArrayList list = new3();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 4: /* reduce AAcommentcommand1Command */
+		    {
+			ArrayList list = new4();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 5: /* reduce AAcommentcommand2Command */
+		    {
+			ArrayList list = new5();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 6: /* reduce AVarCommand */
+		    {
+			ArrayList list = new6();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 7: /* reduce ALvarCommand */
+		    {
+			ArrayList list = new7();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 8: /* reduce AEvarCommand */
+		    {
+			ArrayList list = new8();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 9: /* reduce AUvarCommand */
+		    {
+			ArrayList list = new9();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 10: /* reduce ASetCommand */
+		    {
+			ArrayList list = new10();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 11: /* reduce ANameCommand */
+		    {
+			ArrayList list = new11();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 12: /* reduce AEscape$None1Command */
+		    {
+			ArrayList list = new12();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 13: /* reduce AEscape$One1Command */
+		    {
+			ArrayList list = new13();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 14: /* reduce AEscape$Many1Command */
+		    {
+			ArrayList list = new14();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 15: /* reduce AAutoescape$None1Command */
+		    {
+			ArrayList list = new15();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 16: /* reduce AAutoescape$One1Command */
+		    {
+			ArrayList list = new16();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 17: /* reduce AAutoescape$Many1Command */
+		    {
+			ArrayList list = new17();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 18: /* reduce AWith$None1Command */
+		    {
+			ArrayList list = new18();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 19: /* reduce AWith$One1Command */
+		    {
+			ArrayList list = new19();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 20: /* reduce AWith$Many1Command */
+		    {
+			ArrayList list = new20();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 21: /* reduce ALoopTo$None1Command */
+		    {
+			ArrayList list = new21();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 22: /* reduce ALoopTo$One1Command */
+		    {
+			ArrayList list = new22();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 23: /* reduce ALoopTo$Many1Command */
+		    {
+			ArrayList list = new23();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 24: /* reduce ALoop$None1Command */
+		    {
+			ArrayList list = new24();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 25: /* reduce ALoop$One1Command */
+		    {
+			ArrayList list = new25();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 26: /* reduce ALoop$Many1Command */
+		    {
+			ArrayList list = new26();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 27: /* reduce ALoopInc$None1Command */
+		    {
+			ArrayList list = new27();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 28: /* reduce ALoopInc$One1Command */
+		    {
+			ArrayList list = new28();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 29: /* reduce ALoopInc$Many1Command */
+		    {
+			ArrayList list = new29();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 30: /* reduce AEach$None1Command */
+		    {
+			ArrayList list = new30();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 31: /* reduce AEach$One1Command */
+		    {
+			ArrayList list = new31();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 32: /* reduce AEach$Many1Command */
+		    {
+			ArrayList list = new32();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 33: /* reduce AAlt$None1Command */
+		    {
+			ArrayList list = new33();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 34: /* reduce AAlt$One1Command */
+		    {
+			ArrayList list = new34();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 35: /* reduce AAlt$Many1Command */
+		    {
+			ArrayList list = new35();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 36: /* reduce AAdefcommand1$None1Command */
+		    {
+			ArrayList list = new36();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 37: /* reduce AAdefcommand1$One1Command */
+		    {
+			ArrayList list = new37();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 38: /* reduce AAdefcommand1$Many1Command */
+		    {
+			ArrayList list = new38();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 39: /* reduce AAdefcommand2$None1Command */
+		    {
+			ArrayList list = new39();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 40: /* reduce AAdefcommand2$One1Command */
+		    {
+			ArrayList list = new40();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 41: /* reduce AAdefcommand2$Many1Command */
+		    {
+			ArrayList list = new41();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 42: /* reduce AAcallcommand1Command */
+		    {
+			ArrayList list = new42();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 43: /* reduce AAcallcommand2Command */
+		    {
+			ArrayList list = new43();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 44: /* reduce AIfCommand */
+		    {
+			ArrayList list = new44();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 45: /* reduce AIncludeCommand */
+		    {
+			ArrayList list = new45();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 46: /* reduce AHardIncludeCommand */
+		    {
+			ArrayList list = new46();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 47: /* reduce ALincludeCommand */
+		    {
+			ArrayList list = new47();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 48: /* reduce AHardLincludeCommand */
+		    {
+			ArrayList list = new48();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 49: /* reduce AContentTypeCommand */
+		    {
+			ArrayList list = new49();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 50: /* reduce AInline$None1Command */
+		    {
+			ArrayList list = new50();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 51: /* reduce AInline$One1Command */
+		    {
+			ArrayList list = new51();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 52: /* reduce AInline$Many1Command */
+		    {
+			ArrayList list = new52();
+			push(goTo(1), list);
+		    }
+		    break;
+                    case 53: /* reduce ABitMultipartWord */
+		    {
+			ArrayList list = new53();
+			push(goTo(2), list);
+		    }
+		    break;
+                    case 54: /* reduce AMMultipartWord */
+		    {
+			ArrayList list = new54();
+			push(goTo(2), list);
+		    }
+		    break;
+                    case 55: /* reduce ASingleVariableList */
+		    {
+			ArrayList list = new55();
+			push(goTo(3), list);
+		    }
+		    break;
+                    case 56: /* reduce AMultipleVariableList */
+		    {
+			ArrayList list = new56();
+			push(goTo(3), list);
+		    }
+		    break;
+                    case 57: /* reduce ASingleExpressionList */
+		    {
+			ArrayList list = new57();
+			push(goTo(4), list);
+		    }
+		    break;
+                    case 58: /* reduce AMultipleExpressionList */
+		    {
+			ArrayList list = new58();
+			push(goTo(4), list);
+		    }
+		    break;
+                    case 59: /* reduce ANone1IfBlock */
+		    {
+			ArrayList list = new59();
+			push(goTo(5), list);
+		    }
+		    break;
+                    case 60: /* reduce AOne1IfBlock */
+		    {
+			ArrayList list = new60();
+			push(goTo(5), list);
+		    }
+		    break;
+                    case 61: /* reduce AMany1IfBlock */
+		    {
+			ArrayList list = new61();
+			push(goTo(5), list);
+		    }
+		    break;
+                    case 62: /* reduce APresent$None1ElseIfBlock */
+		    {
+			ArrayList list = new62();
+			push(goTo(6), list);
+		    }
+		    break;
+                    case 63: /* reduce APresent$One1ElseIfBlock */
+		    {
+			ArrayList list = new63();
+			push(goTo(6), list);
+		    }
+		    break;
+                    case 64: /* reduce APresent$Many1ElseIfBlock */
+		    {
+			ArrayList list = new64();
+			push(goTo(6), list);
+		    }
+		    break;
+                    case 65: /* reduce AMissingElseIfBlock */
+		    {
+			ArrayList list = new65();
+			push(goTo(6), list);
+		    }
+		    break;
+                    case 66: /* reduce APresent$None1ElseBlock */
+		    {
+			ArrayList list = new66();
+			push(goTo(7), list);
+		    }
+		    break;
+                    case 67: /* reduce APresent$One1ElseBlock */
+		    {
+			ArrayList list = new67();
+			push(goTo(7), list);
+		    }
+		    break;
+                    case 68: /* reduce APresent$Many1ElseBlock */
+		    {
+			ArrayList list = new68();
+			push(goTo(7), list);
+		    }
+		    break;
+                    case 69: /* reduce ASkipElseBlock */
+		    {
+			ArrayList list = new69();
+			push(goTo(7), list);
+		    }
+		    break;
+                    case 70: /* reduce AEndIfBlock */
+		    {
+			ArrayList list = new70();
+			push(goTo(8), list);
+		    }
+		    break;
+                    case 71: /* reduce AOrExpression */
+		    {
+			ArrayList list = new71();
+			push(goTo(9), list);
+		    }
+		    break;
+                    case 72: /* reduce AAndExpressionExpression */
+		    {
+			ArrayList list = new72();
+			push(goTo(9), list);
+		    }
+		    break;
+                    case 73: /* reduce AAndAndExpression */
+		    {
+			ArrayList list = new73();
+			push(goTo(10), list);
+		    }
+		    break;
+                    case 74: /* reduce AEqualityAndExpression */
+		    {
+			ArrayList list = new74();
+			push(goTo(10), list);
+		    }
+		    break;
+                    case 75: /* reduce AEqEquality */
+		    {
+			ArrayList list = new75();
+			push(goTo(11), list);
+		    }
+		    break;
+                    case 76: /* reduce ANeEquality */
+		    {
+			ArrayList list = new76();
+			push(goTo(11), list);
+		    }
+		    break;
+                    case 77: /* reduce AComparisonEquality */
+		    {
+			ArrayList list = new77();
+			push(goTo(11), list);
+		    }
+		    break;
+                    case 78: /* reduce ALtComparison */
+		    {
+			ArrayList list = new78();
+			push(goTo(12), list);
+		    }
+		    break;
+                    case 79: /* reduce AGtComparison */
+		    {
+			ArrayList list = new79();
+			push(goTo(12), list);
+		    }
+		    break;
+                    case 80: /* reduce ALteComparison */
+		    {
+			ArrayList list = new80();
+			push(goTo(12), list);
+		    }
+		    break;
+                    case 81: /* reduce AGteComparison */
+		    {
+			ArrayList list = new81();
+			push(goTo(12), list);
+		    }
+		    break;
+                    case 82: /* reduce AAddSubtractComparison */
+		    {
+			ArrayList list = new82();
+			push(goTo(12), list);
+		    }
+		    break;
+                    case 83: /* reduce AAddAddSubtract */
+		    {
+			ArrayList list = new83();
+			push(goTo(13), list);
+		    }
+		    break;
+                    case 84: /* reduce ASubtractAddSubtract */
+		    {
+			ArrayList list = new84();
+			push(goTo(13), list);
+		    }
+		    break;
+                    case 85: /* reduce AFactorAddSubtract */
+		    {
+			ArrayList list = new85();
+			push(goTo(13), list);
+		    }
+		    break;
+                    case 86: /* reduce AMultiplyFactor */
+		    {
+			ArrayList list = new86();
+			push(goTo(14), list);
+		    }
+		    break;
+                    case 87: /* reduce ADivideFactor */
+		    {
+			ArrayList list = new87();
+			push(goTo(14), list);
+		    }
+		    break;
+                    case 88: /* reduce AModuloFactor */
+		    {
+			ArrayList list = new88();
+			push(goTo(14), list);
+		    }
+		    break;
+                    case 89: /* reduce AValueFactor */
+		    {
+			ArrayList list = new89();
+			push(goTo(14), list);
+		    }
+		    break;
+                    case 90: /* reduce AVariableValue */
+		    {
+			ArrayList list = new90();
+			push(goTo(15), list);
+		    }
+		    break;
+                    case 91: /* reduce AStringValue */
+		    {
+			ArrayList list = new91();
+			push(goTo(15), list);
+		    }
+		    break;
+                    case 92: /* reduce ANumberValue */
+		    {
+			ArrayList list = new92();
+			push(goTo(15), list);
+		    }
+		    break;
+                    case 93: /* reduce AForcedNumberValue */
+		    {
+			ArrayList list = new93();
+			push(goTo(15), list);
+		    }
+		    break;
+                    case 94: /* reduce ANotValue */
+		    {
+			ArrayList list = new94();
+			push(goTo(15), list);
+		    }
+		    break;
+                    case 95: /* reduce AExistsValue */
+		    {
+			ArrayList list = new95();
+			push(goTo(15), list);
+		    }
+		    break;
+                    case 96: /* reduce AParensValue */
+		    {
+			ArrayList list = new96();
+			push(goTo(15), list);
+		    }
+		    break;
+                    case 97: /* reduce AAfunctionvalue1Value */
+		    {
+			ArrayList list = new97();
+			push(goTo(15), list);
+		    }
+		    break;
+                    case 98: /* reduce AAfunctionvalue2Value */
+		    {
+			ArrayList list = new98();
+			push(goTo(15), list);
+		    }
+		    break;
+                    case 99: /* reduce AAnamevariable1Variable */
+		    {
+			ArrayList list = new99();
+			push(goTo(16), list);
+		    }
+		    break;
+                    case 100: /* reduce AAnamevariable2Variable */
+		    {
+			ArrayList list = new100();
+			push(goTo(16), list);
+		    }
+		    break;
+                    case 101: /* reduce ADecNumberVariable */
+		    {
+			ArrayList list = new101();
+			push(goTo(16), list);
+		    }
+		    break;
+                    case 102: /* reduce AHexNumberVariable */
+		    {
+			ArrayList list = new102();
+			push(goTo(16), list);
+		    }
+		    break;
+                    case 103: /* reduce ADescendNameVariable */
+		    {
+			ArrayList list = new103();
+			push(goTo(16), list);
+		    }
+		    break;
+                    case 104: /* reduce ADescendDecNumberVariable */
+		    {
+			ArrayList list = new104();
+			push(goTo(16), list);
+		    }
+		    break;
+                    case 105: /* reduce ADescendHexNumberVariable */
+		    {
+			ArrayList list = new105();
+			push(goTo(16), list);
+		    }
+		    break;
+                    case 106: /* reduce AExpandVariable */
+		    {
+			ArrayList list = new106();
+			push(goTo(16), list);
+		    }
+		    break;
+                    case 107: /* reduce AUnsignedNumber */
+		    {
+			ArrayList list = new107();
+			push(goTo(17), list);
+		    }
+		    break;
+                    case 108: /* reduce APositiveNumber */
+		    {
+			ArrayList list = new108();
+			push(goTo(17), list);
+		    }
+		    break;
+                    case 109: /* reduce ANegativeNumber */
+		    {
+			ArrayList list = new109();
+			push(goTo(17), list);
+		    }
+		    break;
+                    case 110: /* reduce ADecimalDigits */
+		    {
+			ArrayList list = new110();
+			push(goTo(18), list);
+		    }
+		    break;
+                    case 111: /* reduce AHexDigits */
+		    {
+			ArrayList list = new111();
+			push(goTo(18), list);
+		    }
+		    break;
+                    case 112: /* reduce ATerminal$Command */
+		    {
+			ArrayList list = new112();
+			push(goTo(19), list);
+		    }
+		    break;
+                    case 113: /* reduce ANonTerminal$Command */
+		    {
+			ArrayList list = new113();
+			push(goTo(19), list);
+		    }
+		    break;
+                    }
+                    break;
+                case ACCEPT:
+                    {
+                        EOF node2 = (EOF) this.lexer.next();
+                        PCommand node1 = (PCommand) pop().get(0);
+                        Start node = new Start(node1, node2);
+                        return node;
+                    }
+                case ERROR:
+                    throw new ParserException(this.last_token,
+                        "[" + this.last_line + "," + this.last_pos + "] " +
+                        Parser.errorMessages[Parser.errors[this.action[1]]]);
+            }
+        }
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new0() /* reduce ANone1Grammar */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        PCommand pcommandNode1;
+        {
+            // Block
+
+        pcommandNode1 = new ANoopCommand();
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new1() /* reduce AOne1Grammar */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        pcommandNode1 = (PCommand)nodeArrayList1.get(0);
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new2() /* reduce AMany1Grammar */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        LinkedList listNode4 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode2;
+        LinkedList listNode3 = new LinkedList();
+        pcommandNode2 = (PCommand)nodeArrayList1.get(0);
+        listNode3 = (LinkedList)nodeArrayList2.get(0);
+	if(pcommandNode2 != null)
+	{
+	  listNode4.add(pcommandNode2);
+	}
+	if(listNode3 != null)
+	{
+	  listNode4.addAll(listNode3);
+	}
+        }
+
+        pcommandNode1 = new AMultipleCommand(listNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new3() /* reduce ADataCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        TData tdataNode2;
+        tdataNode2 = (TData)nodeArrayList1.get(0);
+
+        pcommandNode1 = new ADataCommand(tdataNode2);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new4() /* reduce AAcommentcommand1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        @SuppressWarnings("unused") Object nullNode4 = null;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+
+        pcommandNode1 = new ACommentCommand(ppositionNode2, null);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new5() /* reduce AAcommentcommand2Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        TComment tcommentNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        tcommentNode4 = (TComment)nodeArrayList3.get(0);
+
+        pcommandNode1 = new ACommentCommand(ppositionNode2, tcommentNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new6() /* reduce AVarCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode6 = new LinkedList();
+        {
+            // Block
+        LinkedList listNode5 = new LinkedList();
+        listNode5 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode5 != null)
+	{
+	  listNode6.addAll(listNode5);
+	}
+        }
+
+        pexpressionNode4 = new ASequenceExpression(listNode6);
+        }
+
+        pcommandNode1 = new AVarCommand(ppositionNode2, pexpressionNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new7() /* reduce ALvarCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode6 = new LinkedList();
+        {
+            // Block
+        LinkedList listNode5 = new LinkedList();
+        listNode5 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode5 != null)
+	{
+	  listNode6.addAll(listNode5);
+	}
+        }
+
+        pexpressionNode4 = new ASequenceExpression(listNode6);
+        }
+
+        pcommandNode1 = new ALvarCommand(ppositionNode2, pexpressionNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new8() /* reduce AEvarCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode6 = new LinkedList();
+        {
+            // Block
+        LinkedList listNode5 = new LinkedList();
+        listNode5 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode5 != null)
+	{
+	  listNode6.addAll(listNode5);
+	}
+        }
+
+        pexpressionNode4 = new ASequenceExpression(listNode6);
+        }
+
+        pcommandNode1 = new AEvarCommand(ppositionNode2, pexpressionNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new9() /* reduce AUvarCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode6 = new LinkedList();
+        {
+            // Block
+        LinkedList listNode5 = new LinkedList();
+        listNode5 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode5 != null)
+	{
+	  listNode6.addAll(listNode5);
+	}
+        }
+
+        pexpressionNode4 = new ASequenceExpression(listNode6);
+        }
+
+        pcommandNode1 = new AUvarCommand(ppositionNode2, pexpressionNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new10() /* reduce ASetCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+
+        pcommandNode1 = new ASetCommand(ppositionNode2, pvariableNode4, pexpressionNode5);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new11() /* reduce ANameCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+
+        pcommandNode1 = new ANameCommand(ppositionNode2, pvariableNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new12() /* reduce AEscape$None1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        {
+            // Block
+
+        pcommandNode5 = new ANoopCommand();
+        }
+
+        pcommandNode1 = new AEscapeCommand(ppositionNode2, pexpressionNode4, pcommandNode5);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new13() /* reduce AEscape$One1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        pcommandNode5 = (PCommand)nodeArrayList6.get(0);
+
+        pcommandNode1 = new AEscapeCommand(ppositionNode2, pexpressionNode4, pcommandNode5);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new14() /* reduce AEscape$Many1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        {
+            // Block
+        LinkedList listNode8 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode6;
+        LinkedList listNode7 = new LinkedList();
+        pcommandNode6 = (PCommand)nodeArrayList6.get(0);
+        listNode7 = (LinkedList)nodeArrayList7.get(0);
+	if(pcommandNode6 != null)
+	{
+	  listNode8.add(pcommandNode6);
+	}
+	if(listNode7 != null)
+	{
+	  listNode8.addAll(listNode7);
+	}
+        }
+
+        pcommandNode5 = new AMultipleCommand(listNode8);
+        }
+
+        pcommandNode1 = new AEscapeCommand(ppositionNode2, pexpressionNode4, pcommandNode5);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new15() /* reduce AAutoescape$None1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        {
+            // Block
+
+        pcommandNode5 = new ANoopCommand();
+        }
+
+        pcommandNode1 = new AAutoescapeCommand(ppositionNode2, pexpressionNode4, pcommandNode5);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new16() /* reduce AAutoescape$One1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        pcommandNode5 = (PCommand)nodeArrayList6.get(0);
+
+        pcommandNode1 = new AAutoescapeCommand(ppositionNode2, pexpressionNode4, pcommandNode5);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new17() /* reduce AAutoescape$Many1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        {
+            // Block
+        LinkedList listNode8 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode6;
+        LinkedList listNode7 = new LinkedList();
+        pcommandNode6 = (PCommand)nodeArrayList6.get(0);
+        listNode7 = (LinkedList)nodeArrayList7.get(0);
+	if(pcommandNode6 != null)
+	{
+	  listNode8.add(pcommandNode6);
+	}
+	if(listNode7 != null)
+	{
+	  listNode8.addAll(listNode7);
+	}
+        }
+
+        pcommandNode5 = new AMultipleCommand(listNode8);
+        }
+
+        pcommandNode1 = new AAutoescapeCommand(ppositionNode2, pexpressionNode4, pcommandNode5);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new18() /* reduce AWith$None1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        {
+            // Block
+
+        pcommandNode6 = new ANoopCommand();
+        }
+
+        pcommandNode1 = new AWithCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new19() /* reduce AWith$One1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        pcommandNode6 = (PCommand)nodeArrayList8.get(0);
+
+        pcommandNode1 = new AWithCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new20() /* reduce AWith$Many1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        {
+            // Block
+        LinkedList listNode9 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode7;
+        LinkedList listNode8 = new LinkedList();
+        pcommandNode7 = (PCommand)nodeArrayList8.get(0);
+        listNode8 = (LinkedList)nodeArrayList9.get(0);
+	if(pcommandNode7 != null)
+	{
+	  listNode9.add(pcommandNode7);
+	}
+	if(listNode8 != null)
+	{
+	  listNode9.addAll(listNode8);
+	}
+        }
+
+        pcommandNode6 = new AMultipleCommand(listNode9);
+        }
+
+        pcommandNode1 = new AWithCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new21() /* reduce ALoopTo$None1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        {
+            // Block
+
+        pcommandNode6 = new ANoopCommand();
+        }
+
+        pcommandNode1 = new ALoopToCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new22() /* reduce ALoopTo$One1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        pcommandNode6 = (PCommand)nodeArrayList8.get(0);
+
+        pcommandNode1 = new ALoopToCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new23() /* reduce ALoopTo$Many1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        {
+            // Block
+        LinkedList listNode9 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode7;
+        LinkedList listNode8 = new LinkedList();
+        pcommandNode7 = (PCommand)nodeArrayList8.get(0);
+        listNode8 = (LinkedList)nodeArrayList9.get(0);
+	if(pcommandNode7 != null)
+	{
+	  listNode9.add(pcommandNode7);
+	}
+	if(listNode8 != null)
+	{
+	  listNode9.addAll(listNode8);
+	}
+        }
+
+        pcommandNode6 = new AMultipleCommand(listNode9);
+        }
+
+        pcommandNode1 = new ALoopToCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new24() /* reduce ALoop$None1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PExpression pexpressionNode6;
+        PCommand pcommandNode7;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        pexpressionNode6 = (PExpression)nodeArrayList8.get(0);
+        {
+            // Block
+
+        pcommandNode7 = new ANoopCommand();
+        }
+
+        pcommandNode1 = new ALoopCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pexpressionNode6, pcommandNode7);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new25() /* reduce ALoop$One1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList14 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PExpression pexpressionNode6;
+        PCommand pcommandNode7;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        pexpressionNode6 = (PExpression)nodeArrayList8.get(0);
+        pcommandNode7 = (PCommand)nodeArrayList10.get(0);
+
+        pcommandNode1 = new ALoopCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pexpressionNode6, pcommandNode7);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new26() /* reduce ALoop$Many1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList15 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList14 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PExpression pexpressionNode6;
+        PCommand pcommandNode7;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        pexpressionNode6 = (PExpression)nodeArrayList8.get(0);
+        {
+            // Block
+        LinkedList listNode10 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode8;
+        LinkedList listNode9 = new LinkedList();
+        pcommandNode8 = (PCommand)nodeArrayList10.get(0);
+        listNode9 = (LinkedList)nodeArrayList11.get(0);
+	if(pcommandNode8 != null)
+	{
+	  listNode10.add(pcommandNode8);
+	}
+	if(listNode9 != null)
+	{
+	  listNode10.addAll(listNode9);
+	}
+        }
+
+        pcommandNode7 = new AMultipleCommand(listNode10);
+        }
+
+        pcommandNode1 = new ALoopCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pexpressionNode6, pcommandNode7);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new27() /* reduce ALoopInc$None1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList15 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList14 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PExpression pexpressionNode6;
+        PExpression pexpressionNode7;
+        PCommand pcommandNode8;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        pexpressionNode6 = (PExpression)nodeArrayList8.get(0);
+        pexpressionNode7 = (PExpression)nodeArrayList10.get(0);
+        {
+            // Block
+
+        pcommandNode8 = new ANoopCommand();
+        }
+
+        pcommandNode1 = new ALoopIncCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pexpressionNode6, pexpressionNode7, pcommandNode8);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new28() /* reduce ALoopInc$One1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList16 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList15 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList14 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PExpression pexpressionNode6;
+        PExpression pexpressionNode7;
+        PCommand pcommandNode8;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        pexpressionNode6 = (PExpression)nodeArrayList8.get(0);
+        pexpressionNode7 = (PExpression)nodeArrayList10.get(0);
+        pcommandNode8 = (PCommand)nodeArrayList12.get(0);
+
+        pcommandNode1 = new ALoopIncCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pexpressionNode6, pexpressionNode7, pcommandNode8);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new29() /* reduce ALoopInc$Many1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList17 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList16 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList15 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList14 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PExpression pexpressionNode6;
+        PExpression pexpressionNode7;
+        PCommand pcommandNode8;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        pexpressionNode6 = (PExpression)nodeArrayList8.get(0);
+        pexpressionNode7 = (PExpression)nodeArrayList10.get(0);
+        {
+            // Block
+        LinkedList listNode11 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode9;
+        LinkedList listNode10 = new LinkedList();
+        pcommandNode9 = (PCommand)nodeArrayList12.get(0);
+        listNode10 = (LinkedList)nodeArrayList13.get(0);
+	if(pcommandNode9 != null)
+	{
+	  listNode11.add(pcommandNode9);
+	}
+	if(listNode10 != null)
+	{
+	  listNode11.addAll(listNode10);
+	}
+        }
+
+        pcommandNode8 = new AMultipleCommand(listNode11);
+        }
+
+        pcommandNode1 = new ALoopIncCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pexpressionNode6, pexpressionNode7, pcommandNode8);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new30() /* reduce AEach$None1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        {
+            // Block
+
+        pcommandNode6 = new ANoopCommand();
+        }
+
+        pcommandNode1 = new AEachCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new31() /* reduce AEach$One1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        pcommandNode6 = (PCommand)nodeArrayList8.get(0);
+
+        pcommandNode1 = new AEachCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new32() /* reduce AEach$Many1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PVariable pvariableNode4;
+        PExpression pexpressionNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pvariableNode4 = (PVariable)nodeArrayList4.get(0);
+        pexpressionNode5 = (PExpression)nodeArrayList6.get(0);
+        {
+            // Block
+        LinkedList listNode9 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode7;
+        LinkedList listNode8 = new LinkedList();
+        pcommandNode7 = (PCommand)nodeArrayList8.get(0);
+        listNode8 = (LinkedList)nodeArrayList9.get(0);
+	if(pcommandNode7 != null)
+	{
+	  listNode9.add(pcommandNode7);
+	}
+	if(listNode8 != null)
+	{
+	  listNode9.addAll(listNode8);
+	}
+        }
+
+        pcommandNode6 = new AMultipleCommand(listNode9);
+        }
+
+        pcommandNode1 = new AEachCommand(ppositionNode2, pvariableNode4, pexpressionNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new33() /* reduce AAlt$None1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        {
+            // Block
+
+        pcommandNode5 = new ANoopCommand();
+        }
+
+        pcommandNode1 = new AAltCommand(ppositionNode2, pexpressionNode4, pcommandNode5);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new34() /* reduce AAlt$One1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        pcommandNode5 = (PCommand)nodeArrayList6.get(0);
+
+        pcommandNode1 = new AAltCommand(ppositionNode2, pexpressionNode4, pcommandNode5);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new35() /* reduce AAlt$Many1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        {
+            // Block
+        LinkedList listNode8 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode6;
+        LinkedList listNode7 = new LinkedList();
+        pcommandNode6 = (PCommand)nodeArrayList6.get(0);
+        listNode7 = (LinkedList)nodeArrayList7.get(0);
+	if(pcommandNode6 != null)
+	{
+	  listNode8.add(pcommandNode6);
+	}
+	if(listNode7 != null)
+	{
+	  listNode8.addAll(listNode7);
+	}
+        }
+
+        pcommandNode5 = new AMultipleCommand(listNode8);
+        }
+
+        pcommandNode1 = new AAltCommand(ppositionNode2, pexpressionNode4, pcommandNode5);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new36() /* reduce AAdefcommand1$None1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        LinkedList listNode5 = new LinkedList();
+        LinkedList listNode6 = new LinkedList();
+        PCommand pcommandNode7;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode4 = new LinkedList();
+        listNode4 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode4 != null)
+	{
+	  listNode5.addAll(listNode4);
+	}
+        }
+        {
+            // Block
+        }
+        {
+            // Block
+
+        pcommandNode7 = new ANoopCommand();
+        }
+
+        pcommandNode1 = new ADefCommand(ppositionNode2, listNode5, listNode6, pcommandNode7);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new37() /* reduce AAdefcommand1$One1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        LinkedList listNode5 = new LinkedList();
+        LinkedList listNode6 = new LinkedList();
+        PCommand pcommandNode7;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode4 = new LinkedList();
+        listNode4 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode4 != null)
+	{
+	  listNode5.addAll(listNode4);
+	}
+        }
+        {
+            // Block
+        }
+        pcommandNode7 = (PCommand)nodeArrayList8.get(0);
+
+        pcommandNode1 = new ADefCommand(ppositionNode2, listNode5, listNode6, pcommandNode7);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new38() /* reduce AAdefcommand1$Many1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        LinkedList listNode5 = new LinkedList();
+        LinkedList listNode6 = new LinkedList();
+        PCommand pcommandNode7;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode4 = new LinkedList();
+        listNode4 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode4 != null)
+	{
+	  listNode5.addAll(listNode4);
+	}
+        }
+        {
+            // Block
+        }
+        {
+            // Block
+        LinkedList listNode10 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode8;
+        LinkedList listNode9 = new LinkedList();
+        pcommandNode8 = (PCommand)nodeArrayList8.get(0);
+        listNode9 = (LinkedList)nodeArrayList9.get(0);
+	if(pcommandNode8 != null)
+	{
+	  listNode10.add(pcommandNode8);
+	}
+	if(listNode9 != null)
+	{
+	  listNode10.addAll(listNode9);
+	}
+        }
+
+        pcommandNode7 = new AMultipleCommand(listNode10);
+        }
+
+        pcommandNode1 = new ADefCommand(ppositionNode2, listNode5, listNode6, pcommandNode7);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new39() /* reduce AAdefcommand2$None1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        LinkedList listNode5 = new LinkedList();
+        LinkedList listNode7 = new LinkedList();
+        PCommand pcommandNode8;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode4 = new LinkedList();
+        listNode4 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode4 != null)
+	{
+	  listNode5.addAll(listNode4);
+	}
+        }
+        {
+            // Block
+        LinkedList listNode6 = new LinkedList();
+        listNode6 = (LinkedList)nodeArrayList6.get(0);
+	if(listNode6 != null)
+	{
+	  listNode7.addAll(listNode6);
+	}
+        }
+        {
+            // Block
+
+        pcommandNode8 = new ANoopCommand();
+        }
+
+        pcommandNode1 = new ADefCommand(ppositionNode2, listNode5, listNode7, pcommandNode8);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new40() /* reduce AAdefcommand2$One1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        LinkedList listNode5 = new LinkedList();
+        LinkedList listNode7 = new LinkedList();
+        PCommand pcommandNode8;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode4 = new LinkedList();
+        listNode4 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode4 != null)
+	{
+	  listNode5.addAll(listNode4);
+	}
+        }
+        {
+            // Block
+        LinkedList listNode6 = new LinkedList();
+        listNode6 = (LinkedList)nodeArrayList6.get(0);
+	if(listNode6 != null)
+	{
+	  listNode7.addAll(listNode6);
+	}
+        }
+        pcommandNode8 = (PCommand)nodeArrayList9.get(0);
+
+        pcommandNode1 = new ADefCommand(ppositionNode2, listNode5, listNode7, pcommandNode8);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new41() /* reduce AAdefcommand2$Many1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList14 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList13 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList12 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList11 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList10 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        LinkedList listNode5 = new LinkedList();
+        LinkedList listNode7 = new LinkedList();
+        PCommand pcommandNode8;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode4 = new LinkedList();
+        listNode4 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode4 != null)
+	{
+	  listNode5.addAll(listNode4);
+	}
+        }
+        {
+            // Block
+        LinkedList listNode6 = new LinkedList();
+        listNode6 = (LinkedList)nodeArrayList6.get(0);
+	if(listNode6 != null)
+	{
+	  listNode7.addAll(listNode6);
+	}
+        }
+        {
+            // Block
+        LinkedList listNode11 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode9;
+        LinkedList listNode10 = new LinkedList();
+        pcommandNode9 = (PCommand)nodeArrayList9.get(0);
+        listNode10 = (LinkedList)nodeArrayList10.get(0);
+	if(pcommandNode9 != null)
+	{
+	  listNode11.add(pcommandNode9);
+	}
+	if(listNode10 != null)
+	{
+	  listNode11.addAll(listNode10);
+	}
+        }
+
+        pcommandNode8 = new AMultipleCommand(listNode11);
+        }
+
+        pcommandNode1 = new ADefCommand(ppositionNode2, listNode5, listNode7, pcommandNode8);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new42() /* reduce AAcallcommand1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        LinkedList listNode5 = new LinkedList();
+        LinkedList listNode6 = new LinkedList();
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode4 = new LinkedList();
+        listNode4 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode4 != null)
+	{
+	  listNode5.addAll(listNode4);
+	}
+        }
+        {
+            // Block
+        }
+
+        pcommandNode1 = new ACallCommand(ppositionNode2, listNode5, listNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new43() /* reduce AAcallcommand2Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        LinkedList listNode5 = new LinkedList();
+        LinkedList listNode7 = new LinkedList();
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode4 = new LinkedList();
+        listNode4 = (LinkedList)nodeArrayList4.get(0);
+	if(listNode4 != null)
+	{
+	  listNode5.addAll(listNode4);
+	}
+        }
+        {
+            // Block
+        LinkedList listNode6 = new LinkedList();
+        listNode6 = (LinkedList)nodeArrayList6.get(0);
+	if(listNode6 != null)
+	{
+	  listNode7.addAll(listNode6);
+	}
+        }
+
+        pcommandNode1 = new ACallCommand(ppositionNode2, listNode5, listNode7);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new44() /* reduce AIfCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        pcommandNode1 = (PCommand)nodeArrayList1.get(0);
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new45() /* reduce AIncludeCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+
+        pcommandNode1 = new AIncludeCommand(ppositionNode2, pexpressionNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new46() /* reduce AHardIncludeCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+
+        pcommandNode1 = new AHardIncludeCommand(ppositionNode2, pexpressionNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new47() /* reduce ALincludeCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+
+        pcommandNode1 = new ALincludeCommand(ppositionNode2, pexpressionNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new48() /* reduce AHardLincludeCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+
+        pcommandNode1 = new AHardLincludeCommand(ppositionNode2, pexpressionNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new49() /* reduce AContentTypeCommand */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        TString tstringNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        tstringNode4 = (TString)nodeArrayList4.get(0);
+
+        pcommandNode1 = new AContentTypeCommand(ppositionNode2, tstringNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new50() /* reduce AInline$None1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PCommand pcommandNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+
+        pcommandNode4 = new ANoopCommand();
+        }
+
+        pcommandNode1 = new AInlineCommand(ppositionNode2, pcommandNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new51() /* reduce AInline$One1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PCommand pcommandNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pcommandNode4 = (PCommand)nodeArrayList4.get(0);
+
+        pcommandNode1 = new AInlineCommand(ppositionNode2, pcommandNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new52() /* reduce AInline$Many1Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList9 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PCommand pcommandNode4;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        {
+            // Block
+        LinkedList listNode7 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode5;
+        LinkedList listNode6 = new LinkedList();
+        pcommandNode5 = (PCommand)nodeArrayList4.get(0);
+        listNode6 = (LinkedList)nodeArrayList5.get(0);
+	if(pcommandNode5 != null)
+	{
+	  listNode7.add(pcommandNode5);
+	}
+	if(listNode6 != null)
+	{
+	  listNode7.addAll(listNode6);
+	}
+        }
+
+        pcommandNode4 = new AMultipleCommand(listNode7);
+        }
+
+        pcommandNode1 = new AInlineCommand(ppositionNode2, pcommandNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new53() /* reduce ABitMultipartWord */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        LinkedList listNode2 = new LinkedList();
+        {
+            // Block
+        TWord twordNode1;
+        twordNode1 = (TWord)nodeArrayList1.get(0);
+	if(twordNode1 != null)
+	{
+	  listNode2.add(twordNode1);
+	}
+        }
+	nodeList.add(listNode2);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new54() /* reduce AMMultipartWord */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        LinkedList listNode3 = new LinkedList();
+        {
+            // Block
+        LinkedList listNode1 = new LinkedList();
+        TWord twordNode2;
+        listNode1 = (LinkedList)nodeArrayList1.get(0);
+        twordNode2 = (TWord)nodeArrayList3.get(0);
+	if(listNode1 != null)
+	{
+	  listNode3.addAll(listNode1);
+	}
+	if(twordNode2 != null)
+	{
+	  listNode3.add(twordNode2);
+	}
+        }
+	nodeList.add(listNode3);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new55() /* reduce ASingleVariableList */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        LinkedList listNode2 = new LinkedList();
+        {
+            // Block
+        PVariable pvariableNode1;
+        pvariableNode1 = (PVariable)nodeArrayList1.get(0);
+	if(pvariableNode1 != null)
+	{
+	  listNode2.add(pvariableNode1);
+	}
+        }
+	nodeList.add(listNode2);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new56() /* reduce AMultipleVariableList */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        LinkedList listNode3 = new LinkedList();
+        {
+            // Block
+        LinkedList listNode1 = new LinkedList();
+        PVariable pvariableNode2;
+        listNode1 = (LinkedList)nodeArrayList1.get(0);
+        pvariableNode2 = (PVariable)nodeArrayList3.get(0);
+	if(listNode1 != null)
+	{
+	  listNode3.addAll(listNode1);
+	}
+	if(pvariableNode2 != null)
+	{
+	  listNode3.add(pvariableNode2);
+	}
+        }
+	nodeList.add(listNode3);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new57() /* reduce ASingleExpressionList */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        LinkedList listNode2 = new LinkedList();
+        {
+            // Block
+        PExpression pexpressionNode1;
+        pexpressionNode1 = (PExpression)nodeArrayList1.get(0);
+	if(pexpressionNode1 != null)
+	{
+	  listNode2.add(pexpressionNode1);
+	}
+        }
+	nodeList.add(listNode2);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new58() /* reduce AMultipleExpressionList */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        LinkedList listNode3 = new LinkedList();
+        {
+            // Block
+        LinkedList listNode1 = new LinkedList();
+        PExpression pexpressionNode2;
+        listNode1 = (LinkedList)nodeArrayList1.get(0);
+        pexpressionNode2 = (PExpression)nodeArrayList3.get(0);
+	if(listNode1 != null)
+	{
+	  listNode3.addAll(listNode1);
+	}
+	if(pexpressionNode2 != null)
+	{
+	  listNode3.add(pexpressionNode2);
+	}
+        }
+	nodeList.add(listNode3);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new59() /* reduce ANone1IfBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        {
+            // Block
+
+        pcommandNode5 = new ANoopCommand();
+        }
+        pcommandNode6 = (PCommand)nodeArrayList6.get(0);
+
+        pcommandNode1 = new AIfCommand(ppositionNode2, pexpressionNode4, pcommandNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new60() /* reduce AOne1IfBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        pcommandNode5 = (PCommand)nodeArrayList6.get(0);
+        pcommandNode6 = (PCommand)nodeArrayList7.get(0);
+
+        pcommandNode1 = new AIfCommand(ppositionNode2, pexpressionNode4, pcommandNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new61() /* reduce AMany1IfBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        PCommand pcommandNode9;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        {
+            // Block
+        LinkedList listNode8 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode6;
+        LinkedList listNode7 = new LinkedList();
+        pcommandNode6 = (PCommand)nodeArrayList6.get(0);
+        listNode7 = (LinkedList)nodeArrayList7.get(0);
+	if(pcommandNode6 != null)
+	{
+	  listNode8.add(pcommandNode6);
+	}
+	if(listNode7 != null)
+	{
+	  listNode8.addAll(listNode7);
+	}
+        }
+
+        pcommandNode5 = new AMultipleCommand(listNode8);
+        }
+        pcommandNode9 = (PCommand)nodeArrayList8.get(0);
+
+        pcommandNode1 = new AIfCommand(ppositionNode2, pexpressionNode4, pcommandNode5, pcommandNode9);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new62() /* reduce APresent$None1ElseIfBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        {
+            // Block
+
+        pcommandNode5 = new ANoopCommand();
+        }
+        pcommandNode6 = (PCommand)nodeArrayList6.get(0);
+
+        pcommandNode1 = new AIfCommand(ppositionNode2, pexpressionNode4, pcommandNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new63() /* reduce APresent$One1ElseIfBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        PCommand pcommandNode6;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        pcommandNode5 = (PCommand)nodeArrayList6.get(0);
+        pcommandNode6 = (PCommand)nodeArrayList7.get(0);
+
+        pcommandNode1 = new AIfCommand(ppositionNode2, pexpressionNode4, pcommandNode5, pcommandNode6);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new64() /* reduce APresent$Many1ElseIfBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList8 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList7 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        PPosition ppositionNode2;
+        PExpression pexpressionNode4;
+        PCommand pcommandNode5;
+        PCommand pcommandNode9;
+        {
+            // Block
+        TCsOpen tcsopenNode3;
+        tcsopenNode3 = (TCsOpen)nodeArrayList1.get(0);
+
+        ppositionNode2 = new ACsOpenPosition(tcsopenNode3);
+        }
+        pexpressionNode4 = (PExpression)nodeArrayList4.get(0);
+        {
+            // Block
+        LinkedList listNode8 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode6;
+        LinkedList listNode7 = new LinkedList();
+        pcommandNode6 = (PCommand)nodeArrayList6.get(0);
+        listNode7 = (LinkedList)nodeArrayList7.get(0);
+	if(pcommandNode6 != null)
+	{
+	  listNode8.add(pcommandNode6);
+	}
+	if(listNode7 != null)
+	{
+	  listNode8.addAll(listNode7);
+	}
+        }
+
+        pcommandNode5 = new AMultipleCommand(listNode8);
+        }
+        pcommandNode9 = (PCommand)nodeArrayList8.get(0);
+
+        pcommandNode1 = new AIfCommand(ppositionNode2, pexpressionNode4, pcommandNode5, pcommandNode9);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new65() /* reduce AMissingElseIfBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        pcommandNode1 = (PCommand)nodeArrayList1.get(0);
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new66() /* reduce APresent$None1ElseBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+
+        pcommandNode1 = new ANoopCommand();
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new67() /* reduce APresent$One1ElseBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        pcommandNode1 = (PCommand)nodeArrayList4.get(0);
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new68() /* reduce APresent$Many1ElseBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList6 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList5 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+        LinkedList listNode4 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode2;
+        LinkedList listNode3 = new LinkedList();
+        pcommandNode2 = (PCommand)nodeArrayList4.get(0);
+        listNode3 = (LinkedList)nodeArrayList5.get(0);
+	if(pcommandNode2 != null)
+	{
+	  listNode4.add(pcommandNode2);
+	}
+	if(listNode3 != null)
+	{
+	  listNode4.addAll(listNode3);
+	}
+        }
+
+        pcommandNode1 = new AMultipleCommand(listNode4);
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new69() /* reduce ASkipElseBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PCommand pcommandNode1;
+        {
+            // Block
+
+        pcommandNode1 = new ANoopCommand();
+        }
+	nodeList.add(pcommandNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new70() /* reduce AEndIfBlock */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new71() /* reduce AOrExpression */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new AOrExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new72() /* reduce AAndExpressionExpression */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        pexpressionNode1 = (PExpression)nodeArrayList1.get(0);
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new73() /* reduce AAndAndExpression */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new AAndExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new74() /* reduce AEqualityAndExpression */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        pexpressionNode1 = (PExpression)nodeArrayList1.get(0);
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new75() /* reduce AEqEquality */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new AEqExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new76() /* reduce ANeEquality */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new ANeExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new77() /* reduce AComparisonEquality */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        pexpressionNode1 = (PExpression)nodeArrayList1.get(0);
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new78() /* reduce ALtComparison */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new ALtExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new79() /* reduce AGtComparison */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new AGtExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new80() /* reduce ALteComparison */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new ALteExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new81() /* reduce AGteComparison */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new AGteExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new82() /* reduce AAddSubtractComparison */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        pexpressionNode1 = (PExpression)nodeArrayList1.get(0);
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new83() /* reduce AAddAddSubtract */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new AAddExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new84() /* reduce ASubtractAddSubtract */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new ASubtractExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new85() /* reduce AFactorAddSubtract */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        pexpressionNode1 = (PExpression)nodeArrayList1.get(0);
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new86() /* reduce AMultiplyFactor */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new AMultiplyExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new87() /* reduce ADivideFactor */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new ADivideExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new88() /* reduce AModuloFactor */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        PExpression pexpressionNode3;
+        pexpressionNode2 = (PExpression)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pexpressionNode1 = new AModuloExpression(pexpressionNode2, pexpressionNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new89() /* reduce AValueFactor */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        pexpressionNode1 = (PExpression)nodeArrayList1.get(0);
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new90() /* reduce AVariableValue */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PVariable pvariableNode2;
+        pvariableNode2 = (PVariable)nodeArrayList1.get(0);
+
+        pexpressionNode1 = new AVariableExpression(pvariableNode2);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new91() /* reduce AStringValue */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        TString tstringNode2;
+        tstringNode2 = (TString)nodeArrayList1.get(0);
+
+        pexpressionNode1 = new AStringExpression(tstringNode2);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new92() /* reduce ANumberValue */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        pexpressionNode1 = (PExpression)nodeArrayList1.get(0);
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new93() /* reduce AForcedNumberValue */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        pexpressionNode2 = (PExpression)nodeArrayList2.get(0);
+
+        pexpressionNode1 = new ANumericExpression(pexpressionNode2);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new94() /* reduce ANotValue */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        pexpressionNode2 = (PExpression)nodeArrayList2.get(0);
+
+        pexpressionNode1 = new ANotExpression(pexpressionNode2);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new95() /* reduce AExistsValue */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        pexpressionNode2 = (PExpression)nodeArrayList2.get(0);
+
+        pexpressionNode1 = new AExistsExpression(pexpressionNode2);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new96() /* reduce AParensValue */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        LinkedList listNode3 = new LinkedList();
+        {
+            // Block
+        LinkedList listNode2 = new LinkedList();
+        listNode2 = (LinkedList)nodeArrayList2.get(0);
+	if(listNode2 != null)
+	{
+	  listNode3.addAll(listNode2);
+	}
+        }
+
+        pexpressionNode1 = new ASequenceExpression(listNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new97() /* reduce AAfunctionvalue1Value */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PVariable pvariableNode2;
+        LinkedList listNode3 = new LinkedList();
+        pvariableNode2 = (PVariable)nodeArrayList1.get(0);
+        {
+            // Block
+        }
+
+        pexpressionNode1 = new AFunctionExpression(pvariableNode2, listNode3);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new98() /* reduce AAfunctionvalue2Value */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PVariable pvariableNode2;
+        LinkedList listNode4 = new LinkedList();
+        pvariableNode2 = (PVariable)nodeArrayList1.get(0);
+        {
+            // Block
+        LinkedList listNode3 = new LinkedList();
+        listNode3 = (LinkedList)nodeArrayList3.get(0);
+	if(listNode3 != null)
+	{
+	  listNode4.addAll(listNode3);
+	}
+        }
+
+        pexpressionNode1 = new AFunctionExpression(pvariableNode2, listNode4);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new99() /* reduce AAnamevariable1Variable */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PVariable pvariableNode1;
+        {
+            // Block
+        TWord twordNode2;
+        twordNode2 = (TWord)nodeArrayList1.get(0);
+
+        pvariableNode1 = new ANameVariable(twordNode2);
+        }
+	nodeList.add(pvariableNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new100() /* reduce AAnamevariable2Variable */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PVariable pvariableNode1;
+        {
+            // Block
+        TWord twordNode2;
+        twordNode2 = (TWord)nodeArrayList2.get(0);
+
+        pvariableNode1 = new ANameVariable(twordNode2);
+        }
+	nodeList.add(pvariableNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new101() /* reduce ADecNumberVariable */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PVariable pvariableNode1;
+        {
+            // Block
+        TDecNumber tdecnumberNode2;
+        tdecnumberNode2 = (TDecNumber)nodeArrayList2.get(0);
+
+        pvariableNode1 = new ADecNumberVariable(tdecnumberNode2);
+        }
+	nodeList.add(pvariableNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new102() /* reduce AHexNumberVariable */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PVariable pvariableNode1;
+        {
+            // Block
+        THexNumber thexnumberNode2;
+        thexnumberNode2 = (THexNumber)nodeArrayList2.get(0);
+
+        pvariableNode1 = new AHexNumberVariable(thexnumberNode2);
+        }
+	nodeList.add(pvariableNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new103() /* reduce ADescendNameVariable */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PVariable pvariableNode1;
+        {
+            // Block
+        PVariable pvariableNode2;
+        PVariable pvariableNode3;
+        pvariableNode2 = (PVariable)nodeArrayList1.get(0);
+        {
+            // Block
+        TWord twordNode4;
+        twordNode4 = (TWord)nodeArrayList3.get(0);
+
+        pvariableNode3 = new ANameVariable(twordNode4);
+        }
+
+        pvariableNode1 = new ADescendVariable(pvariableNode2, pvariableNode3);
+        }
+	nodeList.add(pvariableNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new104() /* reduce ADescendDecNumberVariable */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PVariable pvariableNode1;
+        {
+            // Block
+        PVariable pvariableNode2;
+        PVariable pvariableNode3;
+        pvariableNode2 = (PVariable)nodeArrayList1.get(0);
+        {
+            // Block
+        TDecNumber tdecnumberNode4;
+        tdecnumberNode4 = (TDecNumber)nodeArrayList3.get(0);
+
+        pvariableNode3 = new ADecNumberVariable(tdecnumberNode4);
+        }
+
+        pvariableNode1 = new ADescendVariable(pvariableNode2, pvariableNode3);
+        }
+	nodeList.add(pvariableNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new105() /* reduce ADescendHexNumberVariable */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PVariable pvariableNode1;
+        {
+            // Block
+        PVariable pvariableNode2;
+        PVariable pvariableNode3;
+        pvariableNode2 = (PVariable)nodeArrayList1.get(0);
+        {
+            // Block
+        THexNumber thexnumberNode4;
+        thexnumberNode4 = (THexNumber)nodeArrayList3.get(0);
+
+        pvariableNode3 = new AHexNumberVariable(thexnumberNode4);
+        }
+
+        pvariableNode1 = new ADescendVariable(pvariableNode2, pvariableNode3);
+        }
+	nodeList.add(pvariableNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new106() /* reduce AExpandVariable */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList4 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList3 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PVariable pvariableNode1;
+        {
+            // Block
+        PVariable pvariableNode2;
+        PExpression pexpressionNode3;
+        pvariableNode2 = (PVariable)nodeArrayList1.get(0);
+        pexpressionNode3 = (PExpression)nodeArrayList3.get(0);
+
+        pvariableNode1 = new AExpandVariable(pvariableNode2, pexpressionNode3);
+        }
+	nodeList.add(pvariableNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new107() /* reduce AUnsignedNumber */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        pexpressionNode1 = (PExpression)nodeArrayList1.get(0);
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new108() /* reduce APositiveNumber */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        pexpressionNode1 = (PExpression)nodeArrayList2.get(0);
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new109() /* reduce ANegativeNumber */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        PExpression pexpressionNode2;
+        pexpressionNode2 = (PExpression)nodeArrayList2.get(0);
+
+        pexpressionNode1 = new ANegativeExpression(pexpressionNode2);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new110() /* reduce ADecimalDigits */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        TDecNumber tdecnumberNode2;
+        tdecnumberNode2 = (TDecNumber)nodeArrayList1.get(0);
+
+        pexpressionNode1 = new ADecimalExpression(tdecnumberNode2);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new111() /* reduce AHexDigits */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        PExpression pexpressionNode1;
+        {
+            // Block
+        THexNumber thexnumberNode2;
+        thexnumberNode2 = (THexNumber)nodeArrayList1.get(0);
+
+        pexpressionNode1 = new AHexExpression(thexnumberNode2);
+        }
+	nodeList.add(pexpressionNode1);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new112() /* reduce ATerminal$Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        LinkedList listNode2 = new LinkedList();
+        {
+            // Block
+        PCommand pcommandNode1;
+        pcommandNode1 = (PCommand)nodeArrayList1.get(0);
+	if(pcommandNode1 != null)
+	{
+	  listNode2.add(pcommandNode1);
+	}
+        }
+	nodeList.add(listNode2);
+        return nodeList;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    ArrayList new113() /* reduce ANonTerminal$Command */
+    {
+        @SuppressWarnings("hiding") ArrayList nodeList = new ArrayList();
+
+        @SuppressWarnings("unused") ArrayList nodeArrayList2 = pop();
+        @SuppressWarnings("unused") ArrayList nodeArrayList1 = pop();
+        LinkedList listNode3 = new LinkedList();
+        {
+            // Block
+        LinkedList listNode1 = new LinkedList();
+        PCommand pcommandNode2;
+        listNode1 = (LinkedList)nodeArrayList1.get(0);
+        pcommandNode2 = (PCommand)nodeArrayList2.get(0);
+	if(listNode1 != null)
+	{
+	  listNode3.addAll(listNode1);
+	}
+	if(pcommandNode2 != null)
+	{
+	  listNode3.add(pcommandNode2);
+	}
+        }
+	nodeList.add(listNode3);
+        return nodeList;
+    }
+
+
+
+    private static int[][][] actionTable;
+/*      {
+			{{-1, REDUCE, 0}, {0, SHIFT, 1}, {51, SHIFT, 2}, },
+			{{-1, REDUCE, 3}, },
+			{{-1, ERROR, 2}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 3}, {56, ACCEPT, -1}, },
+			{{-1, REDUCE, 1}, {0, SHIFT, 1}, {51, SHIFT, 2}, },
+			{{-1, REDUCE, 44}, },
+			{{-1, ERROR, 6}, {53, SHIFT, 28}, },
+			{{-1, ERROR, 7}, {53, SHIFT, 29}, },
+			{{-1, ERROR, 8}, {53, SHIFT, 30}, },
+			{{-1, ERROR, 9}, {53, SHIFT, 31}, },
+			{{-1, ERROR, 10}, {53, SHIFT, 32}, },
+			{{-1, ERROR, 11}, {53, SHIFT, 33}, },
+			{{-1, ERROR, 12}, {53, SHIFT, 34}, },
+			{{-1, ERROR, 13}, {53, SHIFT, 35}, },
+			{{-1, ERROR, 14}, {53, SHIFT, 36}, },
+			{{-1, ERROR, 15}, {53, SHIFT, 37}, },
+			{{-1, ERROR, 16}, {53, SHIFT, 38}, },
+			{{-1, ERROR, 17}, {53, SHIFT, 39}, },
+			{{-1, ERROR, 18}, {53, SHIFT, 40}, },
+			{{-1, ERROR, 19}, {53, SHIFT, 41}, },
+			{{-1, ERROR, 20}, {53, SHIFT, 42}, },
+			{{-1, ERROR, 21}, {53, SHIFT, 43}, {54, SHIFT, 44}, },
+			{{-1, ERROR, 22}, {53, SHIFT, 45}, {54, SHIFT, 46}, },
+			{{-1, ERROR, 23}, {53, SHIFT, 47}, },
+			{{-1, ERROR, 24}, {55, SHIFT, 48}, },
+			{{-1, ERROR, 25}, {1, SHIFT, 49}, {55, SHIFT, 50}, },
+			{{-1, REDUCE, 112}, },
+			{{-1, REDUCE, 2}, {0, SHIFT, 1}, {51, SHIFT, 2}, },
+			{{-1, ERROR, 28}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 29}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 30}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 31}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 32}, {45, SHIFT, 58}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 33}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 34}, {45, SHIFT, 58}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 35}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 36}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 37}, {45, SHIFT, 58}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 38}, {45, SHIFT, 58}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 39}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 40}, {45, SHIFT, 58}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 41}, {49, SHIFT, 86}, },
+			{{-1, ERROR, 42}, {49, SHIFT, 86}, },
+			{{-1, ERROR, 43}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 44}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 45}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 46}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 47}, {34, SHIFT, 93}, },
+			{{-1, ERROR, 48}, {0, SHIFT, 1}, {51, SHIFT, 94}, },
+			{{-1, ERROR, 49}, {55, SHIFT, 96}, },
+			{{-1, REDUCE, 4}, },
+			{{-1, REDUCE, 113}, },
+			{{-1, ERROR, 52}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, REDUCE, 91}, },
+			{{-1, ERROR, 54}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 55}, {47, SHIFT, 60}, {48, SHIFT, 61}, },
+			{{-1, ERROR, 56}, {47, SHIFT, 60}, {48, SHIFT, 61}, },
+			{{-1, ERROR, 57}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 58}, {47, SHIFT, 102}, {48, SHIFT, 103}, {49, SHIFT, 104}, },
+			{{-1, ERROR, 59}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, REDUCE, 110}, },
+			{{-1, REDUCE, 111}, },
+			{{-1, REDUCE, 99}, },
+			{{-1, ERROR, 63}, {23, SHIFT, 106}, {55, SHIFT, 107}, },
+			{{-1, REDUCE, 57}, {33, SHIFT, 108}, },
+			{{-1, REDUCE, 72}, {32, SHIFT, 109}, },
+			{{-1, REDUCE, 74}, {26, SHIFT, 110}, {27, SHIFT, 111}, },
+			{{-1, REDUCE, 77}, {28, SHIFT, 112}, {29, SHIFT, 113}, {30, SHIFT, 114}, {31, SHIFT, 115}, },
+			{{-1, REDUCE, 82}, {36, SHIFT, 116}, {37, SHIFT, 117}, },
+			{{-1, REDUCE, 85}, {38, SHIFT, 118}, {39, SHIFT, 119}, {50, SHIFT, 120}, },
+			{{-1, REDUCE, 89}, },
+			{{-1, REDUCE, 90}, {40, SHIFT, 121}, {42, SHIFT, 122}, {44, SHIFT, 123}, },
+			{{-1, REDUCE, 92}, },
+			{{-1, REDUCE, 107}, },
+			{{-1, ERROR, 74}, {23, SHIFT, 106}, {55, SHIFT, 124}, },
+			{{-1, ERROR, 75}, {23, SHIFT, 106}, {55, SHIFT, 125}, },
+			{{-1, ERROR, 76}, {23, SHIFT, 106}, {55, SHIFT, 126}, },
+			{{-1, ERROR, 77}, {25, SHIFT, 127}, {40, SHIFT, 121}, {44, SHIFT, 123}, },
+			{{-1, ERROR, 78}, {33, SHIFT, 108}, {55, SHIFT, 128}, },
+			{{-1, ERROR, 79}, {25, SHIFT, 129}, {40, SHIFT, 121}, {44, SHIFT, 123}, },
+			{{-1, ERROR, 80}, {33, SHIFT, 108}, {55, SHIFT, 130}, },
+			{{-1, ERROR, 81}, {33, SHIFT, 108}, {55, SHIFT, 131}, },
+			{{-1, ERROR, 82}, {25, SHIFT, 132}, {40, SHIFT, 121}, {44, SHIFT, 123}, },
+			{{-1, ERROR, 83}, {25, SHIFT, 133}, {40, SHIFT, 121}, {44, SHIFT, 123}, },
+			{{-1, ERROR, 84}, {33, SHIFT, 108}, {55, SHIFT, 134}, },
+			{{-1, ERROR, 85}, {40, SHIFT, 121}, {44, SHIFT, 123}, {55, SHIFT, 135}, },
+			{{-1, REDUCE, 53}, },
+			{{-1, ERROR, 87}, {42, SHIFT, 136}, {44, SHIFT, 137}, },
+			{{-1, ERROR, 88}, {42, SHIFT, 138}, {44, SHIFT, 137}, },
+			{{-1, ERROR, 89}, {33, SHIFT, 108}, {55, SHIFT, 139}, },
+			{{-1, ERROR, 90}, {33, SHIFT, 108}, {55, SHIFT, 140}, },
+			{{-1, ERROR, 91}, {33, SHIFT, 108}, {55, SHIFT, 141}, },
+			{{-1, ERROR, 92}, {33, SHIFT, 108}, {55, SHIFT, 142}, },
+			{{-1, ERROR, 93}, {55, SHIFT, 143}, },
+			{{-1, ERROR, 94}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 144}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 95}, {0, SHIFT, 1}, {51, SHIFT, 145}, },
+			{{-1, REDUCE, 5}, },
+			{{-1, REDUCE, 94}, },
+			{{-1, REDUCE, 93}, },
+			{{-1, REDUCE, 108}, },
+			{{-1, REDUCE, 109}, },
+			{{-1, ERROR, 101}, {23, SHIFT, 106}, {43, SHIFT, 147}, },
+			{{-1, REDUCE, 101}, },
+			{{-1, REDUCE, 102}, },
+			{{-1, REDUCE, 100}, },
+			{{-1, REDUCE, 95}, },
+			{{-1, ERROR, 106}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, REDUCE, 6}, },
+			{{-1, ERROR, 108}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 109}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 110}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 111}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 112}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 113}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 114}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 115}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 116}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 117}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 118}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 119}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 120}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 121}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 122}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {43, SHIFT, 163}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 123}, {47, SHIFT, 165}, {48, SHIFT, 166}, {49, SHIFT, 167}, },
+			{{-1, REDUCE, 7}, },
+			{{-1, REDUCE, 8}, },
+			{{-1, REDUCE, 9}, },
+			{{-1, ERROR, 127}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 128}, {0, SHIFT, 1}, {51, SHIFT, 169}, },
+			{{-1, ERROR, 129}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 130}, {0, SHIFT, 1}, {51, SHIFT, 175}, },
+			{{-1, ERROR, 131}, {0, SHIFT, 1}, {51, SHIFT, 177}, },
+			{{-1, ERROR, 132}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 133}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 134}, {0, SHIFT, 1}, {51, SHIFT, 181}, },
+			{{-1, REDUCE, 11}, },
+			{{-1, ERROR, 136}, {43, SHIFT, 183}, {45, SHIFT, 58}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 137}, {49, SHIFT, 186}, },
+			{{-1, ERROR, 138}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {43, SHIFT, 187}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, REDUCE, 45}, },
+			{{-1, REDUCE, 46}, },
+			{{-1, REDUCE, 47}, },
+			{{-1, REDUCE, 48}, },
+			{{-1, REDUCE, 49}, },
+			{{-1, ERROR, 144}, {22, SHIFT, 189}, },
+			{{-1, ERROR, 145}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 190}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 146}, {0, SHIFT, 1}, {51, SHIFT, 191}, },
+			{{-1, REDUCE, 96}, },
+			{{-1, REDUCE, 58}, {33, SHIFT, 108}, },
+			{{-1, REDUCE, 71}, {32, SHIFT, 109}, },
+			{{-1, REDUCE, 73}, {26, SHIFT, 110}, {27, SHIFT, 111}, },
+			{{-1, REDUCE, 75}, {28, SHIFT, 112}, {29, SHIFT, 113}, {30, SHIFT, 114}, {31, SHIFT, 115}, },
+			{{-1, REDUCE, 76}, {28, SHIFT, 112}, {29, SHIFT, 113}, {30, SHIFT, 114}, {31, SHIFT, 115}, },
+			{{-1, REDUCE, 78}, {36, SHIFT, 116}, {37, SHIFT, 117}, },
+			{{-1, REDUCE, 79}, {36, SHIFT, 116}, {37, SHIFT, 117}, },
+			{{-1, REDUCE, 80}, {36, SHIFT, 116}, {37, SHIFT, 117}, },
+			{{-1, REDUCE, 81}, {36, SHIFT, 116}, {37, SHIFT, 117}, },
+			{{-1, REDUCE, 83}, {38, SHIFT, 118}, {39, SHIFT, 119}, {50, SHIFT, 120}, },
+			{{-1, REDUCE, 84}, {38, SHIFT, 118}, {39, SHIFT, 119}, {50, SHIFT, 120}, },
+			{{-1, REDUCE, 86}, },
+			{{-1, REDUCE, 88}, },
+			{{-1, REDUCE, 87}, },
+			{{-1, ERROR, 162}, {33, SHIFT, 108}, {41, SHIFT, 192}, },
+			{{-1, REDUCE, 97}, },
+			{{-1, ERROR, 164}, {23, SHIFT, 106}, {43, SHIFT, 193}, },
+			{{-1, REDUCE, 104}, },
+			{{-1, REDUCE, 105}, },
+			{{-1, REDUCE, 103}, },
+			{{-1, ERROR, 168}, {33, SHIFT, 108}, {55, SHIFT, 194}, },
+			{{-1, ERROR, 169}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {8, SHIFT, 195}, {9, SHIFT, 196}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 197}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 170}, {0, SHIFT, 1}, {51, SHIFT, 169}, },
+			{{-1, REDUCE, 59}, },
+			{{-1, REDUCE, 65}, },
+			{{-1, REDUCE, 69}, },
+			{{-1, ERROR, 174}, {33, SHIFT, 108}, {55, SHIFT, 200}, },
+			{{-1, ERROR, 175}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 201}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 176}, {0, SHIFT, 1}, {51, SHIFT, 202}, },
+			{{-1, ERROR, 177}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 204}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 178}, {0, SHIFT, 1}, {51, SHIFT, 205}, },
+			{{-1, ERROR, 179}, {23, SHIFT, 207}, {33, SHIFT, 108}, {55, SHIFT, 208}, },
+			{{-1, ERROR, 180}, {33, SHIFT, 108}, {55, SHIFT, 209}, },
+			{{-1, ERROR, 181}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 210}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 182}, {0, SHIFT, 1}, {51, SHIFT, 211}, },
+			{{-1, ERROR, 183}, {55, SHIFT, 213}, },
+			{{-1, ERROR, 184}, {23, SHIFT, 214}, {43, SHIFT, 215}, },
+			{{-1, REDUCE, 55}, {40, SHIFT, 121}, {44, SHIFT, 123}, },
+			{{-1, REDUCE, 54}, },
+			{{-1, ERROR, 187}, {55, SHIFT, 216}, },
+			{{-1, ERROR, 188}, {23, SHIFT, 106}, {43, SHIFT, 217}, },
+			{{-1, ERROR, 189}, {55, SHIFT, 218}, },
+			{{-1, ERROR, 190}, {22, SHIFT, 219}, },
+			{{-1, ERROR, 191}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 220}, {52, SHIFT, 25}, },
+			{{-1, REDUCE, 106}, },
+			{{-1, REDUCE, 98}, },
+			{{-1, REDUCE, 10}, },
+			{{-1, ERROR, 195}, {53, SHIFT, 221}, },
+			{{-1, ERROR, 196}, {55, SHIFT, 222}, },
+			{{-1, ERROR, 197}, {7, SHIFT, 223}, },
+			{{-1, REDUCE, 60}, },
+			{{-1, ERROR, 199}, {0, SHIFT, 1}, {51, SHIFT, 169}, },
+			{{-1, ERROR, 200}, {0, SHIFT, 1}, {51, SHIFT, 225}, },
+			{{-1, ERROR, 201}, {11, SHIFT, 227}, },
+			{{-1, ERROR, 202}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 228}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 203}, {0, SHIFT, 1}, {51, SHIFT, 229}, },
+			{{-1, ERROR, 204}, {12, SHIFT, 230}, },
+			{{-1, ERROR, 205}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 231}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 206}, {0, SHIFT, 1}, {51, SHIFT, 232}, },
+			{{-1, ERROR, 207}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 208}, {0, SHIFT, 1}, {51, SHIFT, 234}, },
+			{{-1, ERROR, 209}, {0, SHIFT, 1}, {51, SHIFT, 236}, },
+			{{-1, ERROR, 210}, {15, SHIFT, 238}, },
+			{{-1, ERROR, 211}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 239}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 212}, {0, SHIFT, 1}, {51, SHIFT, 240}, },
+			{{-1, ERROR, 213}, {0, SHIFT, 1}, {51, SHIFT, 241}, },
+			{{-1, ERROR, 214}, {45, SHIFT, 58}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 215}, {55, SHIFT, 244}, },
+			{{-1, REDUCE, 42}, },
+			{{-1, ERROR, 217}, {55, SHIFT, 245}, },
+			{{-1, REDUCE, 50}, },
+			{{-1, ERROR, 219}, {55, SHIFT, 246}, },
+			{{-1, ERROR, 220}, {22, SHIFT, 247}, },
+			{{-1, ERROR, 221}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 222}, {0, SHIFT, 1}, {51, SHIFT, 249}, },
+			{{-1, ERROR, 223}, {55, SHIFT, 252}, },
+			{{-1, REDUCE, 61}, },
+			{{-1, ERROR, 225}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 253}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 226}, {0, SHIFT, 1}, {51, SHIFT, 254}, },
+			{{-1, ERROR, 227}, {55, SHIFT, 256}, },
+			{{-1, ERROR, 228}, {11, SHIFT, 257}, },
+			{{-1, ERROR, 229}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 258}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 230}, {55, SHIFT, 259}, },
+			{{-1, ERROR, 231}, {12, SHIFT, 260}, },
+			{{-1, ERROR, 232}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 261}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 233}, {23, SHIFT, 262}, {33, SHIFT, 108}, {55, SHIFT, 263}, },
+			{{-1, ERROR, 234}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 264}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 235}, {0, SHIFT, 1}, {51, SHIFT, 265}, },
+			{{-1, ERROR, 236}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 267}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 237}, {0, SHIFT, 1}, {51, SHIFT, 268}, },
+			{{-1, ERROR, 238}, {55, SHIFT, 270}, },
+			{{-1, ERROR, 239}, {15, SHIFT, 271}, },
+			{{-1, ERROR, 240}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 272}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 241}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 273}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 242}, {0, SHIFT, 1}, {51, SHIFT, 274}, },
+			{{-1, REDUCE, 56}, {40, SHIFT, 121}, {44, SHIFT, 123}, },
+			{{-1, ERROR, 244}, {0, SHIFT, 1}, {51, SHIFT, 276}, },
+			{{-1, REDUCE, 43}, },
+			{{-1, REDUCE, 51}, },
+			{{-1, ERROR, 247}, {55, SHIFT, 278}, },
+			{{-1, ERROR, 248}, {33, SHIFT, 108}, {55, SHIFT, 279}, },
+			{{-1, ERROR, 249}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 197}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 250}, {0, SHIFT, 1}, {51, SHIFT, 249}, },
+			{{-1, REDUCE, 66}, },
+			{{-1, REDUCE, 70}, },
+			{{-1, ERROR, 253}, {10, SHIFT, 282}, },
+			{{-1, ERROR, 254}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 283}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 255}, {0, SHIFT, 1}, {51, SHIFT, 284}, },
+			{{-1, REDUCE, 12}, },
+			{{-1, ERROR, 257}, {55, SHIFT, 285}, },
+			{{-1, ERROR, 258}, {11, SHIFT, 286}, },
+			{{-1, REDUCE, 15}, },
+			{{-1, ERROR, 260}, {55, SHIFT, 287}, },
+			{{-1, ERROR, 261}, {12, SHIFT, 288}, },
+			{{-1, ERROR, 262}, {24, SHIFT, 52}, {34, SHIFT, 53}, {35, SHIFT, 54}, {36, SHIFT, 55}, {37, SHIFT, 56}, {42, SHIFT, 57}, {45, SHIFT, 58}, {46, SHIFT, 59}, {47, SHIFT, 60}, {48, SHIFT, 61}, {49, SHIFT, 62}, },
+			{{-1, ERROR, 263}, {0, SHIFT, 1}, {51, SHIFT, 290}, },
+			{{-1, ERROR, 264}, {13, SHIFT, 292}, },
+			{{-1, ERROR, 265}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 293}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 266}, {0, SHIFT, 1}, {51, SHIFT, 294}, },
+			{{-1, ERROR, 267}, {14, SHIFT, 295}, },
+			{{-1, ERROR, 268}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 296}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 269}, {0, SHIFT, 1}, {51, SHIFT, 297}, },
+			{{-1, REDUCE, 33}, },
+			{{-1, ERROR, 271}, {55, SHIFT, 298}, },
+			{{-1, ERROR, 272}, {15, SHIFT, 299}, },
+			{{-1, ERROR, 273}, {17, SHIFT, 300}, },
+			{{-1, ERROR, 274}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 301}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 275}, {0, SHIFT, 1}, {51, SHIFT, 302}, },
+			{{-1, ERROR, 276}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 303}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 277}, {0, SHIFT, 1}, {51, SHIFT, 304}, },
+			{{-1, REDUCE, 52}, },
+			{{-1, ERROR, 279}, {0, SHIFT, 1}, {51, SHIFT, 169}, },
+			{{-1, REDUCE, 67}, },
+			{{-1, ERROR, 281}, {0, SHIFT, 1}, {51, SHIFT, 249}, },
+			{{-1, ERROR, 282}, {55, SHIFT, 309}, },
+			{{-1, ERROR, 283}, {10, SHIFT, 310}, },
+			{{-1, ERROR, 284}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 311}, {52, SHIFT, 25}, },
+			{{-1, REDUCE, 13}, },
+			{{-1, ERROR, 286}, {55, SHIFT, 312}, },
+			{{-1, REDUCE, 16}, },
+			{{-1, ERROR, 288}, {55, SHIFT, 313}, },
+			{{-1, ERROR, 289}, {33, SHIFT, 108}, {55, SHIFT, 314}, },
+			{{-1, ERROR, 290}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 315}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 291}, {0, SHIFT, 1}, {51, SHIFT, 316}, },
+			{{-1, ERROR, 292}, {55, SHIFT, 318}, },
+			{{-1, ERROR, 293}, {13, SHIFT, 319}, },
+			{{-1, ERROR, 294}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 320}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 295}, {55, SHIFT, 321}, },
+			{{-1, ERROR, 296}, {14, SHIFT, 322}, },
+			{{-1, ERROR, 297}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 323}, {52, SHIFT, 25}, },
+			{{-1, REDUCE, 34}, },
+			{{-1, ERROR, 299}, {55, SHIFT, 324}, },
+			{{-1, ERROR, 300}, {55, SHIFT, 325}, },
+			{{-1, ERROR, 301}, {17, SHIFT, 326}, },
+			{{-1, ERROR, 302}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 327}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 303}, {17, SHIFT, 328}, },
+			{{-1, ERROR, 304}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 329}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 305}, {0, SHIFT, 1}, {51, SHIFT, 330}, },
+			{{-1, ERROR, 306}, {0, SHIFT, 1}, {51, SHIFT, 169}, },
+			{{-1, REDUCE, 62}, },
+			{{-1, REDUCE, 68}, },
+			{{-1, REDUCE, 18}, },
+			{{-1, ERROR, 310}, {55, SHIFT, 333}, },
+			{{-1, ERROR, 311}, {10, SHIFT, 334}, },
+			{{-1, REDUCE, 14}, },
+			{{-1, REDUCE, 17}, },
+			{{-1, ERROR, 314}, {0, SHIFT, 1}, {51, SHIFT, 335}, },
+			{{-1, ERROR, 315}, {13, SHIFT, 337}, },
+			{{-1, ERROR, 316}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 338}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 317}, {0, SHIFT, 1}, {51, SHIFT, 339}, },
+			{{-1, REDUCE, 21}, },
+			{{-1, ERROR, 319}, {55, SHIFT, 340}, },
+			{{-1, ERROR, 320}, {13, SHIFT, 341}, },
+			{{-1, REDUCE, 30}, },
+			{{-1, ERROR, 322}, {55, SHIFT, 342}, },
+			{{-1, ERROR, 323}, {14, SHIFT, 343}, },
+			{{-1, REDUCE, 35}, },
+			{{-1, REDUCE, 36}, },
+			{{-1, ERROR, 326}, {55, SHIFT, 344}, },
+			{{-1, ERROR, 327}, {17, SHIFT, 345}, },
+			{{-1, ERROR, 328}, {55, SHIFT, 346}, },
+			{{-1, ERROR, 329}, {17, SHIFT, 347}, },
+			{{-1, ERROR, 330}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 348}, {52, SHIFT, 25}, },
+			{{-1, REDUCE, 63}, },
+			{{-1, ERROR, 332}, {0, SHIFT, 1}, {51, SHIFT, 169}, },
+			{{-1, REDUCE, 19}, },
+			{{-1, ERROR, 334}, {55, SHIFT, 350}, },
+			{{-1, ERROR, 335}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 351}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 336}, {0, SHIFT, 1}, {51, SHIFT, 352}, },
+			{{-1, ERROR, 337}, {55, SHIFT, 354}, },
+			{{-1, ERROR, 338}, {13, SHIFT, 355}, },
+			{{-1, ERROR, 339}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 356}, {52, SHIFT, 25}, },
+			{{-1, REDUCE, 22}, },
+			{{-1, ERROR, 341}, {55, SHIFT, 357}, },
+			{{-1, REDUCE, 31}, },
+			{{-1, ERROR, 343}, {55, SHIFT, 358}, },
+			{{-1, REDUCE, 37}, },
+			{{-1, ERROR, 345}, {55, SHIFT, 359}, },
+			{{-1, REDUCE, 39}, },
+			{{-1, ERROR, 347}, {55, SHIFT, 360}, },
+			{{-1, ERROR, 348}, {17, SHIFT, 361}, },
+			{{-1, REDUCE, 64}, },
+			{{-1, REDUCE, 20}, },
+			{{-1, ERROR, 351}, {13, SHIFT, 362}, },
+			{{-1, ERROR, 352}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 363}, {52, SHIFT, 25}, },
+			{{-1, ERROR, 353}, {0, SHIFT, 1}, {51, SHIFT, 364}, },
+			{{-1, REDUCE, 24}, },
+			{{-1, ERROR, 355}, {55, SHIFT, 365}, },
+			{{-1, ERROR, 356}, {13, SHIFT, 366}, },
+			{{-1, REDUCE, 23}, },
+			{{-1, REDUCE, 32}, },
+			{{-1, REDUCE, 38}, },
+			{{-1, REDUCE, 40}, },
+			{{-1, ERROR, 361}, {55, SHIFT, 367}, },
+			{{-1, ERROR, 362}, {55, SHIFT, 368}, },
+			{{-1, ERROR, 363}, {13, SHIFT, 369}, },
+			{{-1, ERROR, 364}, {2, SHIFT, 6}, {3, SHIFT, 7}, {4, SHIFT, 8}, {5, SHIFT, 9}, {6, SHIFT, 10}, {7, SHIFT, 11}, {10, SHIFT, 12}, {11, SHIFT, 13}, {12, SHIFT, 14}, {13, SHIFT, 15}, {14, SHIFT, 16}, {15, SHIFT, 17}, {16, SHIFT, 18}, {17, SHIFT, 19}, {18, SHIFT, 20}, {19, SHIFT, 21}, {20, SHIFT, 22}, {21, SHIFT, 23}, {22, SHIFT, 24}, {50, SHIFT, 370}, {52, SHIFT, 25}, },
+			{{-1, REDUCE, 25}, },
+			{{-1, ERROR, 366}, {55, SHIFT, 371}, },
+			{{-1, REDUCE, 41}, },
+			{{-1, REDUCE, 27}, },
+			{{-1, ERROR, 369}, {55, SHIFT, 372}, },
+			{{-1, ERROR, 370}, {13, SHIFT, 373}, },
+			{{-1, REDUCE, 26}, },
+			{{-1, REDUCE, 28}, },
+			{{-1, ERROR, 373}, {55, SHIFT, 374}, },
+			{{-1, REDUCE, 29}, },
+        };*/
+    private static int[][][] gotoTable;
+/*      {
+			{{-1, 3}, },
+			{{-1, 26}, {0, 4}, {27, 51}, {48, 95}, {128, 170}, {130, 176}, {131, 178}, {134, 182}, {146, 51}, {199, 51}, {200, 226}, {203, 51}, {206, 51}, {208, 235}, {209, 237}, {212, 51}, {213, 242}, {222, 250}, {244, 277}, {255, 51}, {263, 291}, {266, 51}, {269, 51}, {275, 51}, {279, 306}, {281, 51}, {305, 51}, {314, 336}, {317, 51}, {332, 51}, {353, 51}, },
+			{{-1, 87}, {42, 88}, },
+			{{-1, 184}, },
+			{{-1, 63}, {29, 74}, {30, 75}, {31, 76}, {57, 101}, {122, 164}, {138, 188}, },
+			{{-1, 5}, },
+			{{-1, 171}, {170, 198}, {199, 224}, {279, 307}, {306, 331}, {332, 349}, },
+			{{-1, 172}, },
+			{{-1, 173}, {222, 251}, {250, 280}, {281, 308}, },
+			{{-1, 64}, {33, 78}, {35, 80}, {36, 81}, {39, 84}, {43, 89}, {44, 90}, {45, 91}, {46, 92}, {106, 148}, {121, 162}, {127, 168}, {129, 174}, {132, 179}, {133, 180}, {207, 233}, {221, 248}, {262, 289}, },
+			{{-1, 65}, {108, 149}, },
+			{{-1, 66}, {109, 150}, },
+			{{-1, 67}, {110, 151}, {111, 152}, },
+			{{-1, 68}, {112, 153}, {113, 154}, {114, 155}, {115, 156}, },
+			{{-1, 69}, {116, 157}, {117, 158}, },
+			{{-1, 70}, {52, 97}, {54, 98}, {59, 105}, {118, 159}, {119, 160}, {120, 161}, },
+			{{-1, 71}, {32, 77}, {34, 79}, {37, 82}, {38, 83}, {40, 85}, {136, 185}, {214, 243}, },
+			{{-1, 72}, },
+			{{-1, 73}, {55, 99}, {56, 100}, },
+			{{-1, 27}, {95, 146}, {170, 199}, {176, 203}, {178, 206}, {182, 212}, {226, 255}, {235, 266}, {237, 269}, {242, 275}, {250, 281}, {277, 305}, {291, 317}, {306, 332}, {336, 353}, },
+        };*/
+    private static String[] errorMessages;
+/*      {
+			"expecting: data, cs open, EOF",
+			"expecting: 'var', 'lvar', 'evar', 'uvar', 'set', 'if', 'with', 'escape', 'autoescape', 'loop', 'each', 'alt', 'name', 'def', 'call', 'include', 'linclude', 'content-type', 'inline', '#'",
+			"expecting: EOF",
+			"expecting: command delimiter",
+			"expecting: command delimiter, hard delimiter",
+			"expecting: cs close",
+			"expecting: comment, cs close",
+			"expecting: '!', string, '#', '+', '-', '(', '$', '?', dec number, hex number, word",
+			"expecting: '$', word",
+			"expecting: word",
+			"expecting: string",
+			"expecting: data, cs open",
+			"expecting: ',', '==', '!=', '<', '>', '<=', '>=', '&&', '||', '+', '-', '*', '%', ']', ')', '/', cs close",
+			"expecting: dec number, hex number",
+			"expecting: dec number, hex number, word",
+			"expecting: ',', '=', '==', '!=', '<', '>', '<=', '>=', '&&', '||', '+', '-', '*', '%', '[', ']', '(', ')', '.', '/', cs close",
+			"expecting: ',', cs close",
+			"expecting: ',', '||', ')', cs close",
+			"expecting: ',', '&&', '||', ']', ')', cs close",
+			"expecting: ',', '==', '!=', '&&', '||', ']', ')', cs close",
+			"expecting: ',', '==', '!=', '<', '>', '<=', '>=', '&&', '||', ']', ')', cs close",
+			"expecting: ',', '==', '!=', '<', '>', '<=', '>=', '&&', '||', '+', '-', ']', ')', cs close",
+			"expecting: ',', '==', '!=', '<', '>', '<=', '>=', '&&', '||', '+', '-', '*', '%', '[', ']', '(', ')', '.', '/', cs close",
+			"expecting: '=', '[', '.'",
+			"expecting: '||', cs close",
+			"expecting: '[', '.', cs close",
+			"expecting: '(', '.'",
+			"expecting: 'var', 'lvar', 'evar', 'uvar', 'set', 'if', 'with', 'escape', 'autoescape', 'loop', 'each', 'alt', 'name', 'def', 'call', 'include', 'linclude', 'content-type', 'inline', '/', '#'",
+			"expecting: ',', ')'",
+			"expecting: '!', string, '#', '+', '-', '(', ')', '$', '?', dec number, hex number, word",
+			"expecting: ')', '$', word",
+			"expecting: 'inline'",
+			"expecting: '||', ']'",
+			"expecting: 'var', 'lvar', 'evar', 'uvar', 'set', 'if', else if, 'else', 'with', 'escape', 'autoescape', 'loop', 'each', 'alt', 'name', 'def', 'call', 'include', 'linclude', 'content-type', 'inline', '/', '#'",
+			"expecting: ',', '||', cs close",
+			"expecting: ',', '[', ')', '.'",
+			"expecting: 'if'",
+			"expecting: 'escape'",
+			"expecting: 'autoescape'",
+			"expecting: 'alt'",
+			"expecting: 'with'",
+			"expecting: 'loop'",
+			"expecting: 'each'",
+			"expecting: 'def'",
+        };*/
+    private static int[] errors;
+/*      {
+			0, 0, 1, 2, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 5, 6, 0, 0, 7, 7, 7, 7, 8, 7, 8, 7, 7, 8, 8, 7, 8, 9, 9, 7, 7, 7, 7, 10, 11, 5, 0, 0, 7, 12, 7, 13, 13, 7, 14, 7, 12, 12, 15, 16, 17, 18, 19, 20, 21, 12, 12, 22, 12, 12, 16, 16, 16, 23, 24, 23, 24, 24, 23, 23, 24, 25, 26, 26, 26, 24, 24, 24, 24, 5, 27, 11, 0, 12, 12, 12, 12, 28, 15, 15, 15, 12, 7, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 29, 14, 0, 0, 0, 7, 11, 7, 11, 11, 7, 7, 11, 0, 30, 9, 29, 0, 0, 0, 0, 0, 31, 27, 11, 12, 17, 18, 19, 20, 20, 21, 21, 21, 21, 12, 12, 12, 12, 12, 32, 12, 28, 15, 15, 15, 24, 33, 11, 0, 0, 0, 24, 27, 11, 27, 11, 34, 24, 27, 11, 5, 28, 35, 26, 5, 28, 5, 31, 27, 15, 12, 0, 3, 5, 36, 0, 11, 11, 37, 27, 11, 38, 27, 11, 7, 11, 11, 39, 27, 11, 11, 8, 5, 0, 5, 0, 5, 31, 7, 11, 5, 0, 27, 11, 5, 37, 27, 5, 38, 27, 34, 27, 11, 27, 11, 5, 39, 27, 27, 11, 35, 11, 0, 0, 5, 24, 27, 11, 0, 0, 40, 27, 11, 0, 5, 37, 0, 5, 38, 7, 11, 41, 27, 11, 42, 27, 11, 0, 5, 39, 43, 27, 11, 27, 11, 0, 11, 0, 11, 5, 40, 27, 0, 5, 0, 5, 24, 27, 11, 5, 41, 27, 5, 42, 27, 0, 5, 5, 43, 27, 43, 27, 11, 11, 0, 0, 0, 5, 40, 0, 0, 11, 41, 27, 11, 0, 5, 41, 0, 5, 42, 0, 0, 5, 43, 5, 43, 27, 0, 11, 0, 5, 27, 11, 5, 41, 27, 0, 5, 0, 5, 0, 5, 0, 5, 43, 0, 0, 41, 27, 11, 0, 5, 41, 0, 0, 0, 0, 5, 5, 41, 27, 0, 5, 0, 0, 5, 41, 0, 0, 5, 0, 
+        };*/
+
+    static 
+    {
+        try
+        {
+            DataInputStream s = new DataInputStream(
+                new BufferedInputStream(
+                Parser.class.getResourceAsStream("parser.dat")));
+
+            // read actionTable
+            int length = s.readInt();
+            Parser.actionTable = new int[length][][];
+            for(int i = 0; i < Parser.actionTable.length; i++)
+            {
+                length = s.readInt();
+                Parser.actionTable[i] = new int[length][3];
+                for(int j = 0; j < Parser.actionTable[i].length; j++)
+                {
+                for(int k = 0; k < 3; k++)
+                {
+                    Parser.actionTable[i][j][k] = s.readInt();
+                }
+                }
+            }
+
+            // read gotoTable
+            length = s.readInt();
+            gotoTable = new int[length][][];
+            for(int i = 0; i < gotoTable.length; i++)
+            {
+                length = s.readInt();
+                gotoTable[i] = new int[length][2];
+                for(int j = 0; j < gotoTable[i].length; j++)
+                {
+                for(int k = 0; k < 2; k++)
+                {
+                    gotoTable[i][j][k] = s.readInt();
+                }
+                }
+            }
+
+            // read errorMessages
+            length = s.readInt();
+            errorMessages = new String[length];
+            for(int i = 0; i < errorMessages.length; i++)
+            {
+                length = s.readInt();
+                StringBuffer buffer = new StringBuffer();
+
+                for(int j = 0; j < length; j++)
+                {
+                buffer.append(s.readChar());
+                }
+                errorMessages[i] = buffer.toString();
+            }
+
+            // read errors
+            length = s.readInt();
+            errors = new int[length];
+            for(int i = 0; i < errors.length; i++)
+            {
+                errors[i] = s.readInt();
+            }
+
+            s.close();
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException("The file \"parser.dat\" is either missing or corrupted.");
+        }
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/parser/ParserException.java b/src/com/google/clearsilver/jsilver/syntax/parser/ParserException.java
new file mode 100644
index 0000000..23c9599
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/parser/ParserException.java
@@ -0,0 +1,22 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.parser;
+
+import com.google.clearsilver.jsilver.syntax.node.*;
+
+@SuppressWarnings("serial")
+public class ParserException extends Exception
+{
+    Token token;
+
+    public ParserException(@SuppressWarnings("hiding") Token token, String  message)
+    {
+        super(message);
+        this.token = token;
+    }
+
+    public Token getToken()
+    {
+        return this.token;
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/parser/State.java b/src/com/google/clearsilver/jsilver/syntax/parser/State.java
new file mode 100644
index 0000000..f4e96bd
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/parser/State.java
@@ -0,0 +1,17 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.parser;
+
+import java.util.ArrayList;
+
+final class State
+{
+    int state;
+    ArrayList nodes;
+
+    State(@SuppressWarnings("hiding") int state, @SuppressWarnings("hiding") ArrayList nodes)
+    {
+        this.state = state;
+        this.nodes = nodes;
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/parser/TokenIndex.java b/src/com/google/clearsilver/jsilver/syntax/parser/TokenIndex.java
new file mode 100644
index 0000000..e1a043b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/parser/TokenIndex.java
@@ -0,0 +1,353 @@
+/* This file was generated by SableCC (http://www.sablecc.org/). */
+
+package com.google.clearsilver.jsilver.syntax.parser;
+
+import com.google.clearsilver.jsilver.syntax.node.*;
+import com.google.clearsilver.jsilver.syntax.analysis.*;
+
+class TokenIndex extends AnalysisAdapter
+{
+    int index;
+
+    @Override
+    public void caseTData(@SuppressWarnings("unused") TData node)
+    {
+        this.index = 0;
+    }
+
+    @Override
+    public void caseTComment(@SuppressWarnings("unused") TComment node)
+    {
+        this.index = 1;
+    }
+
+    @Override
+    public void caseTVar(@SuppressWarnings("unused") TVar node)
+    {
+        this.index = 2;
+    }
+
+    @Override
+    public void caseTLvar(@SuppressWarnings("unused") TLvar node)
+    {
+        this.index = 3;
+    }
+
+    @Override
+    public void caseTEvar(@SuppressWarnings("unused") TEvar node)
+    {
+        this.index = 4;
+    }
+
+    @Override
+    public void caseTUvar(@SuppressWarnings("unused") TUvar node)
+    {
+        this.index = 5;
+    }
+
+    @Override
+    public void caseTSet(@SuppressWarnings("unused") TSet node)
+    {
+        this.index = 6;
+    }
+
+    @Override
+    public void caseTIf(@SuppressWarnings("unused") TIf node)
+    {
+        this.index = 7;
+    }
+
+    @Override
+    public void caseTElseIf(@SuppressWarnings("unused") TElseIf node)
+    {
+        this.index = 8;
+    }
+
+    @Override
+    public void caseTElse(@SuppressWarnings("unused") TElse node)
+    {
+        this.index = 9;
+    }
+
+    @Override
+    public void caseTWith(@SuppressWarnings("unused") TWith node)
+    {
+        this.index = 10;
+    }
+
+    @Override
+    public void caseTEscape(@SuppressWarnings("unused") TEscape node)
+    {
+        this.index = 11;
+    }
+
+    @Override
+    public void caseTAutoescape(@SuppressWarnings("unused") TAutoescape node)
+    {
+        this.index = 12;
+    }
+
+    @Override
+    public void caseTLoop(@SuppressWarnings("unused") TLoop node)
+    {
+        this.index = 13;
+    }
+
+    @Override
+    public void caseTEach(@SuppressWarnings("unused") TEach node)
+    {
+        this.index = 14;
+    }
+
+    @Override
+    public void caseTAlt(@SuppressWarnings("unused") TAlt node)
+    {
+        this.index = 15;
+    }
+
+    @Override
+    public void caseTName(@SuppressWarnings("unused") TName node)
+    {
+        this.index = 16;
+    }
+
+    @Override
+    public void caseTDef(@SuppressWarnings("unused") TDef node)
+    {
+        this.index = 17;
+    }
+
+    @Override
+    public void caseTCall(@SuppressWarnings("unused") TCall node)
+    {
+        this.index = 18;
+    }
+
+    @Override
+    public void caseTInclude(@SuppressWarnings("unused") TInclude node)
+    {
+        this.index = 19;
+    }
+
+    @Override
+    public void caseTLinclude(@SuppressWarnings("unused") TLinclude node)
+    {
+        this.index = 20;
+    }
+
+    @Override
+    public void caseTContentType(@SuppressWarnings("unused") TContentType node)
+    {
+        this.index = 21;
+    }
+
+    @Override
+    public void caseTInline(@SuppressWarnings("unused") TInline node)
+    {
+        this.index = 22;
+    }
+
+    @Override
+    public void caseTComma(@SuppressWarnings("unused") TComma node)
+    {
+        this.index = 23;
+    }
+
+    @Override
+    public void caseTBang(@SuppressWarnings("unused") TBang node)
+    {
+        this.index = 24;
+    }
+
+    @Override
+    public void caseTAssignment(@SuppressWarnings("unused") TAssignment node)
+    {
+        this.index = 25;
+    }
+
+    @Override
+    public void caseTEq(@SuppressWarnings("unused") TEq node)
+    {
+        this.index = 26;
+    }
+
+    @Override
+    public void caseTNe(@SuppressWarnings("unused") TNe node)
+    {
+        this.index = 27;
+    }
+
+    @Override
+    public void caseTLt(@SuppressWarnings("unused") TLt node)
+    {
+        this.index = 28;
+    }
+
+    @Override
+    public void caseTGt(@SuppressWarnings("unused") TGt node)
+    {
+        this.index = 29;
+    }
+
+    @Override
+    public void caseTLte(@SuppressWarnings("unused") TLte node)
+    {
+        this.index = 30;
+    }
+
+    @Override
+    public void caseTGte(@SuppressWarnings("unused") TGte node)
+    {
+        this.index = 31;
+    }
+
+    @Override
+    public void caseTAnd(@SuppressWarnings("unused") TAnd node)
+    {
+        this.index = 32;
+    }
+
+    @Override
+    public void caseTOr(@SuppressWarnings("unused") TOr node)
+    {
+        this.index = 33;
+    }
+
+    @Override
+    public void caseTString(@SuppressWarnings("unused") TString node)
+    {
+        this.index = 34;
+    }
+
+    @Override
+    public void caseTHash(@SuppressWarnings("unused") THash node)
+    {
+        this.index = 35;
+    }
+
+    @Override
+    public void caseTPlus(@SuppressWarnings("unused") TPlus node)
+    {
+        this.index = 36;
+    }
+
+    @Override
+    public void caseTMinus(@SuppressWarnings("unused") TMinus node)
+    {
+        this.index = 37;
+    }
+
+    @Override
+    public void caseTStar(@SuppressWarnings("unused") TStar node)
+    {
+        this.index = 38;
+    }
+
+    @Override
+    public void caseTPercent(@SuppressWarnings("unused") TPercent node)
+    {
+        this.index = 39;
+    }
+
+    @Override
+    public void caseTBracketOpen(@SuppressWarnings("unused") TBracketOpen node)
+    {
+        this.index = 40;
+    }
+
+    @Override
+    public void caseTBracketClose(@SuppressWarnings("unused") TBracketClose node)
+    {
+        this.index = 41;
+    }
+
+    @Override
+    public void caseTParenOpen(@SuppressWarnings("unused") TParenOpen node)
+    {
+        this.index = 42;
+    }
+
+    @Override
+    public void caseTParenClose(@SuppressWarnings("unused") TParenClose node)
+    {
+        this.index = 43;
+    }
+
+    @Override
+    public void caseTDot(@SuppressWarnings("unused") TDot node)
+    {
+        this.index = 44;
+    }
+
+    @Override
+    public void caseTDollar(@SuppressWarnings("unused") TDollar node)
+    {
+        this.index = 45;
+    }
+
+    @Override
+    public void caseTQuestion(@SuppressWarnings("unused") TQuestion node)
+    {
+        this.index = 46;
+    }
+
+    @Override
+    public void caseTDecNumber(@SuppressWarnings("unused") TDecNumber node)
+    {
+        this.index = 47;
+    }
+
+    @Override
+    public void caseTHexNumber(@SuppressWarnings("unused") THexNumber node)
+    {
+        this.index = 48;
+    }
+
+    @Override
+    public void caseTWord(@SuppressWarnings("unused") TWord node)
+    {
+        this.index = 49;
+    }
+
+    @Override
+    public void caseTSlash(@SuppressWarnings("unused") TSlash node)
+    {
+        this.index = 50;
+    }
+
+    @Override
+    public void caseTCsOpen(@SuppressWarnings("unused") TCsOpen node)
+    {
+        this.index = 51;
+    }
+
+    @Override
+    public void caseTCommentStart(@SuppressWarnings("unused") TCommentStart node)
+    {
+        this.index = 52;
+    }
+
+    @Override
+    public void caseTCommandDelimiter(@SuppressWarnings("unused") TCommandDelimiter node)
+    {
+        this.index = 53;
+    }
+
+    @Override
+    public void caseTHardDelimiter(@SuppressWarnings("unused") THardDelimiter node)
+    {
+        this.index = 54;
+    }
+
+    @Override
+    public void caseTCsClose(@SuppressWarnings("unused") TCsClose node)
+    {
+        this.index = 55;
+    }
+
+    @Override
+    public void caseEOF(@SuppressWarnings("unused") EOF node)
+    {
+        this.index = 56;
+    }
+}
diff --git a/src/com/google/clearsilver/jsilver/syntax/parser/parser.dat b/src/com/google/clearsilver/jsilver/syntax/parser/parser.dat
new file mode 100644
index 0000000..8e87199
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/syntax/parser/parser.dat
Binary files differ
diff --git a/src/com/google/clearsilver/jsilver/template/DefaultRenderingContext.java b/src/com/google/clearsilver/jsilver/template/DefaultRenderingContext.java
new file mode 100644
index 0000000..c9f83e7
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/template/DefaultRenderingContext.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.template;
+
+import com.google.clearsilver.jsilver.autoescape.AutoEscapeContext;
+import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.DataContext;
+import com.google.clearsilver.jsilver.data.UniqueStack;
+import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException;
+import com.google.clearsilver.jsilver.exceptions.JSilverIOException;
+import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
+import com.google.clearsilver.jsilver.functions.FunctionExecutor;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.values.Value;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * Default implementation of RenderingContext.
+ */
+public class DefaultRenderingContext implements RenderingContext, FunctionExecutor {
+
+  public static final Logger logger = Logger.getLogger(DefaultRenderingContext.class.getName());
+  private final DataContext dataContext;
+  private final ResourceLoader resourceLoader;
+  private final Appendable out;
+  private final FunctionExecutor globalFunctionExecutor;
+  private final AutoEscapeOptions autoEscapeOptions;
+  private final UniqueStack<String> includeStack;
+
+  private List<String> escaperStack = new ArrayList<String>(8); // seems like a reasonable initial
+                                                                // capacity.
+  private String currentEscaper; // optimization to reduce List lookup.
+
+  private List<Template> executionStack = new ArrayList<Template>(8);
+
+  private Map<String, Macro> macros = new HashMap<String, Macro>();
+  private List<EscapeMode> autoEscapeStack = new ArrayList<EscapeMode>();
+  private EscapeMode autoEscapeMode;
+  private AutoEscapeContext autoEscapeContext;
+  private int line;
+  private int column;
+  private AutoEscapeContext.AutoEscapeState startingAutoEscapeState;
+
+  public DefaultRenderingContext(DataContext dataContext, ResourceLoader resourceLoader,
+      Appendable out, FunctionExecutor globalFunctionExecutor, AutoEscapeOptions autoEscapeOptions) {
+    this.dataContext = dataContext;
+    this.resourceLoader = resourceLoader;
+    this.out = out;
+    this.globalFunctionExecutor = globalFunctionExecutor;
+    this.autoEscapeOptions = autoEscapeOptions;
+    this.autoEscapeMode = EscapeMode.ESCAPE_NONE;
+    this.autoEscapeContext = null;
+    this.includeStack = new UniqueStack<String>();
+  }
+
+  /**
+   * Lookup a function by name, execute it and return the results.
+   */
+  @Override
+  public Value executeFunction(String name, Value... args) {
+    return globalFunctionExecutor.executeFunction(name, args);
+  }
+
+  @Override
+  public void escape(String name, String input, Appendable output) throws IOException {
+    globalFunctionExecutor.escape(name, input, output);
+  }
+
+  @Override
+  public boolean isEscapingFunction(String name) {
+    return globalFunctionExecutor.isEscapingFunction(name);
+  }
+
+  @Override
+  public void pushEscapingFunction(String name) {
+    escaperStack.add(currentEscaper);
+    if (name == null || name.equals("")) {
+      currentEscaper = null;
+    } else {
+      currentEscaper = name;
+    }
+  }
+
+  @Override
+  public void popEscapingFunction() {
+    int len = escaperStack.size();
+    if (len == 0) {
+      throw new IllegalStateException("No more escaping functions to pop.");
+    }
+    currentEscaper = escaperStack.remove(len - 1);
+  }
+
+  @Override
+  public void writeEscaped(String text) {
+    // If runtime auto escaping is enabled, only apply it if
+    // we are not going to do any other default escaping on the variable.
+    boolean applyAutoEscape = isRuntimeAutoEscaping() && (currentEscaper == null);
+    if (applyAutoEscape) {
+      autoEscapeContext.setCurrentPosition(line, column);
+      pushEscapingFunction(autoEscapeContext.getEscapingFunctionForCurrentState());
+    }
+    try {
+      if (shouldLogEscapedVariables()) {
+        StringBuilder tmp = new StringBuilder();
+        globalFunctionExecutor.escape(currentEscaper, text, tmp);
+        if (!tmp.toString().equals(text)) {
+          logger.warning(new StringBuilder(getLoggingPrefix()).append(" Auto-escape changed [")
+              .append(text).append("] to [").append(tmp.toString()).append("]").toString());
+        }
+        out.append(tmp);
+      } else {
+        globalFunctionExecutor.escape(currentEscaper, text, out);
+      }
+    } catch (IOException e) {
+      throw new JSilverIOException(e);
+    } finally {
+      if (applyAutoEscape) {
+        autoEscapeContext.insertText();
+        popEscapingFunction();
+      }
+    }
+  }
+
+  private String getLoggingPrefix() {
+    return "[" + getCurrentResourceName() + ":" + line + ":" + column + "]";
+  }
+
+  private boolean shouldLogEscapedVariables() {
+    return (autoEscapeOptions != null && autoEscapeOptions.getLogEscapedVariables());
+  }
+
+  @Override
+  public void writeUnescaped(CharSequence text) {
+    if (isRuntimeAutoEscaping() && (currentEscaper == null)) {
+      autoEscapeContext.setCurrentPosition(line, column);
+      autoEscapeContext.parseData(text.toString());
+    }
+    try {
+      out.append(text);
+    } catch (IOException e) {
+      throw new JSilverIOException(e);
+    }
+  }
+
+  @Override
+  public void pushExecutionContext(Template template) {
+    executionStack.add(template);
+  }
+
+  @Override
+  public void popExecutionContext() {
+    executionStack.remove(executionStack.size() - 1);
+  }
+
+  @Override
+  public void setCurrentPosition(int line, int column) {
+    // TODO: Should these be saved in executionStack as part
+    // of pushExecutionContext?
+    this.line = line;
+    this.column = column;
+  }
+
+  @Override
+  public void registerMacro(String name, Macro macro) {
+    macros.put(name, macro);
+  }
+
+  @Override
+  public Macro findMacro(String name) {
+    Macro macro = macros.get(name);
+    if (macro == null) {
+      throw new JSilverInterpreterException("No such macro: " + name);
+    }
+    return macro;
+  }
+
+  @Override
+  public DataContext getDataContext() {
+    return dataContext;
+  }
+
+  @Override
+  public ResourceLoader getResourceLoader() {
+    return resourceLoader;
+  }
+
+  @Override
+  public AutoEscapeOptions getAutoEscapeOptions() {
+    return autoEscapeOptions;
+  }
+
+  @Override
+  public EscapeMode getAutoEscapeMode() {
+    if (isRuntimeAutoEscaping() || (currentEscaper != null)) {
+      return EscapeMode.ESCAPE_NONE;
+    } else {
+      return autoEscapeMode;
+    }
+  }
+
+  @Override
+  public void pushAutoEscapeMode(EscapeMode mode) {
+    if (isRuntimeAutoEscaping()) {
+      throw new JSilverInterpreterException(
+          "cannot call pushAutoEscapeMode while runtime auto escaping is in progress");
+    }
+    autoEscapeStack.add(autoEscapeMode);
+    autoEscapeMode = mode;
+  }
+
+  @Override
+  public void popAutoEscapeMode() {
+    int len = autoEscapeStack.size();
+    if (len == 0) {
+      throw new IllegalStateException("No more auto escaping modes to pop.");
+    }
+    autoEscapeMode = autoEscapeStack.remove(autoEscapeStack.size() - 1);
+  }
+
+  @Override
+  public boolean isRuntimeAutoEscaping() {
+    return autoEscapeContext != null;
+  }
+
+  /**
+   * {@inheritDoc}
+   * 
+   * @throws JSilverInterpreterException if startRuntimeAutoEscaping is called while runtime
+   *         autoescaping is already in progress.
+   */
+  @Override
+  public void startRuntimeAutoEscaping() {
+    if (isRuntimeAutoEscaping()) {
+      throw new JSilverInterpreterException("startRuntimeAutoEscaping() is not re-entrant at "
+          + getCurrentResourceName());
+    }
+    if (!autoEscapeMode.equals(EscapeMode.ESCAPE_NONE)) {
+      // TODO: Get the resourceName as a parameter to this function
+      autoEscapeContext = new AutoEscapeContext(autoEscapeMode, getCurrentResourceName());
+      startingAutoEscapeState = autoEscapeContext.getCurrentState();
+    } else {
+      autoEscapeContext = null;
+    }
+  }
+
+  private String getCurrentResourceName() {
+    if (executionStack.size() == 0) {
+      return "";
+    } else {
+      return executionStack.get(executionStack.size() - 1).getDisplayName();
+    }
+  }
+
+  @Override
+  public void stopRuntimeAutoEscaping() {
+    if (autoEscapeContext != null) {
+      if (!startingAutoEscapeState.equals(autoEscapeContext.getCurrentState())) {
+        // We do not allow a macro call to change context of the rest of the template.
+        // Since the rest of the template has already been auto-escaped at parse time
+        // with the assumption that the macro call will not modify the context.
+        throw new JSilverAutoEscapingException("Macro starts in context " + startingAutoEscapeState
+            + " but ends in different context " + autoEscapeContext.getCurrentState(),
+            autoEscapeContext.getResourceName());
+      }
+    }
+    autoEscapeContext = null;
+  }
+
+  @Override
+  public boolean pushIncludeStackEntry(String templateName) {
+    return includeStack.push(templateName);
+  }
+
+  @Override
+  public boolean popIncludeStackEntry(String templateName) {
+    return templateName.equals(includeStack.pop());
+  }
+
+  @Override
+  public Iterable<String> getIncludedTemplateNames() {
+    return includeStack;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/template/DelegatingTemplateLoader.java b/src/com/google/clearsilver/jsilver/template/DelegatingTemplateLoader.java
new file mode 100644
index 0000000..9d5d654
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/template/DelegatingTemplateLoader.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.template;
+
+/**
+ * Interface that extends TemplateLoader with a method to set the TemplateLoader that Templates
+ * provided by this TemplateLoader should use to resolve includes and such. Useful callback for
+ * informing TemplateLoaders in the chain what the topmost TemplateLoader is.
+ */
+public interface DelegatingTemplateLoader extends TemplateLoader {
+
+  /**
+   * TemplateLoader that Templates will delegate back to for includes etc.
+   */
+  public void setTemplateLoaderDelegate(TemplateLoader templateLoaderDelegate);
+}
diff --git a/src/com/google/clearsilver/jsilver/template/HtmlWhiteSpaceStripper.java b/src/com/google/clearsilver/jsilver/template/HtmlWhiteSpaceStripper.java
new file mode 100644
index 0000000..6b4f039
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/template/HtmlWhiteSpaceStripper.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.template;
+
+import java.io.IOException;
+
+/**
+ * HTML whitespace stripper to be used by JSilver.  It removes leading and
+ * trailing whitespace, it reduces contiguous whitespace characters with just
+ * the first character, and removes lines of nothing but whitespace.
+ *
+ * It does not strip whitespace inside the following elements:
+ * <ul>
+ * <li> PRE
+ * <li> VERBATIM
+ * <li> TEXTAREA
+ * <li> SCRIPT
+ * </ul>
+ * It also strips out empty lines and leading whitespace inside HTML tags (i.e.
+ * between '<' and '>') and inside SCRIPT elements.  It leaves trailing
+ * whitespace since that is more costly to remove and tends to not be common
+ * based on how templates are created (they don't have trailing whitespace).
+ * <p>
+ * Loadtests indicate that this class can strip whitespace almost as quickly
+ * as just reading every character from a string (20% slower).
+ * <p>
+ * While not strictly compatible with the JNI Clearsilver whitestripping
+ * function, we are not aware of any differences that yield functionally
+ * different HTML output. However, we encourage users to verify for themselves
+ * and report any differences.
+ */
+public class HtmlWhiteSpaceStripper implements Appendable {
+
+  // Object to output stripped content to.
+  private final Appendable out;
+  // Level of whitespace stripping to perform. (Currently not used).
+  // TODO: Determine what the exact differences are in levels in
+  // JNI Clearsilver and see if it is worth porting it.
+  private final int level;
+
+  // Has any non-whitespace character been seen since the start of the line.
+  private boolean nonWsSeen = false;
+  // Was there previously one or more whitespace chars? If so, we should output
+  // the first whitespace char in the sequence before any other non-whitespace
+  // character. 0 signifies no pending whitespace.
+  private char pendingWs = 0;
+
+  // We just saw the start of an HTML tag '<'.
+  private boolean startHtmlTag = false;
+  // Are we currently in an opening HTML tag (not "</").
+  private boolean inOpenTag = false;
+  // Are we currently in a closing HTML tag.
+  private boolean inCloseTag = false;
+  // Are we currently in an HTML tag name.
+  private boolean inTagName = false;
+
+  // Are we between <textarea> tags
+  private int textAreaScope = 0;
+  // Are we between <pre> tags
+  private int preScope = 0;
+  // Are we between verbatim flags
+  private int verbatimScope = 0;
+  // Are we between <script> tags
+  private int scriptScope = 0;
+
+  // Used to hold HTML tag element name.
+  private StringBuilder tagName = new StringBuilder(16);
+
+  /**
+   * Intermediate Appendable object that strips whitespace as it passes through characters to
+   * another Appendable object.
+   * 
+   * @param out The Appendable object to dump the stripped output to.
+   */
+  public HtmlWhiteSpaceStripper(Appendable out) {
+    this(out, 1);
+  }
+
+  /**
+   * Intermediate Appendable object that strips whitespace as it passes through characters to
+   * another Appendable object.
+   * 
+   * @param out The Appendable object to dump the stripped output to.
+   * @param level Ignored for now.
+   */
+  public HtmlWhiteSpaceStripper(Appendable out, int level) {
+    this.out = out;
+    this.level = level;
+  }
+
+  @Override
+  public String toString() {
+    return out.toString();
+  }
+
+  @Override
+  public Appendable append(CharSequence csq) throws IOException {
+    return append(csq, 0, csq.length());
+  }
+
+  @Override
+  public Appendable append(CharSequence csq, int start, int end) throws IOException {
+    for (int i = start; i < end; i++) {
+      append(csq.charAt(i));
+    }
+    return this;
+  }
+
+  @Override
+  public Appendable append(char c) throws IOException {
+    if (inOpenTag || inCloseTag) {
+      // In an HTML tag.
+      if (startHtmlTag) {
+        // This is the first character in an HTML tag.
+        if (c == '/') {
+          // We are in a close tag.
+          inOpenTag = false;
+          inCloseTag = true;
+        } else {
+          // This is the first non-'/' character in an HTML tag.
+          startHtmlTag = false;
+          if (isTagNameStartChar(c)) {
+            // we have a valid tag name first char.
+            inTagName = true;
+            tagName.append(c);
+          }
+        }
+      } else if (inTagName) {
+        // We were last parsing the name of an HTML attribute.
+        if (isTagNameChar(c)) {
+          tagName.append(c);
+        } else {
+          processTagName();
+          inTagName = false;
+        }
+      }
+      if (c == '>') {
+        // We are at the end of the tag.
+        inOpenTag = inCloseTag = false;
+        nonWsSeen = true;
+      }
+      stripLeadingWsAndEmptyLines(c);
+    } else {
+      // Outside of HTML tag.
+      if (c == '<') {
+        // Starting a new HTML tag.
+        inOpenTag = true;
+        startHtmlTag = true;
+      }
+      if (preScope > 0 || verbatimScope > 0 || textAreaScope > 0) {
+        // In an HTML element that we want to preserve whitespace in.
+        out.append(c);
+      } else if (scriptScope > 0) {
+        // Want to remove newlines only.
+        stripLeadingWsAndEmptyLines(c);
+      } else {
+        stripAll(c);
+      }
+    }
+
+    return this;
+  }
+
+  private void stripLeadingWsAndEmptyLines(char c) throws IOException {
+    // Detect and delete empty lines.
+    switch (c) {
+      case '\n':
+        if (nonWsSeen) {
+          out.append(c);
+        }
+        nonWsSeen = false;
+        break;
+      case ' ':
+      case '\t':
+      case '\r':
+        if (nonWsSeen) {
+          out.append(c);
+        }
+        break;
+      default:
+        if (!nonWsSeen) {
+          nonWsSeen = true;
+        }
+        out.append(c);
+    }
+  }
+
+  private void stripAll(char c) throws IOException {
+    // All that remains is content that is safe to remove whitespace from.
+    switch (c) {
+      case '\n':
+        if (nonWsSeen) {
+          // We don't want blank lines so we don't output linefeed unless we
+          // saw non-whitespace.
+          out.append(c);
+        }
+        // We don't want trailing whitespace.
+        pendingWs = 0;
+        nonWsSeen = false;
+        break;
+      case ' ':
+      case '\t':
+      case '\r':
+        if (nonWsSeen) {
+          pendingWs = c;
+        } else {
+          // Omit leading whitespace
+        }
+        break;
+      default:
+        if (pendingWs != 0) {
+          out.append(pendingWs);
+          pendingWs = 0;
+        }
+        nonWsSeen = true;
+        out.append(c);
+    }
+  }
+
+  private int updateScope(int current, int inc) {
+    current += inc;
+    return current < 0 ? 0 : current;
+  }
+
+  /**
+   * This code assumes well-formed HTML as input with HTML elements opening and closing properly in
+   * the right order.
+   */
+  private void processTagName() {
+    inTagName = false;
+    String name = tagName.toString();
+    tagName.delete(0, tagName.length());
+    int inc = inOpenTag ? 1 : -1;
+    if ("textarea".equalsIgnoreCase(name)) {
+      textAreaScope = updateScope(textAreaScope, inc);
+    } else if ("pre".equalsIgnoreCase(name)) {
+      preScope = updateScope(preScope, inc);
+    } else if ("verbatim".equalsIgnoreCase(name)) {
+      verbatimScope = updateScope(verbatimScope, inc);
+    } else if ("script".equalsIgnoreCase(name)) {
+      scriptScope = updateScope(scriptScope, inc);
+    }
+  }
+
+  private boolean isTagNameStartChar(char c) {
+    return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
+  }
+
+  // From W3C HTML spec.
+  private boolean isTagNameChar(char c) {
+    return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || (c == '_')
+        || (c == '-') || (c == ':') || (c == '.');
+  }
+
+  /**
+   * Note, we treat '\n' as a separate special character as it has special rules since it determines
+   * what a 'line' of content is for doing leading and trailing whitespace removal and empty line
+   * removal.
+   */
+  private boolean isWs(char c) {
+    return c == ' ' || c == '\t' || c == '\r';
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/template/Macro.java b/src/com/google/clearsilver/jsilver/template/Macro.java
new file mode 100644
index 0000000..45fc2d9
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/template/Macro.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.template;
+
+import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
+
+/**
+ * An executable macro. This exhibits all the same characteristics of a Template.
+ */
+public interface Macro extends Template {
+
+  /**
+   * Name of macro (e.g. showTable). Used to generate error messages.
+   */
+  String getMacroName();
+
+  /**
+   * Get the name of the nth argument defined in the macro. Throws exception if the argument is not
+   * found.
+   */
+  String getArgumentName(int index) throws JSilverInterpreterException;
+
+  /**
+   * Return the number of arguments this macro expects. Must be equal to the number of arguments
+   * supplied.
+   */
+  int getArgumentCount();
+}
diff --git a/src/com/google/clearsilver/jsilver/template/RenderingContext.java b/src/com/google/clearsilver/jsilver/template/RenderingContext.java
new file mode 100644
index 0000000..53f0ba9
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/template/RenderingContext.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.template;
+
+import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.DataContext;
+import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.clearsilver.jsilver.values.Value;
+
+/**
+ * State that is shared across a template rendering.
+ */
+public interface RenderingContext {
+
+  /**
+   * Execute a named function. Will throw an exception if the function is not found, or the function
+   * does not return a value.
+   */
+  Value executeFunction(String name, Value... args) throws JSilverInterpreterException;
+
+  /**
+   * Look up a function by name, and report whether it is an escaping function. Usually, the output
+   * of escaping functions will be written using writeUnescaped() instead of writeEscaped().
+   * 
+   * @see com.google.clearsilver.jsilver.compiler.EscapingEvaluator
+   */
+  public boolean isEscapingFunction(String name);
+
+  /**
+   * Write some text out, using the current escaping function.
+   * 
+   * @see #pushEscapingFunction(String)
+   * @see #popEscapingFunction()
+   */
+  void writeEscaped(String text);
+
+  /**
+   * Write some text out, without doing any escaping. Use this only for trusted content.
+   */
+  void writeUnescaped(CharSequence text);
+
+  /**
+   * Push a new escaping function onto the context. All calls to writeEscape() will use this new
+   * function. When done with this function, call popEscapingFunction() to return to the previous
+   * escaping function.
+   * 
+   * If the escaping function is not found, an exception will be thrown. To use no escaping function
+   * pass null as the escaperName.
+   * 
+   * @see #popEscapingFunction()
+   */
+  void pushEscapingFunction(String escaperName);
+
+  /**
+   * @see #pushEscapingFunction(String)
+   */
+  void popEscapingFunction();
+
+  /**
+   * Push a new template onto the current execution context. This is to generate stack traces when
+   * things go wrong deep in templates, to allow users to figure out how they got there.
+   * 
+   * @see #popExecutionContext()
+   */
+  void pushExecutionContext(Template template);
+
+  /**
+   * @see #pushExecutionContext(Template)
+   */
+  void popExecutionContext();
+
+  /**
+   * Sets the current position in the template. Used to help generate error messages.
+   */
+  void setCurrentPosition(int line, int column);
+
+  /**
+   * Register a macro in the current rendering context. This macro will be available to all other
+   * templates, regardless of implementation.
+   */
+  void registerMacro(String name, Macro macro);
+
+  /**
+   * Lookup a macro that's already been registered. Throws JSilverInterpreterException if macro not
+   * found.
+   */
+  Macro findMacro(String name) throws JSilverInterpreterException;
+
+  /**
+   * Return the DataContext object associated with this RenderingContext.
+   */
+  DataContext getDataContext();
+
+  /**
+   * Returns the ResourceLoader object to use to fetch files needed to render the current template.
+   */
+  ResourceLoader getResourceLoader();
+
+  /**
+   * Returns the configured AutoEscapeOptions to be used while rendering the current template.
+   */
+  AutoEscapeOptions getAutoEscapeOptions();
+
+  /**
+   * Push a new auto escaping mode onto the context. Any template loads via include() or
+   * evaluateVariable() will use this auto escaping mode when loading the template.
+   * 
+   * @see #popAutoEscapeMode
+   * 
+   */
+  void pushAutoEscapeMode(EscapeMode mode);
+
+  /**
+   * @see #pushAutoEscapeMode
+   */
+  void popAutoEscapeMode();
+
+  /**
+   * Read the currently set auto escape mode.
+   * 
+   * @return EscapeMode
+   */
+  EscapeMode getAutoEscapeMode();
+
+  /**
+   * Indicates whether runtime auto escaping is in progress.
+   * 
+   * @return true if runtime auto escaping is enabled.
+   * 
+   * @see #isRuntimeAutoEscaping
+   */
+  boolean isRuntimeAutoEscaping();
+
+  /**
+   * Start an auto escaping context to parse and auto escape template contents as they are being
+   * rendered.
+   * 
+   * @see #stopRuntimeAutoEscaping
+   */
+  void startRuntimeAutoEscaping();
+
+  /**
+   * Stop runtime auto escaping.
+   * 
+   * @see #startRuntimeAutoEscaping
+   */
+  void stopRuntimeAutoEscaping();
+
+  /**
+   * Adds an entry with a name of the template to the stack keeping all names of already included
+   * templates. The name is added only if it is not already on the stack.
+   * 
+   * @param templateName name of the template to be added to the stack. If {@code null} a {@code
+   *        NullPointerException} will be thrown.
+   * @return true if the {@code templateName} was added.
+   */
+  boolean pushIncludeStackEntry(String templateName);
+
+  /**
+   * Removes an entry with a name of the template from the stack.
+   * 
+   * @param templateName
+   * @return true if the {@code templateName} was on the stack.
+   */
+  boolean popIncludeStackEntry(String templateName);
+
+  /**
+   * Returns the ordered, mutable stack of names of included templates.
+   */
+  Iterable<String> getIncludedTemplateNames();
+}
diff --git a/src/com/google/clearsilver/jsilver/template/Template.java b/src/com/google/clearsilver/jsilver/template/Template.java
new file mode 100644
index 0000000..9c4142b
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/template/Template.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.template;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+
+import java.io.IOException;
+
+/**
+ * Represents a template that can be rendered with data.
+ */
+public interface Template {
+
+  /**
+   * Render the template.
+   * 
+   * @param data Data to merge with template.
+   * @param out Target to write to.
+   * @param resourceLoader ResourceLoader to use instead of the default template one when loading
+   *        files.
+   */
+  void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException;
+
+  /**
+   * Render the template with a custom RenderingContext.
+   * 
+   * @param context RenderingContext to use during rendering.
+   */
+  void render(RenderingContext context) throws IOException;
+
+  /**
+   * Create a new RenderingContext.
+   * 
+   * @param data Data to merge with template.
+   * @param out Target to write to.
+   * @param resourceLoader ResourceLoader to load files.
+   */
+  RenderingContext createRenderingContext(Data data, Appendable out, ResourceLoader resourceLoader);
+
+  /**
+   * Name of template (e.g. mytemplate.cs).
+   */
+  String getTemplateName();
+
+  /**
+   * Name to use when displaying error or log messages. May return the same value as
+   * #getTemplateName, or may contain more information.
+   */
+  String getDisplayName();
+
+  /**
+   * Return the EscapeMode in which this template was generated.
+   * 
+   * @return EscapeMode
+   */
+  EscapeMode getEscapeMode();
+}
diff --git a/src/com/google/clearsilver/jsilver/template/TemplateLoader.java b/src/com/google/clearsilver/jsilver/template/TemplateLoader.java
new file mode 100644
index 0000000..bc49869
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/template/TemplateLoader.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.template;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+
+/**
+ * Loads a Template.
+ */
+public interface TemplateLoader {
+
+  /**
+   * Load a template from a named resource, with the provided escape mode. If the mode is
+   * ESCAPE_HTML, ESCAPE_URL or ESCAPE_JS, the corresponding escaping will be all variables in the
+   * template. If the mode is ESCAPE_AUTO, enable <a href="http://go/autoescapecs">auto escaping</a>
+   * on templates. For each variable in the template, this will determine what type of escaping
+   * should be applied to the variable, and automatically apply this escaping.
+   * 
+   * @param templateName e.g. some/path/to/template.cs
+   * @param resourceLoader the ResourceLoader object to use to load any files needed to satisfy this
+   *        request.
+   * @param escapeMode the type of escaping to apply to the entire template.
+   */
+  Template load(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode);
+
+  /**
+   * Create a temporary template from content, with the provided escape mode. If the mode is
+   * ESCAPE_HTML, ESCAPE_URL or ESCAPE_JS, the corresponding escaping will be all variables in the
+   * template. If the mode is ESCAPE_AUTO, enable <a href="http://go/autoescapecs">auto escaping</a>
+   * on templates. For each variable in the template, this will determine what type of escaping
+   * should be applied to the variable, and automatically apply this escaping.
+   * 
+   * @param name A name to identify the temporary template in stack traces.
+   * @param content e.g. "Hello &lt;cs var:name &gt;"
+   * @param escapeMode the type of escaping to apply to the entire template.
+   */
+  Template createTemp(String name, String content, EscapeMode escapeMode);
+}
diff --git a/src/com/google/clearsilver/jsilver/values/NumberValue.java b/src/com/google/clearsilver/jsilver/values/NumberValue.java
new file mode 100644
index 0000000..797c7d7
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/values/NumberValue.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.values;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+
+/**
+ * A simple numeric value.
+ * 
+ * @see Value
+ */
+class NumberValue extends Value {
+
+  private final int value;
+
+  public NumberValue(int value, EscapeMode escapeMode, boolean partiallyEscaped) {
+    super(escapeMode, partiallyEscaped);
+    this.value = value;
+  }
+
+  @Override
+  public boolean asBoolean() {
+    return value != 0;
+  }
+
+  @Override
+  public String asString() {
+    return Integer.toString(value);
+  }
+
+  @Override
+  public int asNumber() {
+    return value;
+  }
+
+  @Override
+  public boolean exists() {
+    return true;
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return value == 0;
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/values/StringValue.java b/src/com/google/clearsilver/jsilver/values/StringValue.java
new file mode 100644
index 0000000..8adfda2
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/values/StringValue.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.values;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+
+/**
+ * A simple string value.
+ * 
+ * @see Value
+ */
+class StringValue extends VariantValue {
+
+  private final String value;
+
+  public StringValue(String value, EscapeMode escapeMode, boolean partiallyEscaped) {
+    super(escapeMode, partiallyEscaped);
+    this.value = value;
+  }
+
+  @Override
+  protected String value() {
+    return value;
+  }
+
+  @Override
+  public boolean exists() {
+    return true;
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return value.length() == 0;
+  }
+}
diff --git a/src/com/google/clearsilver/jsilver/values/Value.java b/src/com/google/clearsilver/jsilver/values/Value.java
new file mode 100644
index 0000000..87a579e
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/values/Value.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.values;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.DataContext;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Dynamic typing system used by JSilver interpreter. A value (e.g. "2") can act as a string,
+ * integer and boolean. Values can be literal or references to variables held elsewhere (e.g. in
+ * external data structures such as HDF).
+ */
+public abstract class Value {
+
+  private static final Map<EscapeMode, Value> EMPTY_PART_ESCAPED;
+  private static final Map<EscapeMode, Value> EMPTY_UNESCAPED;
+  private static final Map<EscapeMode, Value> ZERO_PART_ESCAPED;
+  private static final Map<EscapeMode, Value> ZERO_UNESCAPED;
+  private static final Map<EscapeMode, Value> ONE_PART_ESCAPED;
+  private static final Map<EscapeMode, Value> ONE_UNESCAPED;
+
+  static {
+    // Currently a Value's EscapeMode is either ESCAPE_NONE (no escaping) or
+    // ESCAPE_IS_CONSTANT (is a constant or has some escaping applied).
+    // This may change in the future if we implement stricter auto escaping.
+    // See EscapeMode.combineModes.
+    EMPTY_PART_ESCAPED = new HashMap<EscapeMode, Value>(2);
+    EMPTY_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE,
+        new StringValue("", EscapeMode.ESCAPE_NONE, true));
+    EMPTY_PART_ESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new StringValue("",
+        EscapeMode.ESCAPE_IS_CONSTANT, true));
+
+    EMPTY_UNESCAPED = new HashMap<EscapeMode, Value>(2);
+    EMPTY_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new StringValue("", EscapeMode.ESCAPE_NONE, false));
+    EMPTY_UNESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new StringValue("",
+        EscapeMode.ESCAPE_IS_CONSTANT, false));
+
+    ZERO_PART_ESCAPED = new HashMap<EscapeMode, Value>(2);
+    ZERO_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(0, EscapeMode.ESCAPE_NONE, true));
+    ZERO_PART_ESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(0,
+        EscapeMode.ESCAPE_IS_CONSTANT, true));
+
+    ZERO_UNESCAPED = new HashMap<EscapeMode, Value>(2);
+    ZERO_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(0, EscapeMode.ESCAPE_NONE, false));
+    ZERO_UNESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(0,
+        EscapeMode.ESCAPE_IS_CONSTANT, false));
+
+    ONE_PART_ESCAPED = new HashMap<EscapeMode, Value>(2);
+    ONE_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(1, EscapeMode.ESCAPE_NONE, true));
+    ONE_PART_ESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(1,
+        EscapeMode.ESCAPE_IS_CONSTANT, true));
+
+    ONE_UNESCAPED = new HashMap<EscapeMode, Value>(2);
+    ONE_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(1, EscapeMode.ESCAPE_NONE, false));
+    ONE_UNESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(1,
+        EscapeMode.ESCAPE_IS_CONSTANT, false));
+  }
+
+  /**
+   * True if either the {@code Value} was escaped, or it was created from a combination of escaped
+   * and unescaped values.
+   */
+  private final boolean partiallyEscaped;
+  private final EscapeMode escapeMode;
+
+  public Value(EscapeMode escapeMode, boolean partiallyEscaped) {
+    this.escapeMode = escapeMode;
+    this.partiallyEscaped = partiallyEscaped;
+  }
+
+  /**
+   * Fetch value as boolean. All non empty strings and numbers != 0 are treated as true.
+   */
+  public abstract boolean asBoolean();
+
+  /**
+   * Fetch value as string.
+   */
+  public abstract String asString();
+
+  /**
+   * Fetch value as number. If number is not parseable, 0 is returned (this is consistent with
+   * ClearSilver).
+   */
+  public abstract int asNumber();
+
+  /**
+   * Whether this value exists. Literals always return true, but variable references will return
+   * false if the value behind it is null.
+   */
+  public abstract boolean exists();
+
+  public abstract boolean isEmpty();
+
+  /**
+   * Create a literal value using an int.
+   */
+  public static Value literalValue(int value, EscapeMode mode, boolean partiallyEscaped) {
+    return getIntValue(mode, partiallyEscaped, value);
+  }
+
+  /**
+   * Create a literal value using a String.
+   */
+  public static Value literalValue(String value, EscapeMode mode, boolean partiallyEscaped) {
+    if (value.isEmpty()) {
+      Value v = (partiallyEscaped ? EMPTY_PART_ESCAPED : EMPTY_UNESCAPED).get(mode);
+      if (v != null) {
+        return v;
+      }
+    }
+
+    return new StringValue(value, mode, partiallyEscaped);
+  }
+
+  /**
+   * Create a literal value using a boolean.
+   */
+  public static Value literalValue(boolean value, EscapeMode mode, boolean partiallyEscaped) {
+    return getIntValue(mode, partiallyEscaped, value ? 1 : 0);
+  }
+
+  private static Value getIntValue(EscapeMode mode, boolean partiallyEscaped, int num) {
+    Value v = null;
+    if (num == 0) {
+      v = (partiallyEscaped ? ZERO_PART_ESCAPED : ZERO_UNESCAPED).get(mode);
+    } else if (num == 1) {
+      v = (partiallyEscaped ? ONE_PART_ESCAPED : ONE_UNESCAPED).get(mode);
+    }
+
+    if (v != null) {
+      return v;
+    }
+
+    return new NumberValue(num, mode, partiallyEscaped);
+  }
+
+  /**
+   * Create a literal value using an int with a {@code escapeMode} of {@code
+   * EscapeMode.ESCAPE_IS_CONSTANT} and {@code partiallyEscaped} based on the {@code
+   * partiallyEscaped} values of the inputs.
+   * 
+   * @param value integer value of the literal
+   * @param inputs Values that were used to compute the integer value.
+   */
+  public static Value literalConstant(int value, Value... inputs) {
+    boolean isPartiallyEscaped = false;
+    for (Value input : inputs) {
+      if (input.isPartiallyEscaped()) {
+        isPartiallyEscaped = true;
+        break;
+      }
+    }
+    return literalValue(value, EscapeMode.ESCAPE_IS_CONSTANT, isPartiallyEscaped);
+  }
+
+  /**
+   * Create a literal value using a string with a {@code escapeMode} of {@code
+   * EscapeMode.ESCAPE_IS_CONSTANT} and {@code partiallyEscaped} based on the {@code
+   * partiallyEscaped} values of the inputs.
+   * 
+   * @param value String value of the literal
+   * @param inputs Values that were used to compute the string value.
+   */
+  public static Value literalConstant(String value, Value... inputs) {
+    boolean isPartiallyEscaped = false;
+    for (Value input : inputs) {
+      if (input.isPartiallyEscaped()) {
+        isPartiallyEscaped = true;
+        break;
+      }
+    }
+    return literalValue(value, EscapeMode.ESCAPE_IS_CONSTANT, isPartiallyEscaped);
+  }
+
+  /**
+   * Create a literal value using a boolean with a {@code escapeMode} of {@code
+   * EscapeMode.ESCAPE_IS_CONSTANT} and {@code partiallyEscaped} based on the {@code
+   * partiallyEscaped} values of the inputs.
+   * 
+   * @param value boolean value of the literal
+   * @param inputs Values that were used to compute the boolean value.
+   */
+  public static Value literalConstant(boolean value, Value... inputs) {
+    boolean isPartiallyEscaped = false;
+    for (Value input : inputs) {
+      if (input.isPartiallyEscaped()) {
+        isPartiallyEscaped = true;
+        break;
+      }
+    }
+    return literalValue(value, EscapeMode.ESCAPE_IS_CONSTANT, isPartiallyEscaped);
+  }
+
+  /**
+   * Create a value linked to a variable name.
+   * 
+   * @param name The pathname of the variable relative to the given {@link DataContext}
+   * @param dataContext The DataContext defining the scope and Data objects to use when
+   *        dereferencing the name.
+   * @return A Value object that allows access to the variable name, the variable Data object (if it
+   *         exists) and to the value of the variable.
+   */
+  public static Value variableValue(String name, DataContext dataContext) {
+    return new VariableValue(name, dataContext);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other == null || !(other instanceof Value)) {
+      return false;
+    }
+    Value otherValue = (Value) other;
+    // This behaves the same way as ClearSilver.
+    return exists() == otherValue.exists()
+        && (asString().equals(otherValue.asString()) || (isEmpty() && otherValue.isEmpty()));
+  }
+
+  @Override
+  public int hashCode() {
+    return toString().hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return asString();
+  }
+
+  public boolean isPartiallyEscaped() {
+    return partiallyEscaped;
+  }
+
+  /**
+   * Indicates the escaping that was applied to the expression represented by this value.
+   * 
+   * <p>
+   * May be checked by the JSilver code before applying autoescaping. It differs from {@code
+   * isEscaped}, which is true iff any part of the variable expression contains an escaping
+   * function, even if the entire expression has not been escaped. Both methods are required,
+   * {@code isEscaped} to determine whether &lt;?cs escape &gt; commands should be applied, and
+   * {@code getEscapeMode} for autoescaping. This is done to maintain compatibility with
+   * ClearSilver's behaviour.
+   * 
+   * @return {@code EscapeMode.ESCAPE_IS_CONSTANT} if the value represents a constant string
+   *         literal. Or the appropriate {@link EscapeMode} if the value is the output of an
+   *         escaping function.
+   * 
+   * @see EscapeMode
+   */
+  public EscapeMode getEscapeMode() {
+    return escapeMode;
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/values/VariableValue.java b/src/com/google/clearsilver/jsilver/values/VariableValue.java
new file mode 100644
index 0000000..5e16ede
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/values/VariableValue.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.values;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.clearsilver.jsilver.data.DataContext;
+import com.google.clearsilver.jsilver.data.TypeConverter;
+
+/**
+ * A value linked to a variable reference. When this value is evaluated in an expression a Data
+ * object will be fetched from the provided DataContext and and its value returned or {@code null}
+ * if there is no Data object associated with the given name.
+ * 
+ * @see Value
+ * @see Data
+ */
+public class VariableValue extends VariantValue {
+  // TODO: Make this non-public and come up with a smarter way
+  // for it to be used elsewhere without having to be used in a cast.
+
+  private final String name;
+  private final DataContext dataContext;
+
+  private boolean gotRef = false;
+  private Data reference;
+
+  public VariableValue(String name, DataContext dataContext) {
+    // VariableValues always have partiallyEscaped=false since they represent
+    // a Data object, not a compound expression containing escaping functions.
+    // We override getEscapeMode() to return the Data object's escape mode,
+    // so the mode we pass here is just ignored.
+    super(EscapeMode.ESCAPE_NONE, false);
+    this.dataContext = dataContext;
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Data getReference() {
+    if (!gotRef) {
+      reference = dataContext.findVariable(name, false);
+      gotRef = true;
+    }
+    return reference;
+  }
+
+  @Override
+  protected String value() {
+    Data data = getReference();
+    return data == null ? null : data.getValue();
+  }
+
+  @Override
+  public boolean exists() {
+    return TypeConverter.exists(getReference());
+  }
+
+  // Note we are not overriding asString as we want that to return the value
+  // of the node located at this address.
+  @Override
+  public String toString() {
+    return name;
+  }
+
+  @Override
+  public EscapeMode getEscapeMode() {
+    Data data = getReference();
+    if (data == null) {
+      return super.getEscapeMode();
+    }
+    return data.getEscapeMode();
+  }
+
+}
diff --git a/src/com/google/clearsilver/jsilver/values/VariantValue.java b/src/com/google/clearsilver/jsilver/values/VariantValue.java
new file mode 100644
index 0000000..b219b1f
--- /dev/null
+++ b/src/com/google/clearsilver/jsilver/values/VariantValue.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.clearsilver.jsilver.values;
+
+import com.google.clearsilver.jsilver.autoescape.EscapeMode;
+import com.google.clearsilver.jsilver.data.TypeConverter;
+
+/**
+ * Base class for values of variant types (i.e. those that can be treated as different types at
+ * runtime - e.g. "33").
+ * 
+ * @see Value
+ */
+abstract class VariantValue extends Value {
+
+  private static final String EMPTY = "";
+
+  VariantValue(EscapeMode escapeMode, boolean partiallyEscaped) {
+    super(escapeMode, partiallyEscaped);
+  }
+
+  protected abstract String value();
+
+  @Override
+  public boolean asBoolean() {
+    return TypeConverter.asBoolean(value());
+  }
+
+  @Override
+  public String asString() {
+    String value = value();
+    return value == null ? EMPTY : value;
+  }
+
+  @Override
+  public int asNumber() {
+    // TODO: Cache the result for constant values (or just get rid of this class)
+    return TypeConverter.asNumber(value());
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return asString().isEmpty();
+  }
+
+}
diff --git a/src/com/google/streamhtmlparser/ExternalState.java b/src/com/google/streamhtmlparser/ExternalState.java
new file mode 100644
index 0000000..93b67e2
--- /dev/null
+++ b/src/com/google/streamhtmlparser/ExternalState.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * A representation of the parser state suitable for use by the caller
+ * of the Parser. The meaning of each state and therefore which action
+ * the caller should perform on that state is not self-evident. In particular,
+ * it depends on which parser is used (currently {@link HtmlParser} and
+ * {@link JavascriptParser}). For examples, you will have to look
+ * at the <code>Google Template System</code> and <code>ClearSilver</code>
+ * both of which support Auto-Escaping by interfacing with our parser
+ * (using the parser written in C++).
+ *
+ * <p>The caller of the Parser will query for the current parser state at
+ * points of interest during parsing of templates. Based on the parser's
+ * current state as represented by this class, the caller can determine
+ * the appropriate escaping to apply.
+ *
+ * <p>Note: Given this class is external-facing, I considered creating
+ * an interface but it is not likely we'll ever need to add more flexibility
+ * and the class is so simple, I figured it was not warranted.
+ * 
+ *
+ * @see HtmlParser
+ * @see JavascriptParser
+ */
+public class ExternalState {
+
+  private final String name;
+
+  /**
+   * Creates an {@code ExternalState} object.
+   *
+   * @param name the name to assign to that state
+   * @see HtmlParser
+   * @see JavascriptParser
+   */
+  public ExternalState(String name) {
+    Preconditions.checkNotNull(name);   // Developer error if it happens.
+    this.name = name;
+  }
+
+  /**
+   * Returns the name of the object. The name is only needed
+   * to provide human-readable information when debugging.
+   *
+   * @return the name of that object
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Returns the string representation of this external state.
+   * The details of this representation are subject to change.
+   */
+  @Override
+  public String toString() {
+    return String.format("ExternalState: %s", name);
+  }
+}
diff --git a/src/com/google/streamhtmlparser/HtmlParser.java b/src/com/google/streamhtmlparser/HtmlParser.java
new file mode 100644
index 0000000..d37813f
--- /dev/null
+++ b/src/com/google/streamhtmlparser/HtmlParser.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser;
+
+/**
+ * Methods exposed for HTML parsing of text to facilitate implementation
+ * of Automatic context-aware escaping. The HTML parser also embeds a
+ * Javascript parser for processing Javascript fragments. In the future,
+ * it will also embed other specific parsers and hence most likely remain
+ * the main interface to callers of this package.
+ *
+ * <p>Note: These are the exact methods exposed in the original C++ Parser. The
+ * names are simply modified to conform to Java.
+ */
+public interface HtmlParser extends Parser {
+
+  /**
+   * The Parser Mode requested for parsing a given template.
+   * Currently we support:
+   * <ul>
+   * <li>{@code HTML} for HTML templates.
+   * <li>{@code JS} for javascript templates.
+   * <li>{@code CSS} for Cascading Style-Sheets templates.
+   * <li>{@code HTML_IN_TAG} for HTML templates that consist only of
+   *     HTML attribute name and value pairs. This is typically the case for
+   *     a template that is being included from a parent template where the
+   *     parent template contains the start and the closing of the HTML tag.
+   *     This is a special mode, for standard HTML templates please use
+   *     {@link #HTML}.
+   *     An example of such as template is:
+   *     <p><code>class="someClass" target="_blank"</code></p>
+   *     <p>Which could be included from a parent template that contains
+   *     an anchor tag, say:</p>
+   *     <p><code>&lt;a href="/bla" ["INCLUDED_TEMPLATE"]&gt;</code></p>
+   * </ul>
+   */
+  public enum Mode {
+    HTML,
+    JS,
+    CSS,
+    HTML_IN_TAG
+  }
+
+  /**
+   * Indicates the type of HTML attribute that the parser is currently in or
+   * {@code NONE} if the parser is not currently in an attribute.
+   * {@code URI} is for attributes taking a URI such as "href" and "src".
+   * {@code JS} is for attributes taking javascript such as "onclick".
+   * {@code STYLE} is for the "style" attribute.
+   * All other attributes fall under {@code REGULAR}.
+   *
+   * Returned by {@link HtmlParser#getAttributeType()}
+   */
+  public enum ATTR_TYPE {
+    NONE,
+    REGULAR,
+    URI,
+    JS,
+    STYLE
+  }
+
+  /**
+   * All the states in which the parser can be. These are external states.
+   * The parser has many more internal states that are not exposed and which
+   * are instead mapped to one of these external ones.
+   * {@code STATE_TEXT} the parser is in HTML proper.
+   * {@code STATE_TAG} the parser is inside an HTML tag name.
+   * {@code STATE_COMMENT} the parser is inside an HTML comment.
+   * {@code STATE_ATTR} the parser is inside an HTML attribute name.
+   * {@code STATE_VALUE} the parser is inside an HTML attribute value.
+   * {@code STATE_JS_FILE} the parser is inside javascript code.
+   * {@code STATE_CSS_FILE} the parser is inside CSS code.
+   *
+   * <p>All these states map exactly to those exposed in the C++ (original)
+   * version of the HtmlParser.
+   */
+  public final static ExternalState STATE_TEXT =
+      new ExternalState("STATE_TEXT");
+  public final static ExternalState STATE_TAG =
+      new ExternalState("STATE_TAG");
+  public final static ExternalState STATE_COMMENT =
+      new ExternalState("STATE_COMMENT");
+  public final static ExternalState STATE_ATTR =
+      new ExternalState("STATE_ATTR");
+  public final static ExternalState STATE_VALUE =
+      new ExternalState("STATE_VALUE");
+  public final static ExternalState STATE_JS_FILE =
+      new ExternalState("STATE_JS_FILE");
+  public final static ExternalState STATE_CSS_FILE =
+      new ExternalState("STATE_CSS_FILE");
+
+  /**
+   * Returns {@code true} if the parser is currently processing Javascript.
+   * Such is the case if and only if, the parser is processing an attribute
+   * that takes Javascript, a Javascript script block or the parser
+   * is (re)set with {@link Mode#JS}.
+   *
+   * @return {@code true} if the parser is processing Javascript,
+   *         {@code false} otherwise
+   */
+  public boolean inJavascript();
+
+  /**
+   * Returns {@code true} if the parser is currently processing
+   * a Javascript litteral that is quoted. The caller will typically
+   * invoke this method after determining that the parser is processing
+   * Javascript. Knowing whether the element is quoted or not helps
+   * determine which escaping to apply to it when needed.
+   *
+   * @return {@code true} if and only if the parser is inside a quoted
+   *         Javascript literal
+   */
+  public boolean isJavascriptQuoted();
+
+
+  /**
+   * Returns {@code true} if and only if the parser is currently within
+   * an attribute, be it within the attribute name or the attribute value.
+   *
+   * @return {@code true} if and only if inside an attribute
+   */
+  public boolean inAttribute();
+
+  /**
+   * Returns {@code true} if and only if the parser is currently within
+   * a CSS context. A CSS context is one of the below:
+   * <ul>
+   * <li>Inside a STYLE tag.
+   * <li>Inside a STYLE attribute.
+   * <li>Inside a CSS file when the parser was reset in the CSS mode.
+   * </ul>
+   *
+   * @return {@code true} if and only if the parser is inside CSS
+   */
+  public boolean inCss();
+
+  /**
+   * Returns the type of the attribute that the parser is in
+   * or {@code ATTR_TYPE.NONE} if we are not parsing an attribute.
+   * The caller will typically invoke this method after determining
+   * that the parser is processing an attribute.
+   *
+   * <p>This is useful to determine which escaping to apply based
+   * on the type of value this attribute expects.
+   *
+   * @return type of the attribute
+   * @see HtmlParser.ATTR_TYPE
+   */
+  public ATTR_TYPE getAttributeType();
+
+  /**
+   * Returns {@code true} if and only if the parser is currently within
+   * an attribute value and that attribute value is quoted.
+   *
+   * @return {@code true} if and only if the attribute value is quoted
+   */
+  public boolean isAttributeQuoted();
+
+
+  /**
+   * Returns the name of the HTML tag if the parser is currently within one.
+   * Note that the name may be incomplete if the parser is currently still
+   * parsing the name. Returns an empty {@code String} if the parser is not
+   * in a tag as determined by {@code getCurrentExternalState}.
+   *
+   * @return the name of the HTML tag or an empty {@code String} if we are
+   *         not within an HTML tag
+   */
+  public String getTag();
+
+  /**
+   * Returns the name of the HTML attribute the parser is currently processing.
+   * If the parser is still parsing the name, then the returned name
+   * may be incomplete. Returns an empty {@code String} if the parser is not
+   * in an attribute as determined by {@code getCurrentExternalState}.
+   *
+   * @return the name of the HTML attribute or an empty {@code String}
+   *         if we are not within an HTML attribute
+   */
+  public String getAttribute();
+
+  /**
+   * Returns the value of an HTML attribute if the parser is currently
+   * within one. If the parser is currently parsing the value, the returned
+   * value may be incomplete. The caller will typically first determine
+   * that the parser is processing a value by calling
+   * {@code getCurrentExternalState}.
+   *
+   * @return the value, could be an empty {@code String} if the parser is not
+   *         in an HTML attribute value
+   */
+  public String getValue();
+
+  /**
+   * Returns the current position of the parser within the HTML attribute
+   * value, zero being the position of the first character in the value.
+   * The caller will typically first determine that the parser is
+   * processing a value by calling {@link #getState()}.
+   *
+   * @return the index or zero if the parser is not processing a value
+   */
+  public int getValueIndex();
+
+  /**
+   * Returns {@code true} if and only if the current position of the parser is
+   * at the start of a URL HTML attribute value. This is the case when the
+   * following three conditions are all met:
+   * <p>
+   * <ol>
+   * <li>The parser is in an HTML attribute value.
+   * <li>The HTML attribute expects a URL, as determined by
+   *     {@link #getAttributeType()} returning {@code .ATTR_TYPE#URI}.
+   * <li>The parser has not yet seen any characters from that URL.
+   * </ol>
+   * 
+   * <p> This method may be used by an Html Sanitizer or an Auto-Escape system
+   * to determine whether to validate the URL for well-formedness and validate
+   * the scheme of the URL (e.g. {@code HTTP}, {@code HTTPS}) is safe.
+   * In particular, it is recommended to use this method instead of
+   * checking that {@link #getValueIndex()} is {@code 0} to support attribute
+   * types where the URL does not start at index zero, such as the
+   * {@code content} attribute of the {@code meta} HTML tag.
+   *
+   * @return {@code true} if and only if the parser is at the start of the URL
+   */
+  public boolean isUrlStart();
+
+  /**
+   * Resets the state of the parser, allowing for reuse of the
+   * {@code HtmlParser} object.
+   *
+   * <p>See the {@link HtmlParser.Mode} enum for information on all
+   * the valid modes.
+   *
+   * @param mode is an enum representing the high-level state of the parser
+   */
+  public void resetMode(HtmlParser.Mode mode);
+
+  /**
+   * A specialized directive to tell the parser there is some content
+   * that will be inserted here but that it will not get to parse. Used
+   * by the template system that may not be able to give some content
+   * to the parser but wants it to know there typically will be content
+   * inserted at that point. This is a hint used in corner cases within
+   * parsing of HTML attribute names and values where content we do not
+   * get to see could affect our parsing and alter our current state.
+   *
+   * <p>Returns {@code false} if and only if the parser encountered
+   * a fatal error which prevents it from continuing further parsing.
+   *
+   * <p>Note: The return value is different from the C++ Parser which
+   * always returns {@code true} but in my opinion makes more sense.
+   *
+   * @throws ParseException if an unrecoverable error occurred during parsing
+   */
+  public void insertText() throws ParseException;
+
+  /**
+   * Returns the state the Javascript parser is in.
+   *
+   * <p>See {@link JavascriptParser} for more information on the valid
+   * external states. The caller will typically first determine that the
+   * parser is processing Javascript and then invoke this method to
+   * obtain more fine-grained state information.
+   *
+   * @return external state of the javascript parser
+   */
+  public ExternalState getJavascriptState();
+}
diff --git a/src/com/google/streamhtmlparser/HtmlParserFactory.java b/src/com/google/streamhtmlparser/HtmlParserFactory.java
new file mode 100644
index 0000000..518d8b4
--- /dev/null
+++ b/src/com/google/streamhtmlparser/HtmlParserFactory.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser;
+
+import com.google.streamhtmlparser.impl.HtmlParserImpl;
+
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * A factory class to obtain instances of an {@link HtmlParser}.
+ * Currently each instance is a new object given these are fairly
+ * light-weight.
+ *
+ * <p>In the unlikely case that this class fails to initialize properly
+ * (a developer error), an error is emitted to the error console and the logs
+ * and the specialized parser creation methods will throw
+ * an {@link AssertionError} on all invokations.
+ */
+public class HtmlParserFactory {
+
+  private static final Logger logger =
+      Logger.getLogger(HtmlParserFactory.class.getName());
+
+  /**
+   * To provide additional options when creating an {@code HtmlParser} using
+   * {@link HtmlParserFactory#createParserInAttribute(HtmlParser.ATTR_TYPE,
+   *        boolean, Set)} 
+   */
+  public enum AttributeOptions {
+
+    /**
+     * Indicates that the attribute value is Javascript-quoted. Only takes
+     * effect for Javascript-accepting attributes - as identified by
+     * {@link HtmlParser.ATTR_TYPE#JS} - and only when the attribute is also
+     * HTML quoted.
+     */
+    JS_QUOTED,
+
+    /**
+     * Indicates the attribute value is only a part of a URL as opposed to a
+     * full URL. In particular, the value is not at the start of a URL and
+     * hence does not necessitate validation of the URL scheme.
+     * Only valid for URI-accepting attributes - as identified by
+     * {@link HtmlParser.ATTR_TYPE#URI}.
+     */
+    URL_PARTIAL,
+  }
+
+  /**
+   * To provide additional options when creating an {@code HtmlParser} using
+   * {@link HtmlParserFactory#createParserInMode(HtmlParser.Mode, Set)}
+   */
+  public enum ModeOptions {
+
+    /**
+     * Indicates that the parser is inside a quoted {@code String}. Only
+     * valid in the {@link HtmlParser.Mode#JS} mode.
+     */
+    JS_QUOTED
+  }
+
+  private static final HtmlParser parserInDefaultAttr = createParser();
+  private static final HtmlParser parserInDefaultAttrQ = createParser();
+  private static final HtmlParser parserInUriAttrComplete = createParser();
+  private static final HtmlParser parserInUriAttrQComplete = createParser();
+  private static final HtmlParser parserInUriAttrPartial = createParser();
+  private static final HtmlParser parserInUriAttrQPartial = createParser();
+  private static final HtmlParser parserInJsAttr = createParser();
+  private static final HtmlParser parserInJsAttrQ = createParser();
+  private static final HtmlParser parserInQJsAttr = createParser();
+  private static final HtmlParser parserInStyleAttr = createParser();
+  private static final HtmlParser parserInStyleAttrQ = createParser();
+  private static final HtmlParser parserInJsQ = createParser();
+
+  /**
+   * Protects all the createParserXXX methods by throwing a run-time exception
+   * if this class failed to initialize properly.
+   */
+  private static boolean initSuccess = false;
+
+  static {
+    try {
+      initializeParsers();
+      initSuccess = true;
+    } catch (ParseException e) {
+      // Log a severe error and print it to stderr along with a stack trace.
+      String error = HtmlParserFactory.class.getName() +
+                     " Failed initialization: " + e.getMessage();
+      logger.severe(error);
+      System.err.println(error);
+      e.printStackTrace();
+    }
+  }
+
+  // Static class.
+  private HtmlParserFactory() {
+  }  // COV_NF_LINE
+
+  /**
+   * Returns an {@code HtmlParser} object ready to parse HTML input.
+   *
+   * @return an {@code HtmlParser} in the provided mode
+   */
+  public static HtmlParser createParser() {
+    return new HtmlParserImpl();
+  }
+
+  /**
+   * Returns an {@code HtmlParser} object initialized with the
+   * requested Mode. Provide non {@code null} options to provide
+   * a more precise initialization with the desired Mode.
+   *
+   * @param mode the mode to reset the parser with
+   * @param options additional options or {@code null} for none
+   * @return an {@code HtmlParser} in the provided mode
+   * @throws AssertionError when this class failed to initialize
+   */
+  public static HtmlParser createParserInMode(HtmlParser.Mode mode,
+                                              Set<ModeOptions> options) {
+    requireInitialized();
+
+    if (options != null && options.contains(ModeOptions.JS_QUOTED))
+      return createParser(parserInJsQ);
+
+    // With no options given, this method is just a convenience wrapper for
+    // the two calls below.
+    HtmlParser parser = new HtmlParserImpl();
+    parser.resetMode(mode);
+    return parser;
+  }
+
+  /**
+   * Returns an {@code HtmlParser} that is a copy of the one
+   * supplied. It holds the same internal state and hence can
+   * proceed with parsing in-lieu of the supplied parser.
+   *
+   * @param aHtmlParser a {@code HtmlParser} to copy from
+   * @return an {@code HtmlParser} that is a copy of the provided one
+   * @throws AssertionError when this class failed to initialize
+   */
+  public static HtmlParser createParser(HtmlParser aHtmlParser) {
+    requireInitialized();
+
+    // Should never get a ClassCastException since there is only one
+    // implementation of the HtmlParser interface.
+    return new HtmlParserImpl((HtmlParserImpl) aHtmlParser);
+  }
+
+  /**
+   * A very specialized {@code HtmlParser} accessor that returns a parser
+   * in a state where it expects to read the value of an attribute
+   * of an HTML tag. This is only useful when the parser has not seen a
+   * certain HTML tag and an attribute name and needs to continue parsing
+   * from a state as though it has.
+   *
+   * <p>For example, to create a parser in a state akin to that
+   * after the parser has parsed "&lt;a href=\"", invoke:
+   * <pre>
+   *   createParserInAttribute(HtmlParser.ATTR_TYPE.URI, true)}
+   * </pre>
+   * 
+   * <p>You must provide the proper value of quoting or the parser
+   * will go into an unexpected state.
+   * As a special-case, when called with the {@code HtmlParser.ATTR_TYPE}
+   * of {@code HtmlParser.ATTR_TYPE.NONE}, the parser is created in a state
+   * inside an HTML tag where it expects an attribute name not an attribute
+   * value. It becomes equivalent to a parser initialized in the
+   * {@code HTML_IN_TAG} mode.
+   *
+   * @param attrtype the attribute type which the parser should be in
+   * @param quoted whether the attribute value is enclosed in double quotes
+   * @param options additional options or {@code null} for none
+   * @return an {@code HtmlParser} initialized in the given attribute type
+   *         and quoting
+   * @throws AssertionError when this class failed to initialize
+   */
+  public static HtmlParser createParserInAttribute(
+      HtmlParser.ATTR_TYPE attrtype,
+      boolean quoted, Set<AttributeOptions> options) {
+    requireInitialized();
+
+    HtmlParser parser;
+    switch (attrtype) {
+      case REGULAR:
+        parser = createParser(
+            quoted ? parserInDefaultAttrQ : parserInDefaultAttr);
+        break;
+      case URI:
+        if (options != null && options.contains(AttributeOptions.URL_PARTIAL))
+          parser = createParser(
+              quoted ? parserInUriAttrQPartial : parserInUriAttrPartial);
+        else
+          parser = createParser(
+              quoted ? parserInUriAttrQComplete : parserInUriAttrComplete);
+        break;
+      case JS:
+        // Note: We currently do not support the case of the value being
+        // inside a Javascript quoted string that is in an unquoted HTML
+        // attribute, such as <a href=bla onmouseover=alert('[VALUE')>.
+        // It would be simple to add but currently we assume Javascript
+        // quoted attribute values are always HTML quoted.
+        if (quoted) {
+          if (options != null && options.contains(AttributeOptions.JS_QUOTED))
+            parser = createParser(parserInQJsAttr);
+          else
+            parser = createParser(parserInJsAttrQ);
+        } else {
+          parser = createParser(parserInJsAttr);
+        }
+        break;
+      case STYLE:
+        parser = createParser(
+            quoted ? parserInStyleAttrQ : parserInStyleAttr);
+        break;
+      case NONE:
+        parser = createParserInMode(HtmlParser.Mode.HTML_IN_TAG, null);
+        break;
+      default:
+        throw new IllegalArgumentException(
+            "Did not recognize ATTR_TYPE given: " + attrtype);
+    }
+    return parser;
+  }
+
+  /**
+   * Initializes a set of static parsers to be subsequently used
+   * by the various createParserXXX methods.
+   * The parsers are set to their proper states by making them parse
+   * an appropriate HTML input fragment. This approach is the most likely
+   * to ensure all their internal state is consistent.
+   *
+   * <p>In the very unexpected case of the parsing failing (developer error),
+   * this class will fail to initialize properly.
+   *
+   * <p>In addition:
+   * <ul>
+   * <li>The HTML tag is set to a fictitious name {@code xparsertag}.
+   * <li>The attribute name is chosen to match the required attribute type.
+   *     When several possibilities exist, one is chosen arbitrarily.
+   * <li>If quoting is required, a double quote is provided after the '='.
+   * </ul>
+   *
+   * @throws ParseException if parsing failed.
+   */
+  private static void initializeParsers() throws ParseException {
+    parserInDefaultAttr.parse("<xparsertag htmlparser=");
+    parserInDefaultAttrQ.parse("<xparsertag htmlparser=\"");
+
+    // Chosing the "src" attribute, one of several possible names here
+    parserInUriAttrComplete.parse("<xparsertag src=");
+    parserInUriAttrQComplete.parse("<xparsertag src=\"");
+
+    // To support a parser that is initialized within a URL parameter
+    // rather than at the beginning of a URL. We use a fake domain
+    // (example.com from RFC 2606 <http://www.rfc-editor.org/rfc/rfc2606.txt>)
+    // and a fake query parameter.
+    final String fakeUrlPrefix = "http://example.com/fakequeryparam=";
+    parserInUriAttrPartial.parse("<xparsertag src=" + fakeUrlPrefix);
+    parserInUriAttrQPartial.parse("<xparsertag src=\"" + fakeUrlPrefix);
+
+    // Using onmouse= which is a fictitious attribute name that the parser
+    // understands as being a valid javascript-enabled attribute. Chosing fake
+    // names may help during debugging.
+    parserInJsAttr.parse("<xparsertag onmouse=");
+    parserInJsAttrQ.parse("<xparsertag onmouse=\"");
+    // Single quote added as the Javascript is itself quoted.
+    parserInQJsAttr.parse("<xparsertag onmouse=\"'");
+
+    // A parser in the Javascript context within a (single) quoted string.
+    parserInJsQ.resetMode(HtmlParser.Mode.JS);
+    parserInJsQ.parse("var fakeparservar='");
+
+    // Chosing the "style" attribute as it is the only option
+    parserInStyleAttr.parse("<xparsertag style=");
+    parserInStyleAttrQ.parse("<xparsertag style=\"");
+  }
+
+  /**
+   * Throws an {@link AssertionError} if the class was not initialized
+   * correctly, otherwise simply returns. This is to protect against the
+   * possibility the needed parsers were not created successfully during
+   * static initialized, which can only happen due to an error during
+   * development of this library.
+   * 
+   * @throws AssertionError when this class failed to initialize
+   */
+  private static void requireInitialized() {
+    if (!initSuccess)
+      throw new AssertionError("HtmlParserFactory failed initialization.");
+  }
+}
diff --git a/src/com/google/streamhtmlparser/JavascriptParser.java b/src/com/google/streamhtmlparser/JavascriptParser.java
new file mode 100644
index 0000000..ecea7df
--- /dev/null
+++ b/src/com/google/streamhtmlparser/JavascriptParser.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser;
+
+/**
+ * Methods exposed for Javascript parsing of text to facilitate implementation
+ * of Automatic context-aware escaping. This interface does not add
+ * additional methods on top of {@code Parser} for the time being,
+ * it simply exposes the states in which the Javascript parser may be in.
+ *
+ * <p>Note: These are the exact states exposed in the original C++ Parser.
+ */
+public interface JavascriptParser extends Parser {
+
+  public static final ExternalState STATE_TEXT =
+      new ExternalState("STATE_TEXT");
+  public static final ExternalState STATE_Q =
+      new ExternalState("STATE_Q");
+  public static final ExternalState STATE_DQ =
+      new ExternalState("STATE_DQ");
+  public static final ExternalState STATE_REGEXP =
+      new ExternalState("STATE_REGEXP");
+  public static ExternalState STATE_COMMENT =
+      new ExternalState("STATE_COMMENT");
+}
diff --git a/src/com/google/streamhtmlparser/JavascriptParserFactory.java b/src/com/google/streamhtmlparser/JavascriptParserFactory.java
new file mode 100644
index 0000000..f3b6432
--- /dev/null
+++ b/src/com/google/streamhtmlparser/JavascriptParserFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser;
+
+import com.google.streamhtmlparser.impl.JavascriptParserImpl;
+
+/**
+ * A factory class to obtain instances of an <code>JavascriptParser</code>.
+ * Currently each instance is a new object given these are fairly
+ * light-weight.
+ *
+ * <p>Note that we do not typically expect a caller of this package to require
+ * an instance of a <code>JavascriptParser</code> since one is already
+ * embedded in the more general-purpose <code>HtmlParser</code>. We still
+ * make it possible to require one as it may be useful for more
+ * specialized needs.
+ *
+ */
+
+public class JavascriptParserFactory {
+
+  public static JavascriptParser getInstance() {
+    return new JavascriptParserImpl();
+  }
+
+  // Static class.
+  private JavascriptParserFactory() {
+  }  // COV_NF_LINE
+}
\ No newline at end of file
diff --git a/src/com/google/streamhtmlparser/ParseException.java b/src/com/google/streamhtmlparser/ParseException.java
new file mode 100644
index 0000000..04b99a2
--- /dev/null
+++ b/src/com/google/streamhtmlparser/ParseException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser;
+
+/**
+ * Checked exception thrown on an unrecoverable error during parsing.
+ * 
+ * @see Parser#parse(String)
+ */
+public class ParseException extends Exception {
+
+  /**
+   * Constructs an {@code ParseException} with no detail message.
+   */
+  public ParseException() {}
+
+  /**
+   * Constructs an {@code ParseException} with a detail message obtained
+   * from the supplied message and the parser's line and column numbers.
+   * @param parser the {@code Parser} that triggered the exception
+   * @param msg the error message
+   */
+  public ParseException(Parser parser, String msg) {
+    super(String.format("At line: %d (col: %d); %s",
+                        parser.getLineNumber(),
+                        parser.getColumnNumber(),
+                        msg));
+  }
+}
diff --git a/src/com/google/streamhtmlparser/Parser.java b/src/com/google/streamhtmlparser/Parser.java
new file mode 100644
index 0000000..24bd19f
--- /dev/null
+++ b/src/com/google/streamhtmlparser/Parser.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser;
+
+/**
+ * Defines essential functionality that every parser we implement
+ * will support. This is then extended for HTML and Javascript parsing.
+ *
+ * <p>The typical caller is a Template System and will usually ask
+ * us to parse either a character at a time or a fragment of a template
+ * at a time, stopping only when it needs to determine the state of the
+ * parser for escaping purposes.
+ *
+ * <p>We will later add methods to save and restore the full state
+ * of the parser to better support conditional processing.
+ */
+public interface Parser {
+
+  // Consider using a Constants class instead
+  public final static ExternalState STATE_ERROR =
+      new ExternalState("STATE_ERROR");
+
+  /**
+   * Tell the parser to process the provided {@code char}. Throws exception
+   * on an unrecoverable parsing error.
+   *
+   * @param input the character read
+   * @throws ParseException if an unrecoverable error occurred during parsing
+   */
+  void parse(char input) throws ParseException;
+
+  /**
+   * Tell the parser to process the provided {@code String}. Throws exception
+   * on an unrecoverable parsing error.
+   *
+   * @param input the {@code String} to parse
+   * @throws ParseException if an unrecoverable error occurred during parsing
+   */
+  void parse(String input) throws ParseException;
+
+  /**
+   * Reset the parser back to its initial default state.
+   */
+  void reset();
+
+  /**
+   * Returns the current state of the parser. May be {@link #STATE_ERROR}
+   * if the parser encountered an error. Such an error may be recoverable
+   * and the caller may want to continue parsing until {@link #parse(String)}
+   * returns {@code false}.
+   *
+   * @return current state of the parser
+   */
+  ExternalState getState();
+
+  /**
+   * Sets the current line number which is returned during error messages.
+   * @param lineNumber the line number to set in the parser
+   */
+  void setLineNumber(int lineNumber);
+
+  /**
+   * Returns the current line number.
+   */
+  int getLineNumber();
+
+  /**
+   * Sets the current column number which is returned during error messages.
+   * @param columnNumber the column number to set in the parser
+   */
+  void setColumnNumber(int columnNumber);
+
+  /**
+   * Returns the current column number.
+   */
+  int getColumnNumber();
+}
diff --git a/src/com/google/streamhtmlparser/impl/GenericParser.java b/src/com/google/streamhtmlparser/impl/GenericParser.java
new file mode 100644
index 0000000..aa0f5d3
--- /dev/null
+++ b/src/com/google/streamhtmlparser/impl/GenericParser.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser.impl;
+
+import com.google.common.base.Preconditions;
+import com.google.streamhtmlparser.ExternalState;
+import com.google.streamhtmlparser.Parser;
+import com.google.streamhtmlparser.ParseException;
+import com.google.streamhtmlparser.util.HtmlUtils;
+
+import java.util.Map;
+
+/**
+ * An implementation of the {@code Parser} interface that is common to both
+ * {@code HtmlParser} and {@code JavascriptParser}.
+ *
+ * <p>Provides methods for parsing input and ensuring that all in-state,
+ * entering-a-state and exiting-a-state callbacks are invoked as appropriate.
+ *
+ * <p>This class started as abstract but it was found better for testing to
+ * make it instantiatable so that the parsing logic can be tested with dummy
+ * state transitions.
+ */
+public class GenericParser implements Parser {
+
+  protected final ParserStateTable parserStateTable;
+  protected final Map<InternalState, ExternalState> intToExtStateTable;
+  protected final InternalState initialState;
+  protected InternalState currentState;
+  protected int lineNumber;
+  protected int columnNumber;
+
+  protected GenericParser(ParserStateTable parserStateTable,
+                          Map<InternalState, ExternalState> intToExtStateTable,
+                          InternalState initialState) {
+    this.parserStateTable = parserStateTable;
+    this.intToExtStateTable = intToExtStateTable;
+    this.initialState = initialState;
+    this.currentState = initialState;
+    this.lineNumber = 1;
+    this.columnNumber = 1;
+  }
+
+  /**
+   * Constructs a generic parser that is an exact copy of the
+   * one given. Note that here too, data structures that do not
+   * change are shallow-copied (parser state table and state mappings).
+   *
+   * @param aGenericParser the {@code GenericParser} to copy
+   */
+  protected GenericParser(GenericParser aGenericParser) {
+    parserStateTable = aGenericParser.parserStateTable;
+    intToExtStateTable = aGenericParser.intToExtStateTable;
+    initialState = aGenericParser.initialState;
+    currentState = aGenericParser.currentState;
+    lineNumber = aGenericParser.lineNumber;
+    columnNumber = aGenericParser.columnNumber;
+  }
+
+  /**
+   * Tell the parser to process the provided {@code String}. This is just a
+   * convenience method that wraps over {@link Parser#parse(char)}.
+   * @param input the {@code String} to parse
+   * @throws ParseException if an unrecoverable error occurred during parsing
+   */
+  @Override
+  public void parse(String input) throws ParseException {
+    for (int i = 0; i < input.length(); i++)
+      parse(input.charAt(i));
+  }
+
+  /**
+   * Main loop for parsing of input.
+   *
+   * <p>Absent any callbacks defined, this function simply determines the
+   * next state to switch to based on the <code>ParserStateTable</code> which is
+   * derived from a state-machine configuration file in the original C++ parser.
+   *
+   * <p>However some states have specific callbacks defined which when
+   * receiving specific characters may decide to overwrite the next state to
+   * go to. Hence the next state is a function both of the main state table
+   * in {@code ParserStateTable} as well as specific run-time information
+   * from the callback functions.
+   *
+   * <p>Also note that the callbacks are called in a proper sequence,
+   * first the exit-state one then the enter-state one and finally the
+   * in-state one. Changing the order may result in a functional change.
+   *
+   * @param input the input character to parse (process)
+   * @throws ParseException if an unrecoverable error occurred during parsing
+   */
+  @Override
+  public void parse(char input) throws ParseException {
+    InternalState nextState =
+        parserStateTable.getNextState(currentState, input);
+
+    if (nextState == InternalState.INTERNAL_ERROR_STATE) {
+        String errorMsg =
+            String.format("Unexpected character '%s' in int_state '%s' " +
+                          "(ext_state '%s')",
+                          HtmlUtils.encodeCharForAscii(input),
+                          currentState.getName(), getState().getName());
+      currentState = InternalState.INTERNAL_ERROR_STATE;
+      throw new ParseException(this, errorMsg);
+    }
+
+    if (currentState != nextState) {
+      nextState = handleExitState(currentState, nextState, input);
+    }
+    if (currentState != nextState) {
+      nextState = handleEnterState(nextState, nextState, input);
+    }
+    nextState = handleInState(nextState, input);
+    currentState = nextState;
+    record(input);
+
+    columnNumber++;
+    if (input == '\n') {
+      lineNumber++;
+      columnNumber = 1;
+    }
+  }
+
+  /**
+   * Return the current state of the parser.
+   */
+  @Override
+  public ExternalState getState() {
+    if (!intToExtStateTable.containsKey(currentState)) {
+      throw new NullPointerException("Did not find external state mapping " +
+                                     "For internal state: " + currentState);
+    }
+    return intToExtStateTable.get(currentState);
+  }
+
+  /**
+   * Reset the parser back to its initial default state.
+   */
+  @Override
+  public void reset() {
+    currentState = initialState;
+    lineNumber = 1;
+    columnNumber = 1;
+  }
+
+  /**
+   * Sets the current line number which is returned during error messages.
+   */
+  @Override
+  public void setLineNumber(int lineNumber) {
+    this.lineNumber = lineNumber;
+  }
+
+  /**
+   * Returns the current line number.
+   */
+  @Override
+  public int getLineNumber() {
+    return lineNumber;
+  }
+
+  /**
+   * Sets the current column number which is returned during error messages.
+   */
+  @Override
+  public void setColumnNumber(int columnNumber) {
+    this.columnNumber = columnNumber;
+  }
+
+  /**
+   * Returns the current column number.
+   */
+  @Override
+  public int getColumnNumber() {
+    return columnNumber;
+  }
+
+  InternalState getCurrentInternalState() {
+    return currentState;
+  }
+
+  protected void setNextState(InternalState nextState) throws ParseException {
+    Preconditions.checkNotNull(nextState);   // Developer error if it triggers.
+
+    /* We are not actually parsing hence providing
+     * a null char to the event handlers.
+     */
+    // TODO: Complicated logic to follow in C++ but clean it up.
+    final char nullChar = '\0';
+
+    if (currentState != nextState) {
+      nextState = handleExitState(currentState, nextState, nullChar);
+    }
+    if (currentState != nextState) {
+      handleEnterState(nextState, nextState, nullChar);
+    }
+    currentState = nextState;
+  }
+
+  /**
+   * Invoked when the parser enters a new state.
+   *
+   * @param currentState the current state of the parser
+   * @param expectedNextState the next state according to the
+   *        state table definition
+   * @param input the last character parsed
+   * @return the state to change to, could be the same as the
+   *         {@code expectedNextState} provided
+   * @throws ParseException if an unrecoverable error occurred during parsing
+   */
+  protected InternalState handleEnterState(InternalState currentState,
+                                           InternalState expectedNextState,
+                                           char input) throws ParseException {
+    return expectedNextState;
+  }
+
+  /**
+   * Invoked when the parser exits a state.
+   *
+   * @param currentState the current state of the parser
+   * @param expectedNextState the next state according to the
+   *        state table definition
+   * @param input the last character parsed
+   * @return the state to change to, could be the same as the
+   *         {@code expectedNextState} provided
+   * @throws ParseException if an unrecoverable error occurred during parsing
+   */
+  protected InternalState handleExitState(InternalState currentState,
+                                          InternalState expectedNextState,
+                                          char input) throws ParseException {
+    return expectedNextState;
+  }
+
+  /**
+   * Invoked for each character read when no state change occured.
+   *
+   * @param currentState the current state of the parser
+   * @param input the last character parsed
+   * @return the state to change to, could be the same as the
+   *         {@code expectedNextState} provided
+   * @throws ParseException if an unrecoverable error occurred during parsing
+   */
+  protected InternalState handleInState(InternalState currentState,
+                                        char input) throws ParseException {
+    return currentState;
+  }
+
+  /**
+   * Perform some processing on the given character. Derived classes
+   * may override this method in order to perform additional logic
+   * on every processed character beyond the logic defined in
+   * state transitions.
+   *
+   * @param input the input character to operate on
+   */
+  protected void record(char input) { }
+}
diff --git a/src/com/google/streamhtmlparser/impl/HtmlParserImpl.java b/src/com/google/streamhtmlparser/impl/HtmlParserImpl.java
new file mode 100644
index 0000000..24508ca
--- /dev/null
+++ b/src/com/google/streamhtmlparser/impl/HtmlParserImpl.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser.impl;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import com.google.streamhtmlparser.ExternalState;
+import com.google.streamhtmlparser.HtmlParser;
+import com.google.streamhtmlparser.ParseException;
+import com.google.streamhtmlparser.util.CharacterRecorder;
+import com.google.streamhtmlparser.util.EntityResolver;
+import com.google.streamhtmlparser.util.HtmlUtils;
+
+import java.util.Map;
+
+/**
+ * A custom specialized parser - ported from the main C++ version - used to
+ * implement context-aware escaping of run-time data in web-application
+ * templates.
+ *
+ * <p>This is the main class in the package. It implements the
+ * {@code HtmlParser} interface.
+ *
+ * <p>This class is not thread-safe, in particular you cannot invoke any
+ * state changing operations (such as {@code parse} from multiple threads
+ * on the same object.
+ *
+ * <p>If you are looking at this class, chances are very high you are
+ * implementing Auto-Escaping for a new template system. Please see the
+ * landing page including a design document at
+ * <a href="http://go/autoescape">Auto-Escape Landing Page</a>.
+ */
+public class HtmlParserImpl extends GenericParser implements HtmlParser {
+
+  /*
+   * Internal representation of the parser state, which is at a
+   * finer-granularity than the external state as given to callers.
+   * The relationship between <code>InternalState</code> and
+   * <code>ExternalState</code> is a many-to-one relationship.
+   */
+  private static final InternalState TEXT;
+  private static final InternalState TAG_START;
+  private static final InternalState TAG_NAME;
+  private static final InternalState DECL_START;
+  private static final InternalState DECL_BODY;
+  private static final InternalState COM_OPEN;
+  private static final InternalState COM_BODY;
+  private static final InternalState COM_DASH;
+  private static final InternalState COM_DASH_DASH;
+  private static final InternalState PI;
+  private static final InternalState PI_MAY_END;
+  private static final InternalState TAG_SPACE;
+  private static final InternalState TAG_CLOSE;
+  private static final InternalState ATTR;
+  private static final InternalState ATTR_SPACE;
+  private static final InternalState VALUE;
+  private static final InternalState VALUE_TEXT;
+  private static final InternalState VALUE_Q_START;
+  private static final InternalState VALUE_Q;
+  private static final InternalState VALUE_DQ_START;
+  private static final InternalState VALUE_DQ;
+  private static final InternalState CDATA_COM_START;
+  private static final InternalState CDATA_COM_START_DASH;
+  private static final InternalState CDATA_COM_BODY;
+  private static final InternalState CDATA_COM_DASH;
+  private static final InternalState CDATA_COM_DASH_DASH;
+  private static final InternalState CDATA_TEXT;
+  private static final InternalState CDATA_LT;
+  private static final InternalState CDATA_MAY_CLOSE;
+  private static final InternalState JS_FILE;
+  private static final InternalState CSS_FILE;
+
+  static {
+    TEXT = InternalState.getInstanceHtml("TEXT");
+    TAG_START = InternalState.getInstanceHtml("TAG_START");
+    TAG_NAME = InternalState.getInstanceHtml("TAG_NAME");
+    DECL_START = InternalState.getInstanceHtml("DECL_START");
+    DECL_BODY = InternalState.getInstanceHtml("DECL_BODY");
+    COM_OPEN = InternalState.getInstanceHtml("COM_OPEN");
+    COM_BODY = InternalState.getInstanceHtml("COM_BODY");
+    COM_DASH = InternalState.getInstanceHtml("COM_DASH");
+    COM_DASH_DASH = InternalState.getInstanceHtml("COM_DASH_DASH");
+    PI =InternalState.getInstanceHtml("PI");
+    PI_MAY_END = InternalState.getInstanceHtml("PI_MAY_END");
+    TAG_SPACE = InternalState.getInstanceHtml("TAG_SPACE");
+    TAG_CLOSE = InternalState.getInstanceHtml("TAG_CLOSE");
+    ATTR = InternalState.getInstanceHtml("ATTR");
+    ATTR_SPACE = InternalState.getInstanceHtml("ATTR_SPACE");
+    VALUE = InternalState.getInstanceHtml("VALUE");
+    VALUE_TEXT = InternalState.getInstanceHtml("VALUE_TEXT");
+    VALUE_Q_START = InternalState.getInstanceHtml("VALUE_Q_START");
+    VALUE_Q = InternalState.getInstanceHtml("VALUE_Q");
+    VALUE_DQ_START = InternalState.getInstanceHtml("VALUE_DQ_START");
+    VALUE_DQ = InternalState.getInstanceHtml("VALUE_DQ");
+    CDATA_COM_START = InternalState.getInstanceHtml("CDATA_COM_START");
+    CDATA_COM_START_DASH =
+        InternalState.getInstanceHtml("CDATA_COM_START_DASH");
+    CDATA_COM_BODY = InternalState.getInstanceHtml("CDATA_COM_BODY");
+    CDATA_COM_DASH = InternalState.getInstanceHtml("CDATA_COM_DASH");
+    CDATA_COM_DASH_DASH = InternalState.getInstanceHtml("CDATA_COM_DASH_DASH");
+    CDATA_TEXT = InternalState.getInstanceHtml("CDATA_TEXT");
+    CDATA_LT = InternalState.getInstanceHtml("CDATA_LT");
+    CDATA_MAY_CLOSE = InternalState.getInstanceHtml("CDATA_MAY_CLOSE");
+    JS_FILE = InternalState.getInstanceHtml("JS_FILE");
+    CSS_FILE = InternalState.getInstanceHtml("CSS_FILE");
+  }
+
+  private static final Map<InternalState, ExternalState> STATE_MAPPING =
+      Maps.newHashMap();
+  static {
+    initializeStateMapping();
+  }
+
+  private static final ParserStateTable STATE_TABLE = new ParserStateTable();
+  static {
+    initializeParserStateTable();
+  }
+
+  private final CharacterRecorder tag;
+  private final CharacterRecorder attr;
+  private final CharacterRecorder value;
+  private final CharacterRecorder cdataCloseTag;
+  private final EntityResolver entityResolver;
+  private final JavascriptParserImpl jsParser;
+  private boolean insideJavascript;
+  private int valueIndex;
+  // True iff InsertText() was called at the start of a URL attribute value.
+  private boolean textInsideUrlValue;
+
+  /**
+   * Creates an {@code HtmlParserImpl} object.
+   *
+   * <p>Both for performance reasons and to leverage code a state-flow machine
+   * that is automatically generated from Python for multiple target
+   * languages, this object uses a static {@code ParserStateTable} that
+   * is read-only and obtained from the generated code in {@code HtmlParserFsm}.
+   * That code also maintains the mapping from internal states
+   * ({@code InternalState}) to external states ({@code ExternalState}).
+   */
+  public HtmlParserImpl() {
+    super(STATE_TABLE, STATE_MAPPING, TEXT);
+    tag = new CharacterRecorder();
+    attr = new CharacterRecorder();
+    value = new CharacterRecorder();
+    cdataCloseTag = new CharacterRecorder();
+    entityResolver = new EntityResolver();
+    jsParser = new JavascriptParserImpl();
+    insideJavascript = false;
+    valueIndex = 0;
+    textInsideUrlValue = false;
+  }
+
+  /**
+   * Creates an {@code HtmlParserImpl} that is a copy of the one provided.
+   * 
+   * @param aHtmlParserImpl the {@code HtmlParserImpl} object to copy
+   */
+  public HtmlParserImpl(HtmlParserImpl aHtmlParserImpl) {
+    super(aHtmlParserImpl);
+    tag = new CharacterRecorder(aHtmlParserImpl.tag);
+    attr = new CharacterRecorder(aHtmlParserImpl.attr);
+    value = new CharacterRecorder(aHtmlParserImpl.value);
+    cdataCloseTag = new CharacterRecorder(aHtmlParserImpl.cdataCloseTag);
+    entityResolver = new EntityResolver(aHtmlParserImpl.entityResolver);
+    jsParser = new JavascriptParserImpl(aHtmlParserImpl.jsParser);
+    insideJavascript = aHtmlParserImpl.insideJavascript;
+    valueIndex = aHtmlParserImpl.valueIndex;
+    textInsideUrlValue = aHtmlParserImpl.textInsideUrlValue;
+  }
+
+  @Override
+  public boolean inJavascript() {
+    return (insideJavascript
+            && ( (getState() == STATE_VALUE)
+                 || (currentState == CDATA_TEXT)
+                 || (currentState == CDATA_COM_START)
+                 || (currentState == CDATA_COM_START_DASH)
+                 || (currentState == CDATA_COM_BODY)
+                 || (currentState == CDATA_COM_DASH)
+                 || (currentState == CDATA_COM_DASH_DASH)
+                 || (currentState == CDATA_LT)
+                 || (currentState == CDATA_MAY_CLOSE)
+                 || (currentState == JS_FILE) ));
+  }
+
+  @Override
+  public boolean isJavascriptQuoted() {
+    if (inJavascript()) {
+      ExternalState jsParserState = jsParser.getState();
+      return (jsParserState == JavascriptParserImpl.STATE_Q
+              || jsParserState == JavascriptParserImpl.STATE_DQ);
+    }
+    return false;
+  }
+
+  @Override
+  public boolean inAttribute() {
+    ExternalState extState = getState();
+    return (extState != null && (extState == STATE_ATTR
+                                 || extState == STATE_VALUE));
+  }
+
+  /**
+   * Returns {@code true} if and only if the parser is currently within
+   * a CSS context. A CSS context is one of the below:
+   * <ul>
+   * <li>Inside a STYLE tag.
+   * <li>Inside a STYLE attribute.
+   * <li>Inside a CSS file when the parser was reset in the CSS mode.
+   * </ul>
+   *
+   * @return {@code true} if and only if the parser is inside CSS
+   */
+  @Override
+  public boolean inCss() {
+    return (currentState == CSS_FILE
+            || (getState() == STATE_VALUE
+                && (getAttributeType() == ATTR_TYPE.STYLE))
+            || ("style".equals(getTag())));
+  }
+
+  @Override
+  public ATTR_TYPE getAttributeType() {
+    String attribute = getAttribute();
+    if (!inAttribute()) {
+      return ATTR_TYPE.NONE;
+    }
+    if (HtmlUtils.isAttributeJavascript(attribute)) {
+      return ATTR_TYPE.JS;
+    }
+    if (HtmlUtils.isAttributeUri(attribute)) {
+      return ATTR_TYPE.URI;
+    }
+    if (HtmlUtils.isAttributeStyle(attribute)) {
+      return ATTR_TYPE.STYLE;
+    }
+
+    // Special logic to handle the "content" attribute of the "meta" tag.
+    if ("meta".equals(getTag()) && "content".equals(getAttribute())) {
+      HtmlUtils.META_REDIRECT_TYPE redirectType =
+          HtmlUtils.parseContentAttributeForUrl(getValue());
+      if (redirectType == HtmlUtils.META_REDIRECT_TYPE.URL_START ||
+          redirectType == HtmlUtils.META_REDIRECT_TYPE.URL)
+        return ATTR_TYPE.URI;
+    }
+    
+    return ATTR_TYPE.REGULAR;
+  }
+
+  @Override
+  public ExternalState getJavascriptState() {
+    return jsParser.getState();
+  }
+
+  @Override
+  public boolean isAttributeQuoted() {
+    return (currentState == VALUE_Q_START
+            || currentState == VALUE_Q
+            || currentState == VALUE_DQ_START
+            || currentState == VALUE_DQ);
+  }
+
+  @Override
+  public String getTag() {
+    return tag.getContent().toLowerCase();
+  }
+
+  @Override
+  public String getAttribute() {
+    return inAttribute() ? attr.getContent().toLowerCase() : "";
+  }
+
+  @Override
+  public String getValue() {
+    return (getState() == STATE_VALUE) ? value.getContent() : "";
+  }
+
+  @Override
+  public int getValueIndex() {
+    if (getState() != STATE_VALUE) {
+      return 0;
+    }
+    return valueIndex;
+  }
+
+  @Override
+  public boolean isUrlStart() {
+    // False when not inside an HTML attribute value
+    if (getState() != STATE_VALUE) {
+      return false;
+    }
+
+    //  Or when the HTML attribute is not of URI type.
+    if (getAttributeType() != ATTR_TYPE.URI) {
+      return false;
+    }
+
+    // Or when we received an InsertText() directive at the start of a URL.
+    if (textInsideUrlValue) {
+      return false;
+    }
+
+    if ("meta".equals(getTag())) {
+      // At this point, we know we are in the "content" attribute
+      // or we would not have the URI attribute type.
+      return (HtmlUtils.parseContentAttributeForUrl(getValue()) ==
+              HtmlUtils.META_REDIRECT_TYPE.URL_START);
+    }
+
+    // For all other URI attributes, check if we are at index 0.
+    return (getValueIndex() == 0);
+}
+
+  /**
+   * {@inheritDoc}
+   * 
+   * Resets the state of the parser to a state consistent with the
+   * {@code Mode} provided. This will reset finer-grained state
+   * information back to a default value, hence use only when
+   * you want to parse text from a very clean slate.
+   *
+   * <p>See the {@link HtmlParser.Mode} enum for information on all
+   * the valid modes.
+   *
+   * @param mode is an enum representing the high-level state of the parser
+   */
+  @Override
+  public void resetMode(Mode mode) {
+    insideJavascript = false;
+    tag.reset();
+    attr.reset();
+    value.reset();
+    cdataCloseTag.reset();
+    valueIndex = 0;
+    textInsideUrlValue = false;
+    jsParser.reset();
+
+    switch (mode) {
+      case HTML:
+        currentState = TEXT;
+        break;
+      case JS:
+        currentState = JS_FILE;
+        insideJavascript = true;
+        break;
+      case CSS:
+        currentState = CSS_FILE;
+        break;
+      case HTML_IN_TAG:
+        currentState = TAG_SPACE;
+        break;
+      default:
+        throw new IllegalArgumentException("Did not recognize Mode: " +
+                                           mode.toString());
+    }
+  }
+
+  /**
+   * Resets the state of the parser to the initial state of parsing HTML.
+   */
+  public void reset() {
+    super.reset();
+    resetMode(Mode.HTML);
+  }
+
+  /**
+   * A specialized directive to tell the parser there is some content
+   * that will be inserted here but that it will not get to parse. Used
+   * by the template system that may not be able to give some content
+   * to the parser but wants it to know there typically will be content
+   * inserted at that point.  This is a hint used in corner cases within
+   * parsing of HTML attribute names and values where content we do not
+   * get to see could affect our parsing and alter our current state.
+   *
+   * <p>The two cases where {@code #insertText()} affects our parsing are:
+   * <ul>
+   * <li>We are at the start of the value of a URL-accepting HTML attribute. In
+   * that case, we change internal state to no longer be considered at the
+   * start of the URL. This may affect what escaping template systems may want
+   * to perform on the HTML attribute value. We avoid injecting fake data and
+   * hence not modify the current index of the value as determined by
+   * {@link #getValueIndex()}</li>
+   * <li>We just transitioned from an attribute name to an attribute value
+   * (by parsing the separating {@code '='} character). In that case, we
+   * change internal state to be now inside a non-quoted HTML attribute
+   * value.</li>
+   * </ul>
+   * 
+   * @throws ParseException if an unrecoverable error occurred during parsing
+   */
+  @Override
+  public void insertText() throws ParseException {
+    // Case: Inside URL attribute value.
+    if (getState() == STATE_VALUE
+        && getAttributeType() == ATTR_TYPE.URI
+        && isUrlStart()) {
+      textInsideUrlValue = true;
+    }
+    // Case: Before parsing any attribute value.
+    if (currentState == VALUE) {
+      setNextState(VALUE_TEXT);
+    }
+  }
+
+  @Override
+  protected InternalState handleEnterState(InternalState currentState,
+                                           InternalState expectedNextState,
+                                           char input) {
+    InternalState nextState = expectedNextState;
+    if (currentState == TAG_NAME) {
+      enterTagName();
+    } else if (currentState == ATTR) {
+      enterAttribute();
+    } else if (currentState == TAG_CLOSE) {
+      nextState = tagClose(currentState);
+    } else if (currentState == CDATA_MAY_CLOSE) {
+      enterStateCdataMayClose();
+    } else if (currentState == VALUE) {
+      enterValue();
+    } else
+    if (currentState == VALUE_TEXT || currentState == VALUE_Q
+        || currentState == VALUE_DQ) {
+      enterValueContent();
+    }
+    return nextState;
+  }
+
+  @Override
+  protected InternalState handleExitState(InternalState currentState,
+                                          InternalState expectedNextState,
+                                          char input) {
+    InternalState nextState = expectedNextState;
+    if (currentState == TAG_NAME) {
+      exitTagName();
+    } else if (currentState == ATTR) {
+      exitAttribute();
+    } else if (currentState == CDATA_MAY_CLOSE) {
+      nextState = exitStateCdataMayClose(nextState, input);
+    } else
+    if ((currentState == VALUE_TEXT) || (currentState == VALUE_Q)
+        || (currentState == VALUE_DQ)) {
+      exitValueContent();
+    }
+    return nextState;
+  }
+
+  @Override
+  protected InternalState handleInState(InternalState currentState,
+                                        char input) throws ParseException {
+    if ((currentState == CDATA_TEXT)
+        || (currentState == CDATA_COM_START)
+        || (currentState == CDATA_COM_START_DASH)
+        || (currentState == CDATA_COM_BODY)
+        || (currentState == CDATA_COM_DASH)
+        || (currentState == CDATA_COM_DASH_DASH)
+        || (currentState == CDATA_LT)
+        || (currentState == CDATA_MAY_CLOSE)
+        || (currentState == JS_FILE)) {
+      inStateCdata(input);
+    } else if ((currentState == VALUE_TEXT)
+               || (currentState == VALUE_Q)
+               || (currentState == VALUE_DQ)) {
+      inStateValue(input);
+    }
+    return currentState;
+  }
+
+  /**
+   * Invokes recording on all CharacterRecorder objects. Currently we do
+   * not check that one and only one of them is recording. I did a fair
+   * bit of testing on the C++ parser and was not convinced there is
+   * such a guarantee.
+   */
+  @Override
+  protected void record(char input) {
+    attr.maybeRecord(input);
+    tag.maybeRecord(input);
+    value.maybeRecord(input);
+    cdataCloseTag.maybeRecord(input);
+  }
+
+  /**
+   * Starts recording the name of the HTML tag. Called when the parser
+   * enters a new tag.
+   */
+  private void enterTagName() {
+    tag.startRecording();
+  }
+
+  private void exitTagName() {
+    tag.stopRecording();
+    String tagString = tag.getContent();
+    if (!tagString.isEmpty() && tagString.charAt(0) == '/') {
+      tag.reset();
+    }
+  }
+
+  /**
+   * Starts recording the name of the HTML attribute. Called when the parser
+   * enters a new HTML attribute.
+   */
+  private void enterAttribute() {
+    attr.startRecording();
+  }
+
+  private void exitAttribute() {
+    attr.stopRecording();
+  }
+
+  /**
+   * Tracks the index within the HTML attribute value and initializes
+   * the javascript parser for attributes that take javascript.
+   *
+   * Called when the parser enters a new HTML attribute value.
+   */
+  private void enterValue() {
+    valueIndex = 0;
+    textInsideUrlValue = false;
+    if (HtmlUtils.isAttributeJavascript(getAttribute())) {
+      entityResolver.reset();
+      jsParser.reset();
+      insideJavascript = true;
+    } else {
+      insideJavascript = false;
+    }
+  }
+
+  /**
+   * Starts recordning the contents of the attribute value.
+   *
+   * Called when entering an attribute value.
+   */
+  private void enterValueContent() {
+    value.startRecording();
+  }
+
+  /**
+   * Stops the recording of the attribute value and exits javascript
+   * (in case we were inside it).
+   */
+  private void exitValueContent() {
+    value.stopRecording();
+    insideJavascript = false;
+  }
+
+  /**
+   * Processes javascript after performing entity resolution and updates
+   * the position within the attribute value.
+   * If the status of the entity resolution is <code>IN_PROGRESS</code>,
+   * we don't invoke the javascript parser.
+   *
+   * <p>Called for every character inside an attribute value.
+   *
+   * @param input character read
+   * @throws ParseException if an unrecoverable error occurred during parsing
+   */
+  private void inStateValue(char input) throws ParseException {
+    valueIndex++;
+    if (insideJavascript) {
+      EntityResolver.Status status = entityResolver.processChar(input);
+      if (status == EntityResolver.Status.COMPLETED) {
+        jsParser.parse(entityResolver.getEntity());
+        entityResolver.reset();
+      } else if (status == EntityResolver.Status.NOT_STARTED) {
+        jsParser.parse(input);
+      }
+    }
+  }
+
+  /**
+   * Handles the tag it finished reading.
+   *
+   * <p>For a script tag, it initializes the javascript parser. For all
+   * tags that are recognized to have CDATA values
+   * (including the script tag), it switches the CDATA state to handle them
+   * properly. For code simplification, CDATA and RCDATA sections are
+   * treated the same.
+   *
+   * <p>Called when the parser leaves a tag definition.
+   *
+   * @param state current state
+   * @return state next state, could be the same as current state
+   */
+  private InternalState tagClose(InternalState state) {
+    InternalState nextState = state;
+    String tagName = getTag();
+    if ("script".equals(tagName)) {
+      nextState = CDATA_TEXT;
+      jsParser.reset();
+      insideJavascript = true;
+    } else if ("style".equals(tagName)
+                 || "title".equals(tagName)
+                 || "textarea".equals(tagName)) {
+      nextState = CDATA_TEXT;
+      insideJavascript = false;
+    }
+    return nextState;
+  }
+
+  /**
+   * Feeds the character to the javascript parser for processing.
+   *
+   * <p>Called inside CDATA blocks to parse javascript.
+   *
+   * @param input character read
+   * @throws ParseException if an unrecoverable error occurred during parsing
+   */
+  private void inStateCdata(char input) throws ParseException {
+    if (insideJavascript) {
+      jsParser.parse(input);
+    }
+  }
+
+  /**
+   * Starts recording. This is so we find the closing tag name in order to
+   * know if the tag is going to be closed or not.
+   *
+   * <p>Called when encountering a '<' character in a CDATA section.
+   */
+  private void enterStateCdataMayClose() {
+    cdataCloseTag.startRecording();
+  }
+
+  /**
+   * Determines whether to close the tag element, It closes it if it finds
+   * the corresponding end tag. Called when reading what could be a
+   * closing CDATA tag.
+   *
+   * @param input the character read
+   * @param expectedNextState the expected state to go to next
+   *        unless we want to change it here
+   * @return the next state to go to
+   */
+  private InternalState exitStateCdataMayClose(
+      InternalState expectedNextState,
+      char input) {
+    InternalState nextState = expectedNextState;
+    cdataCloseTag.stopRecording();
+    String cdataCloseTagString = cdataCloseTag.getContent();
+    Preconditions.checkState(!cdataCloseTagString.isEmpty()
+        && cdataCloseTagString.charAt(0) == '/');  // Developer error.
+
+    if (cdataCloseTagString.substring(1).equalsIgnoreCase(getTag())
+        && (input == '>' || HtmlUtils.isHtmlSpace(input))) {
+      tag.clear();
+      insideJavascript = false;
+    } else {
+      nextState = CDATA_TEXT;
+    }
+    return nextState;
+  }
+
+
+  // ======================================================= //
+  // SECTION BELOW WILL ALL BE AUTO-GENERATED IN FUTURE.     //
+  // ======================================================= //
+
+  private static void registerMapping(InternalState internalState,
+                                      ExternalState externalState) {
+    STATE_MAPPING.put(internalState, externalState);
+  }
+
+  private static void initializeStateMapping() {
+    // Each parser implementation must map the error state appropriately.
+    registerMapping(InternalState.INTERNAL_ERROR_STATE, HtmlParser.STATE_ERROR);
+
+    registerMapping(TEXT, HtmlParser.STATE_TEXT);
+    registerMapping(TAG_START, HtmlParser.STATE_TAG);
+    registerMapping(TAG_NAME, HtmlParser.STATE_TAG);
+    registerMapping(DECL_START, HtmlParser.STATE_TEXT);
+    registerMapping(DECL_BODY, HtmlParser.STATE_TEXT);
+    registerMapping(COM_OPEN, HtmlParser.STATE_TEXT);
+    registerMapping(COM_BODY, HtmlParser.STATE_COMMENT);
+    registerMapping(COM_DASH, HtmlParser.STATE_COMMENT);
+    registerMapping(COM_DASH_DASH, HtmlParser.STATE_COMMENT);
+    registerMapping(PI, HtmlParser.STATE_TEXT);
+    registerMapping(PI_MAY_END, HtmlParser.STATE_TEXT);
+    registerMapping(TAG_SPACE, HtmlParser.STATE_TAG);
+    registerMapping(TAG_CLOSE, HtmlParser.STATE_TEXT);
+    registerMapping(ATTR, HtmlParser.STATE_ATTR);
+    registerMapping(ATTR_SPACE, HtmlParser.STATE_ATTR);
+    registerMapping(VALUE, HtmlParser.STATE_VALUE);
+    registerMapping(VALUE_TEXT, HtmlParser.STATE_VALUE);
+    registerMapping(VALUE_Q_START, HtmlParser.STATE_VALUE);
+    registerMapping(VALUE_Q, HtmlParser.STATE_VALUE);
+    registerMapping(VALUE_DQ_START, HtmlParser.STATE_VALUE);
+    registerMapping(VALUE_DQ, HtmlParser.STATE_VALUE);
+    registerMapping(CDATA_COM_START, HtmlParser.STATE_TEXT);
+    registerMapping(CDATA_COM_START_DASH, HtmlParser.STATE_TEXT);
+    registerMapping(CDATA_COM_BODY, HtmlParser.STATE_TEXT);
+    registerMapping(CDATA_COM_DASH, HtmlParser.STATE_TEXT);
+    registerMapping(CDATA_COM_DASH_DASH, HtmlParser.STATE_TEXT);
+    registerMapping(CDATA_TEXT, HtmlParser.STATE_TEXT);
+    registerMapping(CDATA_LT, HtmlParser.STATE_TEXT);
+    registerMapping(CDATA_MAY_CLOSE, HtmlParser.STATE_TEXT);
+    registerMapping(JS_FILE, HtmlParser.STATE_JS_FILE);
+    registerMapping(CSS_FILE, HtmlParser.STATE_CSS_FILE);
+  }
+
+  private static void registerTransition(String expression,
+                                         InternalState source,
+                                         InternalState to) {
+    // It seems to silly to go through a StateTableTransition here
+    // but it adds extra data checking.
+    StateTableTransition stt = new StateTableTransition(expression,
+                                                        source, to);
+    STATE_TABLE.setExpression(stt.getExpression(), stt.getFrom(),
+                              stt.getTo());
+  }
+
+  // NOTE: The "[:default:]" transition should be registered before any
+  //   other transitions for a given state or it will over-write them.
+  private static void initializeParserStateTable() {
+    registerTransition("[:default:]", CSS_FILE, CSS_FILE);
+    registerTransition("[:default:]", JS_FILE, JS_FILE);
+    registerTransition("[:default:]", CDATA_MAY_CLOSE, CDATA_TEXT);
+    registerTransition(" \t\n\r", CDATA_MAY_CLOSE, TAG_SPACE);
+    registerTransition(">", CDATA_MAY_CLOSE, TEXT);
+    registerTransition("A-Za-z0-9/_:-", CDATA_MAY_CLOSE, CDATA_MAY_CLOSE);
+    registerTransition("[:default:]", CDATA_LT, CDATA_TEXT);
+    registerTransition("!", CDATA_LT, CDATA_COM_START);
+    registerTransition("/", CDATA_LT, CDATA_MAY_CLOSE);
+    registerTransition("[:default:]", CDATA_TEXT, CDATA_TEXT);
+    registerTransition("<", CDATA_TEXT, CDATA_LT);
+    registerTransition("[:default:]", CDATA_COM_DASH_DASH, CDATA_COM_BODY);
+    registerTransition(">", CDATA_COM_DASH_DASH, CDATA_TEXT);
+    registerTransition("-", CDATA_COM_DASH_DASH, CDATA_COM_DASH_DASH);
+    registerTransition("[:default:]", CDATA_COM_DASH, CDATA_COM_BODY);
+    registerTransition("-", CDATA_COM_DASH, CDATA_COM_DASH_DASH);
+    registerTransition("[:default:]", CDATA_COM_BODY, CDATA_COM_BODY);
+    registerTransition("-", CDATA_COM_BODY, CDATA_COM_DASH);
+    registerTransition("[:default:]", CDATA_COM_START_DASH, CDATA_TEXT);
+    registerTransition("-", CDATA_COM_START_DASH, CDATA_COM_BODY);
+    registerTransition("[:default:]", CDATA_COM_START, CDATA_TEXT);
+    registerTransition("-", CDATA_COM_START, CDATA_COM_START_DASH);
+    registerTransition("[:default:]", VALUE_DQ, VALUE_DQ);
+    registerTransition("\"", VALUE_DQ, TAG_SPACE);
+    registerTransition("[:default:]", VALUE_DQ_START, VALUE_DQ);
+    registerTransition("\"", VALUE_DQ_START, TAG_SPACE);
+    registerTransition("[:default:]", VALUE_Q, VALUE_Q);
+    registerTransition("\'", VALUE_Q, TAG_SPACE);
+    registerTransition("[:default:]", VALUE_Q_START, VALUE_Q);
+    registerTransition("\'", VALUE_Q_START, TAG_SPACE);
+    registerTransition("[:default:]", VALUE_TEXT, VALUE_TEXT);
+    registerTransition(" \t\n\r", VALUE_TEXT, TAG_SPACE);
+    registerTransition(">", VALUE_TEXT, TAG_CLOSE);
+    registerTransition("[:default:]", VALUE, VALUE_TEXT);
+    registerTransition(">", VALUE, TAG_CLOSE);
+    registerTransition(" \t\n\r", VALUE, VALUE);
+    registerTransition("\"", VALUE, VALUE_DQ_START);
+    registerTransition("\'", VALUE, VALUE_Q_START);
+    registerTransition("=", ATTR_SPACE, VALUE);
+    registerTransition("/", ATTR_SPACE, TAG_SPACE);
+    registerTransition("A-Za-z0-9_:-", ATTR_SPACE, ATTR);
+    registerTransition(" \t\n\r", ATTR_SPACE, ATTR_SPACE);
+    registerTransition(">", ATTR_SPACE, TAG_CLOSE);
+    registerTransition(" \t\n\r", ATTR, ATTR_SPACE);
+    registerTransition("=", ATTR, VALUE);
+    registerTransition("/", ATTR, TAG_SPACE);
+    registerTransition(">", ATTR, TAG_CLOSE);
+    registerTransition("A-Za-z0-9_:.-", ATTR, ATTR);
+    registerTransition("[:default:]", TAG_CLOSE, TEXT);
+    registerTransition("<", TAG_CLOSE, TAG_START);
+    registerTransition("/", TAG_SPACE, TAG_SPACE);
+    registerTransition("A-Za-z0-9_:-", TAG_SPACE, ATTR);
+    registerTransition(" \t\n\r", TAG_SPACE, TAG_SPACE);
+    registerTransition(">", TAG_SPACE, TAG_CLOSE);
+    registerTransition("[:default:]", PI_MAY_END, PI);
+    registerTransition(">", PI_MAY_END, TEXT);
+    registerTransition("[:default:]", PI, PI);
+    registerTransition("?", PI, PI_MAY_END);
+    registerTransition("[:default:]", COM_DASH_DASH, COM_BODY);
+    registerTransition(">", COM_DASH_DASH, TEXT);
+    registerTransition("-", COM_DASH_DASH, COM_DASH_DASH);
+    registerTransition("[:default:]", COM_DASH, COM_BODY);
+    registerTransition("-", COM_DASH, COM_DASH_DASH);
+    registerTransition("[:default:]", COM_BODY, COM_BODY);
+    registerTransition("-", COM_BODY, COM_DASH);
+    registerTransition("[:default:]", COM_OPEN, TEXT);
+    registerTransition("-", COM_OPEN, COM_BODY);
+    registerTransition("[:default:]", DECL_BODY, DECL_BODY);
+    registerTransition(">", DECL_BODY, TEXT);
+    registerTransition("[:default:]", DECL_START, DECL_BODY);
+    registerTransition(">", DECL_START, TEXT);
+    registerTransition("-", DECL_START, COM_OPEN);
+    registerTransition(">", TAG_NAME, TAG_CLOSE);
+    registerTransition(" \t\n\r", TAG_NAME, TAG_SPACE);
+    registerTransition("A-Za-z0-9/_:-", TAG_NAME, TAG_NAME);
+
+    // Manual change to remain in-sync with CL 10597850 in C HtmlParser.
+    registerTransition("[:default:]", TAG_START, TEXT);
+    registerTransition("<", TAG_START, TAG_START);
+    // End of manual change.
+
+    registerTransition("!", TAG_START, DECL_START);
+    registerTransition("?", TAG_START, PI);
+    registerTransition("A-Za-z0-9/_:-", TAG_START, TAG_NAME);
+    registerTransition("[:default:]", TEXT, TEXT);
+    registerTransition("<", TEXT, TAG_START);
+  }
+}
diff --git a/src/com/google/streamhtmlparser/impl/InternalState.java b/src/com/google/streamhtmlparser/impl/InternalState.java
new file mode 100644
index 0000000..3d66b8f
--- /dev/null
+++ b/src/com/google/streamhtmlparser/impl/InternalState.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser.impl;
+
+import com.google.common.base.Preconditions;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A very simple representation of the parser internal state. The state
+ * contains a small integer identifier (from 1 to 255) to allow for
+ * the implementation of a simple finite state machine. The name is
+ * purely informational.
+ *
+ * <p>In order to eliminate the possibility that different states have
+ * the same identifier, this class manages the idenitifiers themselves.
+ * The HTML and Javascript parser states are managed elsewhere in different
+ * "namespaces" hence will not clash and there is no current need for this
+ * class to disambiguate them further.
+ *
+ * <p>The methods to create new <code>InternalState</code> instances are
+ * package-scope only as they are only needed by <code>HtmlParserImpl</code>
+ * and <code>JavascriptParserImpl</code>.
+ */
+class InternalState {
+
+  // An InternalState to represent an error condition for all parsers.
+  static final InternalState INTERNAL_ERROR_STATE = new InternalState();
+
+  // MAX_ID and FIRST_ID are only used for asserts against developer error.
+  private static final int MAX_ID = 255;
+  private static final int FIRST_ID = 1;
+
+  private static AtomicInteger htmlStates = new AtomicInteger(FIRST_ID);
+  private static AtomicInteger javascriptStates = new AtomicInteger(FIRST_ID);
+  private final String name;
+  private final int id;
+
+  /**
+   * @param name the {@code String} identifier for this state
+   * @param id the integer identiifer for this state, guaranteed to be unique
+   */
+  private InternalState(String name, int id) {
+    Preconditions.checkNotNull(name);
+    Preconditions.checkArgument(id >= FIRST_ID);
+    Preconditions.checkArgument(id <= MAX_ID);
+    this.name = name;
+    this.id = id;
+  }
+
+  /**
+   * Used only for the error state. Bypasses assert checks.
+   */
+  private InternalState() {
+    name = "InternalStateError";
+    id = 0;
+  }
+
+  /**
+   * @return {@code String} name of that state.
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * @return {@code int} id of that state.
+   */
+  public int getId() {
+    return id;
+  }
+
+  /**
+   * @return {@code String} representation of that object, the format
+   *         may change.
+   */
+  @Override
+  public String toString() {
+    return String.format("InternalState: Name: %s; Id: %d", name, id);
+  }
+
+  /**
+   * Obtain a new {@code InternalState} instance for the HTML parser.
+   *
+   * @param name a unique identifier for this state useful during debugging
+   * @return a new {@code InternalState} object
+   */
+  static InternalState getInstanceHtml(String name) {
+    int htmlStateId = htmlStates.getAndIncrement();
+    return new InternalState(name, htmlStateId);
+  }
+
+  /**
+   * Obtain a new <code>InternalState</code> instance for the Javascript parser.
+   *
+   * @param name A unique identifier for this state useful during debugging
+   * @return a new {@code InternalState} object
+   */
+  static InternalState getInstanceJavascript(String name) {
+    int javascriptStateId = javascriptStates.getAndIncrement();
+    return new InternalState(name, javascriptStateId);
+  }
+}
diff --git a/src/com/google/streamhtmlparser/impl/JavascriptParserImpl.java b/src/com/google/streamhtmlparser/impl/JavascriptParserImpl.java
new file mode 100644
index 0000000..1f6ea49
--- /dev/null
+++ b/src/com/google/streamhtmlparser/impl/JavascriptParserImpl.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser.impl;
+
+import com.google.common.collect.Maps;
+import com.google.streamhtmlparser.ExternalState;
+import com.google.streamhtmlparser.JavascriptParser;
+import com.google.streamhtmlparser.util.HtmlUtils;
+import com.google.streamhtmlparser.util.JavascriptTokenBuffer;
+
+import java.util.Map;
+
+/**
+ * <p>Many comments copied almost verbatim from the original C version.
+ */
+public class JavascriptParserImpl extends GenericParser
+    implements JavascriptParser {
+
+  final static InternalState JS_TEXT;
+  final static InternalState JS_Q;
+  final static InternalState JS_Q_E;
+  final static InternalState JS_DQ;
+  final static InternalState JS_DQ_E;
+  final static InternalState JS_SLASH;
+  final static InternalState JS_REGEXP_SLASH;
+  final static InternalState JS_REGEXP;
+  final static InternalState JS_REGEXP_BRK;
+  final static InternalState JS_REGEXP_BRK_E;
+  final static InternalState JS_REGEXP_E;
+  final static InternalState JS_COM_LN;
+  final static InternalState JS_COM_ML;
+  final static InternalState JS_COM_ML_CLOSE;
+  final static InternalState JS_COM_AFTER;
+
+  static {
+    JS_TEXT = InternalState.getInstanceJavascript("JS_TEXT");
+    JS_Q  = InternalState.getInstanceJavascript("JS_Q");
+    JS_Q_E = InternalState.getInstanceJavascript("JS_Q_E");
+    JS_DQ = InternalState.getInstanceJavascript("JS_DQ");
+    JS_DQ_E = InternalState.getInstanceJavascript("JS_DQ_E");
+    JS_SLASH = InternalState.getInstanceJavascript("JS_SLASH");
+    JS_REGEXP = InternalState.getInstanceJavascript("JS_REGEXP");
+    JS_REGEXP_SLASH = InternalState.getInstanceJavascript("JS_REGEXP_SLASH");
+    JS_REGEXP_E = InternalState.getInstanceJavascript("JS_REGEXP_E");
+    JS_REGEXP_BRK = InternalState.getInstanceJavascript("JS_REGEXP_BRK");
+    JS_REGEXP_BRK_E = InternalState.getInstanceJavascript("JS_REGEXP_BRK_E");
+    JS_COM_LN = InternalState.getInstanceJavascript("COMMENT_LN");
+    JS_COM_ML = InternalState.getInstanceJavascript("COMMENT_ML");
+    JS_COM_ML_CLOSE = InternalState.getInstanceJavascript("COMMENT_ML_CLOSE");
+    JS_COM_AFTER = InternalState.getInstanceJavascript("COMMENT_AFTER");
+  }
+
+  private static final Map<InternalState, ExternalState> STATE_MAPPING =
+      Maps.newHashMap();
+  static {
+    initializeStateMapping();
+  }
+
+  private static final ParserStateTable STATE_TABLE = new ParserStateTable();
+  static {
+    initializeParserStateTable();
+  }
+
+  private final JavascriptTokenBuffer ccBuffer;
+
+  /**
+   * Creates a {@code JavascriptParserImpl} object.
+   */
+  public JavascriptParserImpl() {
+    super(STATE_TABLE, STATE_MAPPING, JS_TEXT);
+    ccBuffer = new JavascriptTokenBuffer();
+  }
+
+  /**
+   * Creates a {@code JavascriptParserImpl} object that is a copy
+   * of the one provided.
+   *
+   * @param aJavascriptParserImpl the {@code JavascriptParserImpl} to copy
+   */
+  public JavascriptParserImpl(JavascriptParserImpl aJavascriptParserImpl) {
+    super(aJavascriptParserImpl);
+    ccBuffer = new JavascriptTokenBuffer(aJavascriptParserImpl.ccBuffer);
+  }
+
+  @Override
+  public void reset() {
+    super.reset();
+    currentState = JS_TEXT;
+  }
+
+  @Override
+  protected InternalState handleEnterState(InternalState currentState,
+                                           InternalState expectedNextState,
+                                           char input) {
+    InternalState nextState = expectedNextState;
+    if (currentState == JS_SLASH) {
+      nextState = enterStateJsSlash(currentState, input);
+    } else if (currentState == JS_COM_AFTER) {
+      enterStateJsCommentAfter();
+    }
+    return nextState;
+  }
+
+  @Override
+  protected InternalState handleExitState(InternalState currentState,
+                                          InternalState expectedNextState,
+                                          char input) {
+    // Nothing to do - no handlers for exit states
+    return expectedNextState;
+  }
+
+  @Override
+  protected InternalState handleInState(InternalState currentState,
+                                        char input) {
+    if (currentState == JS_TEXT) {
+      inStateJsText(input);
+    }
+    return currentState;
+  }
+
+  /**
+   * Called every time we find a slash ('/') character in the javascript
+   * text (except for slashes that close comments or regexp literals).
+   *
+   * <p>Comment copied verbatim from the corresponding C-version.
+   *
+   * <p>Implements the logic to figure out if this slash character is a
+   * division operator or if it opens a regular expression literal.
+   * This is heavily inspired by the syntactic resynchronization
+   * for javascript 2.0:
+   *
+   * <p>When we receive a '/', we look at the previous non space character
+   * to figure out if it's the ending of a punctuator that can precede a
+   * regexp literal, in which case we assume the current '/' is part of a
+   * regular expression literal (or the opening of a javascript comment,
+   * but that part is dealt with in the state machine). The exceptions to
+   * this are unary operators, so we look back a second character to rule
+   * out '++' and '--'.
+   *
+   * <p> Although it is not straightforward to figure out if the binary
+   * operator is a postfix of the previous expression or a prefix of the
+   * regular expression, we rule out the later as it is an uncommon practice.
+   *
+   * <p>If we ruled out the previous token to be a valid regexp preceding
+   * punctuator, we extract the last identifier in the buffer and match
+   * against a list of keywords that are known to precede expressions in
+   * the grammar. If we get a match on any of these keywords, then we are
+   * opening a regular expression, if not, then we have a division operator.
+   *
+   * <p>Known cases that are accepted by the grammar but we handle
+   * differently, although I (falmeida) don't believe there is a
+   * legitimate usage for those:
+   *   Division of a regular expression: var result = /test/ / 5;
+   *   Prefix unary increment of a regular expression: var result = ++/test/;
+   *   Division of an object literal: { a: 1 } /x/.exec('x');
+   *
+   * @param state being entered to
+   * @param input character being processed
+   * @return state next state to go to, may be the same as the one we
+   *     were called with
+   *
+   * <a>http://www.mozilla.org/js/language/js20-2000-07/rationale/syntax.html>
+   * Syntactic Resynchronization</a>
+   */
+  private InternalState enterStateJsSlash(InternalState state, char input) {
+
+    InternalState nextState = state;
+    int position = -1;
+
+    // Consume the last whitespace
+    if (HtmlUtils.isJavascriptWhitespace(ccBuffer.getChar(position))) {
+      --position;
+    }
+
+    switch (ccBuffer.getChar(position)) {
+      // Ignore unary increment
+      case '+':
+        if (ccBuffer.getChar(position - 1) != '+') {
+          nextState = JS_REGEXP_SLASH;
+        }
+        break;
+      case '-':
+        // Ignore unary decrement
+        if (ccBuffer.getChar(position - 1) != '-') {
+          nextState = JS_REGEXP_SLASH;
+        }
+        break;
+        // List of punctuator endings except ), ], }, + and - *
+      case '=':
+      case '<':
+      case '>':
+      case '&':
+      case '|':
+      case '!':
+      case '%':
+      case '*':
+      case '/':
+      case ',':
+      case ';':
+      case '?':
+      case ':':
+      case '^':
+      case '~':
+      case '{':
+      case '(':
+      case '[':
+      case '}':
+      case '\0':
+        nextState = JS_REGEXP_SLASH;
+        break;
+      default:
+        String lastIdentifier = ccBuffer.getLastIdentifier();
+        if (lastIdentifier != null && HtmlUtils
+            .isJavascriptRegexpPrefix(lastIdentifier)) {
+          nextState = JS_REGEXP_SLASH;
+        }
+    }
+    ccBuffer.appendChar(input);
+    return nextState;
+  }
+
+  /**
+   * Called at the end of a javascript comment.
+   *
+   * <p>When we open a comment, the initial '/' was inserted into the ring
+   * buffer, but it is not a token and should be considered whitespace
+   * for parsing purposes.
+   *
+   * <p>When we first saw the '/' character, we didn't yet know if it was
+   * the beginning of a comment, a division operator, or a regexp.
+   *
+   * <p>In this function we just replace the inital '/' with a whitespace
+   * character, unless we had a preceding whitespace character, in which
+   * case we just remove the '/'. This is needed to ensure all spaces in
+   * the buffer are correctly folded.
+   */
+  private void enterStateJsCommentAfter() {
+    if (HtmlUtils.isJavascriptWhitespace(ccBuffer.getChar(-2))) {
+      ccBuffer.popChar();
+    } else {
+      ccBuffer.setChar(-1, ' ');
+    }
+  }
+
+  private void inStateJsText(char input) {
+    ccBuffer.appendChar(input);
+  }
+
+// ======================================================= //
+// SECTION BELOW WILL ALL BE AUTO-GENERATED IN FUTURE.     //
+// ======================================================= //
+
+  private static void registerMapping(InternalState internalState,
+                                      ExternalState externalState) {
+    STATE_MAPPING.put(internalState, externalState);
+  }
+
+  private static void initializeStateMapping() {
+    // Each parser implementation must map the error state appropriately.
+    registerMapping(InternalState.INTERNAL_ERROR_STATE,
+                    JavascriptParser.STATE_ERROR);
+
+    registerMapping(JS_TEXT, JavascriptParser.STATE_TEXT);
+    registerMapping(JS_Q, JavascriptParser.STATE_Q);
+    registerMapping(JS_Q_E, JavascriptParser.STATE_Q);
+    registerMapping(JS_DQ, JavascriptParser.STATE_DQ);
+    registerMapping(JS_DQ_E, JavascriptParser.STATE_DQ);
+    registerMapping(JS_SLASH, JavascriptParser.STATE_TEXT);
+    registerMapping(JS_REGEXP_SLASH, JavascriptParser.STATE_TEXT);
+    registerMapping(JS_REGEXP, JavascriptParser.STATE_REGEXP);
+    registerMapping(JS_REGEXP_BRK,JavascriptParser.STATE_REGEXP);
+    registerMapping(JS_REGEXP_BRK_E, JavascriptParser.STATE_REGEXP);
+    registerMapping(JS_REGEXP_E,JavascriptParser.STATE_REGEXP);
+    registerMapping(JS_COM_LN, JavascriptParser.STATE_COMMENT);
+    registerMapping(JS_COM_ML, JavascriptParser.STATE_COMMENT);
+    registerMapping(JS_COM_ML_CLOSE, JavascriptParser.STATE_COMMENT);
+    registerMapping(JS_COM_AFTER, JavascriptParser.STATE_TEXT);
+  }
+
+  private static void registerTransition(String expression,
+                                         InternalState source,
+                                         InternalState to) {
+    // It seems to silly to go through a StateTableTransition here
+    // but it adds extra data checking.
+    StateTableTransition stt = new StateTableTransition(expression,
+                                                        source, to);
+    STATE_TABLE.setExpression(stt.getExpression(), stt.getFrom(),
+                              stt.getTo());
+  }
+
+  private static void initializeParserStateTable() {
+    registerTransition("[:default:]", JS_COM_AFTER, JS_TEXT);
+    registerTransition("/", JS_COM_AFTER, JS_SLASH);
+    registerTransition("\"", JS_COM_AFTER, JS_DQ);
+    registerTransition("\'", JS_COM_AFTER, JS_Q);
+    registerTransition("[:default:]", JS_COM_ML_CLOSE, JS_COM_ML);
+    registerTransition("/", JS_COM_ML_CLOSE,JS_COM_AFTER);
+    registerTransition("[:default:]", JS_COM_ML, JS_COM_ML);
+    registerTransition("*", JS_COM_ML, JS_COM_ML_CLOSE);
+    registerTransition("[:default:]", JS_COM_LN,JS_COM_LN);
+    registerTransition("\n", JS_COM_LN,JS_COM_AFTER);
+    registerTransition("[:default:]", JS_REGEXP_E, JS_REGEXP);
+    registerTransition("[:default:]", JS_REGEXP_BRK_E, JS_REGEXP_BRK);
+    registerTransition("[:default:]", JS_REGEXP_BRK, JS_REGEXP_BRK);
+    registerTransition("]", JS_REGEXP_BRK, JS_REGEXP);
+    registerTransition("\\", JS_REGEXP_BRK, JS_REGEXP_BRK_E);
+    registerTransition("[:default:]", JS_REGEXP, JS_REGEXP);
+    registerTransition("/", JS_REGEXP, JS_TEXT);
+    registerTransition("[", JS_REGEXP, JS_REGEXP_BRK);
+    registerTransition("\\", JS_REGEXP, JS_REGEXP_E);
+    registerTransition("[:default:]", JS_REGEXP_SLASH, JS_REGEXP);
+    registerTransition("[", JS_REGEXP_SLASH, JS_REGEXP_BRK);
+    registerTransition("\\", JS_REGEXP_SLASH, JS_REGEXP_E);
+    registerTransition("*", JS_REGEXP_SLASH, JS_COM_ML);
+    registerTransition("/", JS_REGEXP_SLASH, JS_COM_LN);
+    registerTransition("[:default:]", JS_SLASH, JS_TEXT);
+    registerTransition("*", JS_SLASH, JS_COM_ML);
+    registerTransition("/", JS_SLASH, JS_COM_LN);
+    registerTransition("[:default:]", JS_DQ_E,JS_DQ);
+    registerTransition("[:default:]", JS_DQ,JS_DQ);
+    registerTransition("\"", JS_DQ, JS_TEXT);
+    registerTransition("\\", JS_DQ, JS_DQ_E);
+    registerTransition("[:default:]", JS_Q_E,JS_Q);
+    registerTransition("[:default:]", JS_Q,JS_Q);
+    registerTransition("\'", JS_Q, JS_TEXT);
+    registerTransition("\\", JS_Q, JS_Q_E);
+    registerTransition("[:default:]", JS_TEXT, JS_TEXT);
+    registerTransition("/", JS_TEXT, JS_SLASH);
+    registerTransition("\"", JS_TEXT, JS_DQ);
+    registerTransition("\'", JS_TEXT, JS_Q);
+  }
+}
\ No newline at end of file
diff --git a/src/com/google/streamhtmlparser/impl/ParserStateTable.java b/src/com/google/streamhtmlparser/impl/ParserStateTable.java
new file mode 100644
index 0000000..a79a640
--- /dev/null
+++ b/src/com/google/streamhtmlparser/impl/ParserStateTable.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser.impl;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Holds a state table which is defined as the set of all
+ * recognized state transitions and the set of characters that
+ * trigger them.
+ *
+ * <p>The logic of what character causes what state transition is derived from
+ * a base definition written as a Python configuration file in the original
+ * C++ parser.
+ *
+ * <p>This class provides methods to initially build the state table and then
+ * methods at parsing time to determine the transitions to subsequent states.
+ *
+ * <p>Note on characters outside the extended ASCII range: Currently, all state
+ * transitions in the Python configuration file trigger only on extended
+ * ASCII characters, that is characters in the Unicode space of [U+0000 to
+ * U+00FF]. We use that property to design a more efficient state transition
+ * representation. When receiving characters outside that ASCII range, we
+ * simply apply the DEFAULT transition for the given state - as we do for any
+ * character that is not a hot character for that state. If no default
+ * transition exists, we switch to the Internal Error state.
+ *
+ * <p>Technical note: In Java, a {@code char} is a code unit and in some cases
+ * may not represent a complete Unicode code point. However, when that happens,
+ * the code units that follow for that code point are all in the surrogate area
+ * [U+D800 - U+DFFF] and hence outside of the ASCII range and will not trigger
+ * any incorrect state transitions.
+ *
+ * <p>This class is storage-inefficient but it is static at least
+ * and not generated for each Parser instance.
+ */
+class ParserStateTable {
+
+  /**
+   * A limit on how many different states we can have in one state table.
+   * Can be increased should it no longer be sufficient.
+   */
+  private static final int MAX_STATES = 256;
+
+  /**
+   * We only check transitions for (extended) ASCII characters, hence
+   * characters in the range 0 to MAX_CHARS -1.
+   */
+  private static final int MAX_CHARS = 256;
+
+  /**
+   * Records all state transitions except those identified as DEFAULT
+   * transitions. It is two dimensional: Stores a target {@code InternalState}
+   * given a source state (referenced by its numeric ID) and the current
+   * character.
+   */
+  private final InternalState[][] stateTable;
+
+  /**
+   * Records all DEFAULT state transitions. These are transitions provided
+   * using the {@code "[:default:]"} syntax in the Python configuration file.
+   * There can be only one such transition for any given source state, hence
+   * the array is one dimensional.
+   */
+  private final InternalState[] defaultStateTable;
+
+  public ParserStateTable() {
+    stateTable = new InternalState[MAX_STATES][MAX_CHARS];
+    defaultStateTable = new InternalState[MAX_STATES];
+  }
+
+  /**
+   * Returns the state to go to when receiving the current {@code char}
+   * in the {@code from} state.
+   * Returns {@code InternalState.INTERNAL_ERROR_STATE} if there is no
+   * state transition for that character and no default state transition
+   * for that state.
+   *
+   * <p>For ASCII characters, first look-up an explicit state transition for
+   * the current character. If none is found, look-up a default transition. For
+   * non-ASCII characters, look-up a default transition only.
+   *
+   * @param from the source state
+   * @param currentChar the character received
+   * @return the state to move to or {@code InternalState.INTERNAL_ERROR_STATE}
+   */
+  InternalState getNextState(InternalState from, int currentChar) {
+    // TODO: Consider throwing run-time error here.
+    if (from == null || currentChar < 0)
+      return InternalState.INTERNAL_ERROR_STATE;
+
+    int id = from.getId();
+    if (id < 0 || id >= MAX_STATES) {
+      return InternalState.INTERNAL_ERROR_STATE;
+    }
+
+    InternalState result = null;
+    if (currentChar < MAX_CHARS) {
+      result = stateTable[id][currentChar];
+    }
+    if (result == null) {
+        result = defaultStateTable[from.getId()];
+    }
+    return result != null ? result : InternalState.INTERNAL_ERROR_STATE;
+  }
+
+  void setExpression(String expr, InternalState from, InternalState to) {
+    if ((expr == null) || (from == null) || (to == null)) {
+      return;
+    }
+
+    // This special string indicates a default state transition.
+    if ("[:default:]".equals(expr)) {
+      setDefaultDestination(from, to);
+      return;
+    }
+    int i = 0;
+    while (i < expr.length()) {
+      // If next char is a '-' which is not the last character of the expr
+      if (i < (expr.length() - 2) && expr.charAt(i + 1) == '-') {
+        setRange(from, expr.charAt(i), expr.charAt(i + 2), to);
+        i += 2;
+      } else {
+        setDestination(from, expr.charAt(i), to);
+        i++;
+      }
+    }
+  }
+
+  private void fill(InternalState from, InternalState to) {
+    char c;
+    for (c = 0; c < MAX_CHARS; c++) {
+      setDestination(from, c, to);
+    }
+  }
+
+  private void setDefaultDestination(InternalState from, InternalState to) {
+    Preconditions.checkNotNull(from);   // Developer error if it triggers
+    Preconditions.checkNotNull(to);     // Developer error if it triggers
+    int id = from.getId();
+    if ((id < 0) || (id >= MAX_STATES)) {
+      return;
+    }
+    // TODO: Consider asserting if there was a state transition defined.
+    defaultStateTable[from.getId()] = to;
+  }
+
+  private void setDestination(InternalState from, char chr, InternalState to) {
+    Preconditions.checkNotNull(from);   // Developer error if it triggers
+    Preconditions.checkNotNull(to);     // Developer error if it triggers
+    Preconditions.checkArgument(chr >= 0 && chr < MAX_CHARS,
+                                "char must be in ASCII set: %c", chr);
+    int id = from.getId();
+    if ((id < 0) || (id >= MAX_STATES)) {
+      return;
+    }
+    stateTable[from.getId()][chr] = to;
+  }
+
+  private void setRange(InternalState from, char start, char end,
+                        InternalState to) {
+    // Developer error if either trigger.
+    Preconditions.checkArgument(start >= 0 && start < MAX_CHARS,
+                                "char must be in ASCII set: %c", start);
+    Preconditions.checkArgument(end >= 0 && end < MAX_CHARS,
+                                "char must be in ASCII set: %c", end);
+    char c;
+    for (c = start; c <= end; c++) {
+      setDestination(from, c, to);
+    }
+  }
+}
diff --git a/src/com/google/streamhtmlparser/impl/StateTableTransition.java b/src/com/google/streamhtmlparser/impl/StateTableTransition.java
new file mode 100644
index 0000000..6b8c2c6
--- /dev/null
+++ b/src/com/google/streamhtmlparser/impl/StateTableTransition.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser.impl;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Holds one state transition as derived from a Python configuration
+ * file. A state transition is a triplet as follows:
+ * <ul>
+ * <li>An expression which consists of one or more characters and/or
+ *     one or more range of characters.
+ * <li> A source state.
+ * <li> A destination state.
+ * </ul>
+ *
+ * <p>For example, the triplet ("a-z123", A, B) will cause the
+ * state to go from A to B for any character that is either 1,2,3 or in
+ * the range a-z inclusive.
+ */
+class StateTableTransition {
+
+  private final String expression;
+  private final InternalState from;
+  private final InternalState to;
+
+   /**
+   * Returns the full state of the {@code StateTableTransition} in a
+   * human readable form. The format of the returned {@code String} is not
+   * specified and is subject to change.
+   *
+   * @return full state of the {@code StateTableTransition}
+   */
+  @Override
+  public String toString() {
+    return String.format("Expression: %s; From: %s; To: %s",
+                         expression, from, to);
+  }
+
+  StateTableTransition(String expression, InternalState from,
+                       InternalState to) {
+    // Developer error if any triggers.
+    Preconditions.checkNotNull(expression);
+    Preconditions.checkNotNull(from);
+    Preconditions.checkNotNull(to);
+    this.expression = expression;
+    this.from = from;
+    this.to = to;
+  }
+
+   String getExpression() {
+     return expression;
+   }
+
+   InternalState getFrom() {
+     return from;
+   }
+
+   InternalState getTo() {
+     return to;
+   }
+}
diff --git a/src/com/google/streamhtmlparser/util/CharacterRecorder.java b/src/com/google/streamhtmlparser/util/CharacterRecorder.java
new file mode 100644
index 0000000..bbce6ad
--- /dev/null
+++ b/src/com/google/streamhtmlparser/util/CharacterRecorder.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser.util;
+
+/**
+ * Records (stores) characters supplied one at a time conditional on
+ * whether recording is currently enabled.
+ *
+ * <p>When {@link #maybeRecord(char)} is called, it will add the
+ * supplied character to the recording buffer but only if
+ * recording is in progress. This is useful in our
+ * {@link com.google.security.streamhtmlparser.HtmlParser}
+ * as the caller logic to enable/disable recording is decoupled from the logic
+ * of recording.
+ *
+ * <p>This is a specialized class - of no use to external code -
+ * which aims to be 100% compatible with the corresponding logic
+ * in the C-version of the HtmlParser, specifically in
+ * <code>statemachine.c</code>. In particular:
+ * <ul>
+ *   <li>The {@code startRecording()} and {@code stopRecording()} methods
+ *       may be called repeatedly without interleaving since the C version is
+ *       not guaranteed to interleave them.
+ *   <li>There is a size limit to the recording buffer as set in
+ *       {@link #RECORDING_BUFFER_SIZE}. Once the size is
+ *       reached, no further characters are recorded regardless of whether
+ *       recording is currently enabled.
+ * </ul>
+ */
+public class CharacterRecorder {
+
+  /**
+   * How many characters can be recorded before stopping to accept new
+   * ones. Set to one less than in the C-version as we do not need
+   * to reserve a character for the terminating null.
+   */
+  public static final int RECORDING_BUFFER_SIZE = 255;
+
+  /**
+   * This is where characters provided for recording are stored. Given
+   * that the <code>CharacterRecorder</code> object is re-used, might as well
+   * allocate the full size from the get-go.
+   */
+  private final StringBuilder sb;
+
+  /** Holds whether we are currently recording characters or not. */
+  private boolean recording;
+
+  /**
+   * Constructs an empty character recorder of fixed size currently
+   * not recording. See {@link #RECORDING_BUFFER_SIZE} for the size.
+   */
+  public CharacterRecorder() {
+    sb = new StringBuilder(RECORDING_BUFFER_SIZE);
+    recording = false;
+  }
+
+  /**
+   * Constructs a character recorder of fixed size that is a copy
+   * of the one provided. In particular it has the same recording
+   * setting and the same contents.
+   *
+   * @param aCharacterRecorder the {@code CharacterRecorder} to copy
+   */
+  public CharacterRecorder(CharacterRecorder aCharacterRecorder) {
+    recording = aCharacterRecorder.recording;
+    sb = new StringBuilder(RECORDING_BUFFER_SIZE);
+    sb.append(aCharacterRecorder.getContent());
+  }
+
+  /**
+   * Enables recording for incoming characters. The recording buffer is cleared
+   * of content it may have contained.
+   */
+  public void startRecording() {
+    // This is very fast, no memory (re-) allocation will take place.
+    sb.setLength(0);
+    recording = true;
+  }
+
+  /**
+   * Disables recording further characters.
+   */
+  public void stopRecording() {
+    recording = false;
+  }
+
+  /**
+   * Records the {@code input} if recording is currently on and we
+   * have space available in the buffer. If recording is not
+   * currently on, this method will not perform any action.
+   *
+   * @param input the character to record
+   */
+  public void maybeRecord(char input) {
+    if (recording && (sb.length() < RECORDING_BUFFER_SIZE)) {
+      sb.append(input);
+    }
+  }
+
+  /**
+   * Empties the underlying storage but does not change the recording
+   * state [i.e whether we are recording or not incoming characters].
+   */
+  public void clear() {
+    sb.setLength(0);
+  }
+
+  /**
+   * Empties the underlying storage and resets the recording indicator
+   * to indicate we are not recording currently.
+   */
+  public void reset() {
+    clear();
+    recording = false;
+  }
+
+  /**
+   * Returns the characters recorded in a {@code String} form. This
+   * method has no side-effects, the characters remain stored as is.
+   *
+   * @return the contents in a {@code String} form
+   */
+  public String getContent() {
+    return sb.toString();
+  }
+
+  /**
+   * Returns whether or not we are currently recording incoming characters.
+   *
+   * @return {@code true} if we are recording, {@code false} otherwise
+   */
+  public boolean isRecording() {
+    return recording;
+  }
+
+  /**
+   * Returns the full state of the object in a human readable form. The
+   * format of the returned {@code String} is not specified and is
+   * subject to change.
+   *
+   * @return the full state of this object
+   */
+  @Override
+  public String toString() {
+    return String.format("In recording: %s; Value: %s", isRecording(),
+                         sb.toString());
+  }
+}
diff --git a/src/com/google/streamhtmlparser/util/EntityResolver.java b/src/com/google/streamhtmlparser/util/EntityResolver.java
new file mode 100644
index 0000000..5e636a1
--- /dev/null
+++ b/src/com/google/streamhtmlparser/util/EntityResolver.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser.util;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Map;
+
+/**
+ * <p>Decodes (unescapes) HTML entities with the complication that these
+ * are received one character at a time hence must be stored temporarily.
+ * Also, we may receive some "junk" characters before the actual
+ * entity which we will discard.
+ *
+ * <p>This class is designed to be 100% compatible with the corresponding
+ * logic in the C-version of the
+ * {@link com.google.security.streamhtmlparser.HtmlParser}, found
+ * in <code>htmlparser.c</code>. There are however a few intentional
+ * differences outlines below:
+ * <ul>
+ *   <li>We accept lower and upper-case hex NCRs, the C-version
+ *       accepts only lower-case ones.
+ *   <li>The output on some invalid inputs may be different. This is
+ *       currently in the process of consolidation with Filipe.
+ *   <li>The API is a bit different, I find this one better suited
+ *       for Java. In particular, the C method <code>processChar</code>
+ *       returns the output {@code String} whereas in Java, we return
+ *       a status code and then provide the {@code String} in a separate
+ *       method <code>getEntity</code>. It is cleaner as it avoids the
+ *       need to return empty {@code String}s during incomplete processing.
+ * </ul>
+ *
+ * <p>Valid HTML entities have one of the following three forms:
+ * <ul>
+ *   <li><code>&amp;dd;</code> where dd is a number in decimal (base 10) form.
+ *   <li><code>&amp;x|Xyy;</code> where yy is a hex-number (base 16).
+ *   <li><code>&&lt;html-entity&gt;;</code> where
+ *       <code>&lt;html-entity&gt;</code> is one of <code>lt</code>,
+ *       <code>gt</code>, <code>amp</code>, <code>quot</code> or
+ *       <code>apos</code>.
+ * </ul>
+ *
+ * <p>A <code>reset</code> method is provided to facilitate object re-use.
+ */
+public class EntityResolver {
+
+  /**
+   * Returned in <code>processChar</code> method.
+   * <p>
+   * <ul>
+   *   <li><code>NOT_STARTED</code> indicates we are still processing
+   *       trailing characters before the start of an entity.
+   *       The caller may want to save the characters it provided us.
+   *   <li><code>IN_PROGRESS</code> indicates we are currently processing
+   *       characters part of an entity.
+   *   <li><code>COMPLETED</code> indicates we have finished processing
+   *       an entity. The caller can then invoke <code>getEntity</code>
+   *       then re-set the object for future re-use.
+   * </ul>
+   */
+  public enum Status {
+    NOT_STARTED("Not Started"),
+    IN_PROGRESS("In Progress"),
+    COMPLETED("Completed");
+
+    private final String message;
+
+    private Status(String message) {
+      this.message = message;
+    }
+
+    /**
+     * Returns a brief description of the {@code Status} for
+     * debugging purposes. The format of the returned {@code String}
+     * is not fully specified nor guaranteed to remain the same.
+     */
+    @Override
+    public String toString() {
+      return message;
+    }
+  }
+
+  /**
+   * How many characters to store as we are processing an entity. Once we
+   * reach that size, we know the entity is definitely invalid. The size
+   * is higher than needed but keeping it as-is for compatibility with
+   * the C-version.
+   */
+  private static final int MAX_ENTITY_SIZE = 10;
+
+  /**
+   * Map containing the recognized HTML entities and their decoded values.
+   * The trailing ';' is not included in the key but it is accounted for.
+   */
+  private static final Map<String, String> HTML_ENTITIES_MAP =
+      new ImmutableMap.Builder<String, String>()
+          .put("&lt", "<")
+          .put("&gt", ">")
+          .put("&amp", "&")
+          .put("&apos", "'")
+          .build();
+
+  /** Storage for received until characters until an HTML entity is complete. */
+  private final StringBuilder sb;
+
+  /**
+   * Indicates the state we are in. see {@link EntityResolver.Status}.
+   */
+  private Status status;
+  private String entity;
+
+  /**
+   * Constructs an entity resolver that is initially empty and
+   * with status {@code NOT_STARTED}, see {@link EntityResolver.Status}.
+   * 
+   */
+  public EntityResolver() {
+    sb = new StringBuilder();
+    status = Status.NOT_STARTED;
+    entity = "";
+  }
+
+  /**
+   * Constructs an entity resolver that is an exact copy of
+   * the one provided. In particular it has the same contents
+   * and status.
+   *
+   * @param aEntityResolver the entity resolver to copy
+   */
+  public EntityResolver(EntityResolver aEntityResolver) {
+    sb = new StringBuilder();
+    sb.replace(0, sb.length(), aEntityResolver.sb.toString());
+    entity = aEntityResolver.entity;
+    status = aEntityResolver.status;
+  }
+
+  /**
+   * Returns the object to its original state for re-use, deleting any
+   * stored characters that may be present.
+   */
+  public void reset() {
+    status = Status.NOT_STARTED;
+    sb.setLength(0);
+    entity = "";
+  }
+
+  /**
+   * Returns the full state of the <code>StreamEntityResolver</code>
+   * in a human readable form. The format of the returned <code>String</code>
+   * is not specified and is subject to change.
+   *
+   * @return full state of this object
+   */
+  @Override
+  public String toString() {
+    return String.format("Status: %s; Contents (%d): %s", status.toString(),
+                         sb.length(), sb.toString());
+  }
+
+  /**
+   * Returns the decoded HTML Entity. Should only be called
+   * after {@code processChar} returned status {@code COMPLETED}.
+   *
+   * @return the decoded HTML Entity or an empty {@code String} if
+   *         we were called with any status other than {@code COMPLETED}
+   */
+  public String getEntity() {
+    return entity;
+  }
+
+  /**
+   * Processes a character from the input stream and decodes any html entities
+   * from that processed input stream.
+   *
+   * @param input the {@code char} to process
+   * @return the processed {@code String}. Typically returns an empty
+   *         {@code String} while awaiting for more characters to complete
+   *         processing of the entity.
+   */
+  public Status processChar(char input) {
+    // Developer error if the precondition fails.
+    Preconditions.checkState(status != Status.NOT_STARTED || sb.length() == 0);
+    if (status == Status.NOT_STARTED) {
+      if (input == '&') {
+        sb.append(input);
+        status = Status.IN_PROGRESS;
+      }
+    } else if (status == Status.IN_PROGRESS) {
+      if ((input == ';') || (HtmlUtils.isHtmlSpace(input))) {
+        status = Status.COMPLETED;
+        entity = convertEntity(input);
+      } else {
+        if (sb.length() < MAX_ENTITY_SIZE) {
+          sb.append(input);
+        } else {
+          status = Status.COMPLETED;
+          entity = uncovertedInput(input);
+        }
+      }
+    } else {
+      // Status.COMPLETED, ignore character, do nothing.
+    }
+    return status;
+  }
+
+  /**
+   * Performs the decoding of a complete HTML entity and saves the
+   * result back into the buffer.
+   * <a href="http://www.w3.org/TR/REC-html40/charset.html#h-5.3.1">
+   * Numeric Character References</a>
+   *
+   * @param terminator the last character read, unused on successful
+   *        conversions since it is the end delimiter of the entity
+   * @return The decoded entity or the original input if we could not decode it.
+   */
+  private String convertEntity(char terminator) {
+    // Developer error if the buffer was empty or does not start with '&'.
+    Preconditions.checkArgument(sb.length() > 0);
+    Preconditions.checkArgument(sb.charAt(0) == '&');
+
+    if (sb.length() > 1) {
+      if (sb.charAt(1) == '#') {
+        if (sb.length() <= 2) {    // Error => return content as-is.
+          return uncovertedInput(terminator);
+        }
+        try {
+          if ((sb.charAt(2) == 'x') || (sb.charAt(2) == 'X')) {    // Hex NCR
+            return new String(Character.toChars(
+                Integer.parseInt(sb.substring(3), 16)));
+          } else {                                              // Decimal NCR
+            return new String(Character.toChars(
+                Integer.parseInt(sb.substring(2))));
+          }
+        } catch (NumberFormatException e) {
+          return uncovertedInput(terminator);
+        }
+      }
+
+      // See if it matches any of the few recognized entities.
+      String key = sb.toString();
+      if (HTML_ENTITIES_MAP.containsKey(key)) {
+        return HTML_ENTITIES_MAP.get(key);
+      }
+    }
+    // Covers the case of a lonely '&' given or valid/invalid unknown entities.
+    return uncovertedInput(terminator);
+  }
+
+  private String uncovertedInput(char terminator) {
+    return String.format("%s%c", sb.toString(), terminator);
+  }
+}
diff --git a/src/com/google/streamhtmlparser/util/HtmlUtils.java b/src/com/google/streamhtmlparser/util/HtmlUtils.java
new file mode 100644
index 0000000..1da7d48
--- /dev/null
+++ b/src/com/google/streamhtmlparser/util/HtmlUtils.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser.util;
+
+import com.google.common.collect.ImmutableSortedSet;
+
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/**
+ * Utility functions for HTML and Javascript that are most likely
+ * not interesting to users outside this package.
+ *
+ * <p>The <code>HtmlParser</code> will be open-sourced hence we took the
+ * decision to keep these utilities in this package as well as not to
+ * leverage others that may exist in the <code>google3</code> code base.
+ *
+ * <p>The functionality exposed is designed to be 100% compatible with
+ * the corresponding logic in the C-version of the HtmlParser as such
+ * we are particularly concerned with cross-language compatibility.
+ *
+ * <p>Note: The words {@code Javascript} and {@code ECMAScript} are used
+ * interchangeably unless otherwise noted.
+ */
+public final class HtmlUtils {
+
+  /**
+   * static utility class
+   */
+  private HtmlUtils() {
+  }  // COV_NF_LINE
+
+  /**
+   * Indicates the type of content contained in the {@code content} HTML
+   * attribute of the {@code meta} HTML tag. Used by
+   * {@link HtmlUtils#parseContentAttributeForUrl(String)}.
+   * <p>The values are:
+   * <ul>
+   * <li>{@code NONE} if it does not contain a URL in the expected format.
+   * <li>{@code URL_START} if it contains a URL but hasn't seen any of
+   * its contents.
+   * <li>{@code URL} if it contains a URL and has seen at least some of
+   * its contents.
+   * </ul>
+   */
+  public enum META_REDIRECT_TYPE {
+    NONE,
+    URL_START,
+    URL
+  }
+
+  /**
+   * A regular expression matching the format of a {@code content} attribute
+   * that contains a URL. Used by {@link #parseContentAttributeForUrl}.
+   */
+  private static final String META_REDIRECT_REGEX =
+      "^\\s*\\d*\\s*;\\s*URL\\s*=\\s*[\'\"]?";
+
+  // Safe for use by concurrent threads so we compile once.
+  private static final Pattern META_REDIRECT_PATTERN =
+      Pattern.compile(META_REDIRECT_REGEX, Pattern.CASE_INSENSITIVE);
+
+  /**
+   * Set of keywords that can precede a regular expression literal. Taken from:
+   * <a href="http://www.mozilla.org/js/language/js20-2000-07/rationale/syntax.html">
+   * Language Syntax</a>
+   *
+   * <p>The token {@code void} was added to the list. Several keywords are
+   * defined in Ecmascript 4 not Ecmascript 3. However, to keep the logic
+   * simple we do not differentiate on the version and bundle them all together.
+   */
+  private static final Set<String> REGEXP_TOKEN_PREFIXS =
+      ImmutableSortedSet.of(
+          "abstract",
+          "break",
+          "case",
+          "catch",
+          "class",
+          "const",
+          "continue",
+          "debugger",
+          "default",
+          "delete",
+          "do",
+          "else",
+          "enum",
+          "eval",
+          "export",
+          "extends",
+          "field",
+          "final",
+          "finally",
+          "for",
+          "function",
+          "goto",
+          "if",
+          "implements",
+          "import",
+          "in",
+          "instanceof",
+          "native",
+          "new",
+          "package",
+          "private",
+          "protected",
+          "public",
+          "return",
+          "static",
+          "switch",
+          "synchronized",
+          "throw",
+          "throws",
+          "transient",
+          "try",
+          "typeof",
+          "var",
+          "void",
+          "volatile",
+          "while",
+          "with");
+
+  /**
+   * Set of all HTML attributes which expect a URI (as the value).
+   * <a href="http://www.w3.org/TR/html4/index/attributes.html">Index of Attributes</a>
+   */
+  private static final Set<String> ATTRIBUTE_EXPECTS_URI =
+      ImmutableSortedSet.of(
+          "action",
+          "archive",
+          "background",
+          "cite",
+          "classid",
+          "codebase",
+          "data",
+          "dynsrc",
+          "href",
+          "longdesc",
+          "src",
+          "usemap");
+
+  /**
+   * Set of {@code Character}s considered whitespace in Javascript.
+   * See {@link #isJavascriptWhitespace(char)}
+   */
+  private static final Set<Character> JAVASCRIPT_WHITESPACE =
+      ImmutableSortedSet.of(
+            '\u0009',         /* Tab \t */
+            '\n',             /* Line-Feed 0x0A */
+            '\u000B',         /* Vertical Tab 0x0B */
+            '\u000C',         /* Form Feed \f */
+            '\r',             /* Carriage Return 0x0D */
+            ' ',              /* Space 0x20 */
+            '\u00A0',         /* Non-breaking space 0xA0 */
+            '\u2028',         /* Line separator */
+            '\u2029');        /* Paragraph separator */
+
+  /**
+  * Set of {@code Character}s considered whitespace in HTML.
+  * See {@link #isHtmlSpace(char)}
+  */
+ private static final Set<Character> HTML_WHITESPACE =
+      ImmutableSortedSet.of(
+          ' ',
+          '\t',
+          '\n',
+          '\r',
+          '\u200B');
+
+
+  /**
+   * Determines if the HTML attribute specified expects javascript
+   * for its value. Such is the case for example with the {@code onclick}
+   * attribute.
+   *
+   * <p>Currently returns {@code true} for any attribute name that starts
+   * with "on" which is not exactly correct but we trust a developer to
+   * not use non-spec compliant attribute names (e.g. onbogus).
+   *
+   * @param attribute the name of an HTML attribute
+   * @return {@code false} if the input is null or is not an attribute
+   *         that expects javascript code; {@code true}
+   */
+  public static boolean isAttributeJavascript(String attribute) {
+    return ((attribute != null) && attribute.startsWith("on"));
+  }
+
+  /**
+   * Determines if the HTML attribute specified expects a {@code style}
+   * for its value. Currently this is only true for the {@code style}
+   * HTML attribute.
+   *
+   * @param attribute the name of an HTML attribute
+   * @return {@code true} iff the attribute name is one that expects a
+   *     style for a value; otherwise {@code false}
+   */
+  public static boolean isAttributeStyle(String attribute) {
+    return "style".equals(attribute);
+  }
+
+  /**
+   * Determines if the HTML attribute specified expects a {@code URI}
+   * for its value. For example, both {@code href} and {@code src}
+   * expect a {@code URI} but {@code style} does not. Returns
+   * {@code false} if the attribute given was {@code null}.
+   *
+   * @param attribute the name of an HTML attribute
+   * @return {@code true} if the attribute name is one that expects
+   *         a URI for a value; otherwise {@code null}
+   *
+   * @see #ATTRIBUTE_EXPECTS_URI
+   */
+  public static boolean isAttributeUri(String attribute) {
+    return ATTRIBUTE_EXPECTS_URI.contains(attribute);
+  }
+
+  /**
+   * Determines if the specified character is an HTML whitespace character.
+   * A character is an HTML whitespace character if and only if it is one
+   * of the characters below.
+   * <ul>
+   * <li>A <code>Space</code> character
+   * <li>A <code>Tab</code> character
+   * <li>A <code>Line feed</code> character
+   * <li>A <code>Carriage Return</code> character
+   * <li>A <code>Zero-Width Space</code> character
+   * </ul>
+   *
+   * Note: The list includes the zero-width space (<code>&amp;#x200B;</code>)
+   * which is not included in the C version.
+   *
+   * @param chr the {@code char} to check
+   * @return {@code true} if the character is an HTML whitespace character
+   *
+   * <a href="http://www.w3.org/TR/html401/struct/text.html#h-9.1">White space</a>
+   */
+  public static boolean isHtmlSpace(char chr) {
+    return HTML_WHITESPACE.contains(chr);
+  }
+
+  /**
+   * Determines if the specified character is an ECMAScript whitespace or line
+   * terminator character. A character is a whitespace or line terminator if
+   * and only if it is one of the characters below:
+   * <ul>
+   * <li>A white-space character (<code>Tab</code>, <code>Vertical Tab</code>,
+   *     <code>Form Feed</code>, <code>Space</code>,
+   *     <code>No-break space</code>)
+   * <li>A line terminator character (<code>Line Feed</code>,
+   *     <code>Carriage Return</code>, <code>Line separator</code>,
+   *     <code>Paragraph Separator</code>).
+   * </ul>
+   *
+   * <p>Encompasses the characters in sections 7.2 and 7.3 of ECMAScript 3, in
+   * particular, this list is quite different from that in
+   * <code>Character.isWhitespace</code>.
+   * <a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf">
+   * ECMAScript Language Specification</a>
+   *
+   * @param chr the {@code char} to check
+   * @return {@code true} or {@code false}
+   *
+   */
+  public static boolean isJavascriptWhitespace(char chr) {
+    return JAVASCRIPT_WHITESPACE.contains(chr);
+  }
+
+  /**
+   * Determines if the specified character is a valid character in an
+   * ECMAScript identifier. This determination is currently not exact,
+   * in particular:
+   * <ul>
+   * <li>It does not accept Unicode letters, only ASCII ones.
+   * <li>It does not distinguish between the first character of an identifier
+   *     (which cannot contain numbers) and subsequent characters.
+   * </li>
+   * </ul>
+   *
+   * We are considering leveraging <code>Character.isJavaIdentifierStart</code>
+   * and <code>Character.isJavaIdentifierPart</code> given that Java
+   * and Javascript follow similar identifier naming rules but we lose
+   * compatibility with the C-version.
+   *
+   * @param chr {@code char} to check
+   * @return {@code true} if the {@code chr} is a Javascript whitespace
+   *         character; otherwise {@code false}
+   */
+  public static boolean isJavascriptIdentifier(char chr) {
+    return ((chr >= 'a' && chr <= 'z')
+        || (chr >= 'A' && chr <= 'Z')
+        || (chr >= '0' && chr <= '9')
+        || chr == '_' || chr == '$');
+  }
+
+  /**
+   * Determines if the input token provided is a valid token prefix to a
+   * javascript regular expression.  The token argument is compared against
+   * a {@code Set} of identifiers that can precede a regular expression in the
+   * javascript grammar, and returns {@code true} if the provided
+   * {@code String} is in that {@code Set}.
+   *
+   * @param input the {@code String} token to check
+   * @return {@code true} iff the token is a valid prefix of a regexp
+   */
+  public static boolean isJavascriptRegexpPrefix(String input) {
+    return REGEXP_TOKEN_PREFIXS.contains(input);
+  }
+
+  /**
+   * Encodes the specified character using Ascii for convenient insertion into
+   * a single-quote enclosed {@code String}. Printable characters
+   * are returned as-is. Carriage Return, Line Feed, Horizontal Tab,
+   * back-slash and single quote are all backslash-escaped. All other characters
+   * are returned hex-encoded.
+   * 
+   * @param chr {@code char} to encode
+   * @return an Ascii-friendly encoding of the given {@code char}
+   */
+  public static String encodeCharForAscii(char chr) {
+    if (chr == '\'') {
+      return "\\'";
+    } else if (chr == '\\') {
+      return "\\\\";
+    } else if (chr >= 32 && chr <= 126) {
+      return String.format("%c", chr);
+    } else if (chr == '\n') {
+      return "\\n";
+    } else if (chr == '\r') {
+      return "\\r";
+    } else if (chr == '\t') {
+      return "\\t";
+    } else {
+      // Cannot apply a precision specifier for integral types. Specifying
+      // 0-padded hex-encoding with minimum width of two.
+      return String.format("\\u%04x", (int)chr);
+    }
+  }
+
+  /**
+   * Parses the given {@code String} to determine if it contains a URL in the
+   * format followed by the {@code content} attribute of the {@code meta}
+   * HTML tag.
+   *
+   * <p>This function expects to receive the value of the {@code content} HTML
+   * attribute. This attribute takes on different meanings depending on the
+   * value of the {@code http-equiv} HTML attribute of the same {@code meta}
+   * tag. Since we may not have access to the {@code http-equiv} attribute,
+   * we instead rely on parsing the given value to determine if it contains
+   * a URL.
+   *
+   * The specification of the {@code meta} HTML tag can be found in:
+   *   http://dev.w3.org/html5/spec/Overview.html#attr-meta-http-equiv-refresh
+   *
+   * <p>We return {@link HtmlUtils.META_REDIRECT_TYPE} indicating whether the
+   * value contains a URL and whether we are at the start of the URL or past
+   * the start. We are at the start of the URL if and only if one of the two
+   * conditions below is true:
+   * <ul>
+   * <li>The given input does not contain any characters from the URL proper.
+   * Example "5; URL=".
+   * <li>The given input only contains the optional leading single or double
+   * quote leading the URL. Example "5; URL='".
+   * </li>
+   * </ul>
+   *
+   * <p>Examples:
+   * <ul>
+   * <li> Example of a complete {@code meta} tag where the {@code content}
+   * attribute contains a URL [we are not at the start of the URL]:
+   * <pre>
+   * &lt;meta http-equiv="refresh" content="5; URL=http://www.google.com"&gt;
+   * </pre>
+   * <li> Example of a complete {@code meta} tag where the {@code content}
+   * attribute contains a URL [we are at the start of the URL]:
+   * <pre>
+   * &lt;meta http-equiv="refresh" content="5; URL="&gt;
+   * </pre>
+   * <li>Example of a complete {@code meta} tag where the {@code content}
+   * attribute does not contain a URL:
+   * <pre>
+   * &lt;meta http-equiv="content-type" content="text/html"&gt;
+   * </pre>
+   * </ul>
+   *
+   * @param value {@code String} to parse
+   * @return {@link HtmlUtils.META_REDIRECT_TYPE} indicating the presence
+   * of a URL in the given value
+   */
+  public static META_REDIRECT_TYPE parseContentAttributeForUrl(String value) {
+    if (value == null)
+      return META_REDIRECT_TYPE.NONE;
+
+    Matcher matcher = META_REDIRECT_PATTERN.matcher(value);
+    if (!matcher.find())
+      return META_REDIRECT_TYPE.NONE;
+
+    // We have more content.
+    if (value.length() > matcher.end())
+      return META_REDIRECT_TYPE.URL;
+
+    return META_REDIRECT_TYPE.URL_START;
+  }
+}
diff --git a/src/com/google/streamhtmlparser/util/JavascriptTokenBuffer.java b/src/com/google/streamhtmlparser/util/JavascriptTokenBuffer.java
new file mode 100644
index 0000000..1fa1718
--- /dev/null
+++ b/src/com/google/streamhtmlparser/util/JavascriptTokenBuffer.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 com.google.streamhtmlparser.util;
+
+import com.google.common.base.Preconditions;
+
+import java.util.Arrays;
+
+/**
+ * Implements a circular (ring) buffer of characters with specialized
+ * application logic in order to determine the context of some
+ * Javascript content that is being parsed.
+ *
+ * This is a specialized class - of no use to external code -
+ * which aims to be 100% compatible with the corresponding logic
+ * in the C-version of the HtmlParser, specifically
+ * <code>jsparser.c</code>. In particular:
+ * <ul>
+ *   <li> The API is odd, using negative indexes to access content in
+ *        the buffer. Changing the API would mean changing the test
+ *        cases and have more difficulty determining whether we are
+ *        remaining compatible with the C-version. It is left as an
+ *        exercise for once the code is very stable and proven.
+ *   <li> Repeated whitespace is folded into just one character to
+ *        use the space available efficiently.
+ *   <li> The buffer size is fixed. There is currently no need to
+ *        make it variable so we avoid the need for constructors.
+ * </ul>
+ */
+public class JavascriptTokenBuffer {
+
+  /**
+   * Size of the ring buffer used to lookup the last token in the javascript
+   * stream. The size is somewhat arbitrary but must be larger than
+   * the biggest token we want to lookup plus three: Two delimiters plus
+   * an empty ring buffer slot.
+   */
+  private static final int BUFFER_SIZE = 18;
+
+  /** Storage implementing the circular buffer. */
+  private final char[] buffer;
+
+  /** Index of the first item in our circular buffer. */
+  private int startIndex;
+
+  /** Index of the last item in our circular buffer. */
+  private int endIndex;
+
+  /**
+   * Constructs an empty javascript token buffer. The size is fixed,
+   * see {@link #BUFFER_SIZE}.
+   */
+  public JavascriptTokenBuffer() {
+    buffer = new char[BUFFER_SIZE];
+    startIndex = 0;
+    endIndex = 0;
+  }
+
+  /**
+   * Constructs a javascript token buffer that is identical to
+   * the one given. In particular, it has the same size and contents.
+   *
+   * @param aJavascriptTokenBuffer the {@code JavascriptTokenBuffer} to copy
+   */
+  public JavascriptTokenBuffer(JavascriptTokenBuffer aJavascriptTokenBuffer) {
+    buffer = Arrays.copyOf(aJavascriptTokenBuffer.buffer,
+                           aJavascriptTokenBuffer.buffer.length);
+    startIndex = aJavascriptTokenBuffer.startIndex;
+    endIndex = aJavascriptTokenBuffer.endIndex;
+  }
+
+  /**
+   * A simple wrapper over <code>appendChar</code>, it appends a string
+   * to the buffer. Sequences of whitespace and newlines
+   * are folded into one character to save space. Null strings are
+   * not allowed.
+   *
+   * @param input the {@code String} to append, cannot be {@code null}
+   */
+  // TODO: Move to testing since not used in code.
+  public void appendString(String input) {
+    if (input == null) {
+      throw new NullPointerException("input == null is not allowed");
+    }
+    for (int i = 0; i < input.length(); i++) {
+      appendChar(input.charAt(i));
+    }
+  }
+
+  /**
+   * Appends a character to the buffer. We fold sequences of whitespace and
+   * newlines into one to save space.
+   *
+   * @param input the {@code char} to append
+   */
+  public void appendChar(char input) {
+    if (HtmlUtils.isJavascriptWhitespace(input) &&
+        HtmlUtils.isJavascriptWhitespace(getChar(-1))) {
+      return;
+    }
+    buffer[endIndex] = input;
+    endIndex = (endIndex + 1) % buffer.length;
+    if (endIndex == startIndex) {
+      startIndex = (endIndex + 1) % buffer.length;
+    }
+  }
+
+  /**
+   * Returns the last character in the buffer and removes it from the buffer
+   * or the NUL character '\0' if the buffer is empty.
+   *
+   * @return last character in the buffer or '\0' if the buffer is empty
+   */
+  public char popChar() {
+    if (startIndex == endIndex) {
+      return '\0';
+    }
+    endIndex--;
+    if (endIndex < 0) {
+      endIndex += buffer.length;
+    }
+    return buffer[endIndex];
+  }
+
+  /**
+   * Returns the character at a given index in the buffer or nul ('\0')
+   * if the index is outside the range of the buffer. Such could happen
+   * if the buffer is not filled enough or the index is larger than the
+   * size of the buffer.
+   *
+   * <p>Position must be negative where -1 is the index of the last
+   * character in the buffer.
+   *
+   * @param position The index into the buffer
+   *
+   * @return character at the requested index
+   */
+  public char getChar(int position) {
+    assert(position < 0);   // Developer error if it triggers.
+
+    int absolutePosition = getAbsolutePosition(position);
+    if (absolutePosition < 0) {
+      return '\0';
+    }
+
+    return buffer[absolutePosition];
+  }
+
+  /**
+   * Sets the given {@code input} at the given {@code position} of the buffer.
+   * Returns {@code true} if we succeeded or {@code false} if we
+   * failed (i.e. the write was beyond the buffer boundary).
+   *
+   * <p>Index positions are negative where -1 is the index of the
+   * last character in the buffer.
+   *
+   * @param position The index at which to set the character
+   * @param input The character to set in the buffer
+   * @return {@code true} if we succeeded, {@code false} otherwise
+   */
+  public boolean setChar(int position, char input) {
+    assert(position < 0);   // Developer error if it triggers.
+
+    int absolutePosition = getAbsolutePosition(position);
+    if (absolutePosition < 0) {
+      return false;
+    }
+
+    buffer[absolutePosition] = input;
+    return true;
+  }
+
+
+  /**
+   * Returns the last javascript identifier/keyword in the buffer.
+   *
+   * @return the last identifier or {@code null} if none was found
+   */
+  public String getLastIdentifier() {
+    int end = -1;
+
+    if (HtmlUtils.isJavascriptWhitespace(getChar(-1))) {
+      end--;
+    }
+    int position;
+    for (position = end; HtmlUtils.isJavascriptIdentifier(getChar(position));
+         position--) {
+    }
+    if ((position + 1) >= end) {
+      return null;
+    }
+    return slice(position + 1, end);
+  }
+
+  /**
+   * Returns a slice of the buffer delimited by the given indices.
+   *
+   * The start and end indexes represent the start and end of the
+   * slice to copy. If the start argument extends beyond the beginning
+   * of the buffer, the slice will only contain characters
+   * starting from the beginning of the buffer.
+   *
+   * @param start The index of the first character the copy
+   * @param end the index of the last character to copy
+   *
+   * @return {@code String} between the given indices
+   */
+  public String slice(int start, int end) {
+    // Developer error if any of the asserts below fail.
+    Preconditions.checkArgument(start <= end);
+    Preconditions.checkArgument(start < 0);
+    Preconditions.checkArgument(end < 0);
+    
+    StringBuffer output = new StringBuffer();
+    for (int position = start; position <= end; position++) {
+      char c = getChar(position);
+      if (c != '\0') {
+        output.append(c);
+      }
+    }
+    return new String(output);
+  }
+
+  /**
+   * Returns the position relative to the start of the buffer or -1
+   * if the position is past the size of the buffer.
+   *
+   * @param position the index to be translated
+   * @return the position relative to the start of the buffer
+   */
+  private int getAbsolutePosition(int position) {
+    assert (position < 0);   // Developer error if it triggers.
+    if (position <= -buffer.length) {
+      return -1;
+    }
+    int len = endIndex - startIndex;
+    if (len < 0) {
+      len += buffer.length;
+    }
+    if (position < -len) {
+      return -1;
+    }
+    int absolutePosition = (position + endIndex) % buffer.length;
+    if (absolutePosition < 0) {
+      absolutePosition += buffer.length;
+    }
+    return absolutePosition;
+  }
+}
diff --git a/src/org/clearsilver/CS.java b/src/org/clearsilver/CS.java
new file mode 100644
index 0000000..8ae028b
--- /dev/null
+++ b/src/org/clearsilver/CS.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+public interface CS extends Closeable {
+
+  /**
+   * Specify a new/different global HDF
+   */
+  void setGlobalHDF(HDF global);
+
+  /**
+   * Return global hdf in use
+   */
+  HDF getGlobalHDF();
+
+  /**
+   * Clean up CS object state.
+   */
+  void close();
+
+  /**
+   * Parses the specified file as if it has template content. The file will
+   * be located using the HDF's loadpaths.
+   * @param filename the name of file to read in and parse.
+   * @throws java.io.FileNotFoundException if the specified file does not
+   * exist.
+   * @throws IOException other problems reading the file.
+   */
+  void parseFile(String filename) throws IOException;
+
+  /**
+   * Parse the given string as a CS template.
+   * @param content string to parse.
+   */
+  void parseStr(String content);
+  
+  /**
+   * Generate output from the CS templates and HDF objects that have been read
+   * in.
+   * @return the output of the template rendering.
+   */
+  String render();
+
+  /**
+   * Get the file loader in use, if any.
+   * @return the file loader in use.
+   */
+  CSFileLoader getFileLoader();
+
+  /**
+   * Set the CS file loader to use
+   * @param fileLoader the file loader that should be used.
+   */
+  void setFileLoader(CSFileLoader fileLoader);
+
+}
diff --git a/src/org/clearsilver/CSFileLoader.java b/src/org/clearsilver/CSFileLoader.java
new file mode 100644
index 0000000..59951d3
--- /dev/null
+++ b/src/org/clearsilver/CSFileLoader.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver;
+
+import java.io.IOException;
+
+/**
+ * Interface for CS file hook
+ */
+public interface CSFileLoader {
+
+  /**
+   * Callback method that is expected to return the contents of the specified
+   * file as a string.
+   * @param hdf the HDF structure associated with HDF or CS object making the
+   * callback.
+   * @param filename the name of the file that should be loaded.
+   * @return a string containing the contents of the file.
+   */
+  public String load(HDF hdf, String filename) throws IOException;
+
+}
diff --git a/src/org/clearsilver/CSUtil.java b/src/org/clearsilver/CSUtil.java
new file mode 100644
index 0000000..44e8f3a
--- /dev/null
+++ b/src/org/clearsilver/CSUtil.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Utility class containing helper methods
+ */
+public final class CSUtil {
+
+  private CSUtil() { }
+
+  public static final String HDF_LOADPATHS = "hdf.loadpaths";
+
+  /**
+   * Helper function that returns a concatenation of the loadpaths in the
+   * provided HDF.
+   * @param hdf an HDF structure containing load paths.
+   * @return A list of loadpaths in order in which to search.
+   * @throws NullPointerException if no loadpaths are found.
+   */
+  public static List<String> getLoadPaths(HDF hdf) {
+    return getLoadPaths(hdf, false);
+  }
+
+  /**
+   * Helper function that returns a concatenation of the loadpaths in the
+   * provided HDF.
+   * @param hdf an HDF structure containing load paths.
+   * @param allowEmpty if {@code true} then this will return an empty list when
+   *     no loadpaths are found in the HDF object, otherwise a
+   *     {@link NullPointerException} is thrown. Loadpaths are not needed if
+   *     no files are read in or are all specified by absolute paths.
+   * @return A list of loadpaths in order in which to search.
+   * @throws NullPointerException if no loadpaths are found and allowEmpty is
+   *     {@code false}.
+   */
+  public static List<String> getLoadPaths(HDF hdf, boolean allowEmpty) {
+    List<String> list = new LinkedList<String>();
+    HDF loadpathsHdf = hdf.getObj(HDF_LOADPATHS);
+    if (loadpathsHdf == null) {
+      if (allowEmpty) {
+        return list;
+      } else {
+        throw new NullPointerException("No HDF loadpaths located in the "
+            + "specified HDF structure");
+      }
+    }
+    for (HDF lpHdf = loadpathsHdf.objChild(); lpHdf != null;
+        lpHdf = lpHdf.objNext()) {
+      list.add(lpHdf.objValue());
+    }
+    return list;
+  }
+
+  /**
+   * Given an ordered list of directories to look in, locate the specified file.
+   * Returns <code>null</code> if file not found.
+   * @param loadpaths the ordered list of paths to search.
+   * @param filename the name of the file.
+   * @return a File object corresponding to the file. <code>null</code> if
+   *     file not found.
+   */
+  public static File locateFile(List<String> loadpaths, String filename) {
+    if (filename == null) {
+      throw new NullPointerException("No filename provided");
+    }
+    if (loadpaths == null) {
+      throw new NullPointerException("No loadpaths provided.");
+    }
+    for (String path : loadpaths) {
+      File file = new File(path, filename);
+      if (file.exists()) {
+        return file;
+      }
+    }
+    return null;
+  }
+}
diff --git a/src/org/clearsilver/ClearsilverFactory.java b/src/org/clearsilver/ClearsilverFactory.java
new file mode 100644
index 0000000..2561dd0
--- /dev/null
+++ b/src/org/clearsilver/ClearsilverFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver;
+
+/**
+ * A factory for constructing new CS and HDF objects.  Allows applications to
+ * provide subclasses of HDF or CS to be used by the Java Clearsilver
+ * templating system.
+ */
+public interface ClearsilverFactory {
+
+  /**
+   * Create a new CS object.
+   * @param hdf the HDF object to use in constructing the CS object.
+   * @return a new CS object
+   */
+  public CS newCs(HDF hdf);
+
+  /**
+   * Create a new CS object.
+   * @param hdf the HDF object to use in constructing the CS object.
+   * @param globalHdf the global HDF object to use in constructing the
+   * CS object.
+   * @return a new CS object
+   */
+  public CS newCs(HDF hdf, HDF globalHdf);
+
+  /**
+   * Create a new HDF object.
+   * @return a new HDF object
+   */
+  public HDF newHdf();
+}
diff --git a/src/org/clearsilver/DelegatedCs.java b/src/org/clearsilver/DelegatedCs.java
new file mode 100644
index 0000000..c0c9741
--- /dev/null
+++ b/src/org/clearsilver/DelegatedCs.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver;
+
+import java.io.IOException;
+
+/**
+ * Utility class that delegates all methods of an CS object. Made to
+ * facilitate the transition to CS being an interface and thus not
+ * extensible in the same way as it was.
+ * <p>
+ * This class, and its subclasses must take care to wrap or unwrap HDF and CS
+ * objects as they are passed through from the callers to the delegate object.
+ *
+ */
+public abstract class DelegatedCs implements CS {
+  private final CS cs;
+
+  public DelegatedCs(CS cs) {
+    // Give it an empty HDF. We aren't going to be using the super object anyways.
+    this.cs = cs;
+  }
+
+  public CS getCs() {
+    return cs;
+  }
+
+  /**
+   * Method subclasses are required to override with a method that returns a
+   * new DelegatedHdf object that wraps the specified HDF object.
+   *
+   * @param hdf an HDF object that should be wrapped in a new DelegatedHdf
+   * object of the same type as this current object.
+   * @return an object that is a subclass of DelegatedHdf and which wraps the
+   * given HDF object.
+   */
+  protected abstract DelegatedHdf newDelegatedHdf(HDF hdf);
+
+  public void setGlobalHDF(HDF global) {
+    if (global != null && global instanceof DelegatedHdf) {
+      global = ((DelegatedHdf)global).getHdf();
+    }
+    getCs().setGlobalHDF(global);
+  }
+
+  public HDF getGlobalHDF() {
+    HDF hdf =  getCs().getGlobalHDF();
+    return hdf != null ? newDelegatedHdf(hdf) : null;
+  }
+
+  public void close() {
+    getCs().close();
+  }
+
+  public void parseFile(String filename) throws IOException {
+    getCs().parseFile(filename);
+  }
+  public void parseStr(String content) {
+    getCs().parseStr(content);
+  }
+
+  public String render() {
+    return getCs().render();
+  }
+
+  public CSFileLoader getFileLoader() {
+    return getCs().getFileLoader();
+  }
+
+  public void setFileLoader(CSFileLoader fileLoader) {
+    getCs().setFileLoader(fileLoader);
+  }
+
+}
diff --git a/src/org/clearsilver/DelegatedHdf.java b/src/org/clearsilver/DelegatedHdf.java
new file mode 100644
index 0000000..02ffecf
--- /dev/null
+++ b/src/org/clearsilver/DelegatedHdf.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Utility class that delegates all methods of an HDF object. Made to
+ * facilitate the transition to HDF being an interface and thus not
+ * extensible in the same way as it was.
+ * <p>
+ * This class, and its subclasses must take care to wrap or unwrap HDF and CS
+ * objects as they are passed through from the callers to the delegate object.
+ */
+public abstract class DelegatedHdf implements HDF {
+
+  private final HDF hdf;
+
+  public DelegatedHdf(HDF hdf) {
+    if (hdf == null) {
+      throw new NullPointerException("Null HDF is not allowed in constructor of DelegatedHdf.");
+    }
+    this.hdf = hdf;
+  }
+
+  /**
+   * Utility function for concrete ClearsilverFactories to unwrap DelegatedHdfs
+   * and get down to a concrete (or unknown) HDF object.
+   * @param hdf the possibly DelegatedHdf to unwrap
+   * @return the innermost non-DelegatedHdf HDF object.
+   */
+  public static HDF getFullyUnwrappedHdf(HDF hdf) {
+    while (hdf instanceof DelegatedHdf) {
+      hdf = ((DelegatedHdf)hdf).getHdf();
+    }
+    return hdf;
+  }
+
+  public HDF getHdf() {
+    return hdf;
+  }
+
+  /**
+   * Method subclasses are required to override with a method that returns a
+   * new DelegatedHdf object that wraps the specified HDF object.
+   *
+   * @param hdf an HDF object that should be wrapped in a new DelegatedHdf
+   * object of the same type as this current object.
+   * @return an object that is a subclass of DelegatedHdf and which wraps the
+   * given HDF object.
+   */
+  protected abstract DelegatedHdf newDelegatedHdf(HDF hdf);
+
+  public void close() {
+    getHdf().close();
+  }
+
+  public boolean readFile(String filename) throws IOException, FileNotFoundException {
+    return getHdf().readFile(filename);
+  }
+
+  public CSFileLoader getFileLoader() {
+    return getHdf().getFileLoader();
+  }
+
+  public void setFileLoader(CSFileLoader fileLoader) {
+    getHdf().setFileLoader(fileLoader);
+  }
+
+  public boolean writeFile(String filename) throws IOException {
+    return getHdf().writeFile(filename);
+  }
+
+  public boolean readString(String data) {
+    return getHdf().readString(data);
+  }
+
+  public String writeString() {
+    return getHdf().writeString();
+  }
+
+  public int getIntValue(String hdfname,
+      int default_value) {
+    return getHdf().getIntValue(hdfname, default_value);
+  }
+
+  public String getValue(String hdfname, String default_value) {
+    return getHdf().getValue(hdfname, default_value);
+  }
+
+  public void setValue(
+      String hdfname, String value) {
+    getHdf().setValue(hdfname, value);
+  }
+
+  public void removeTree(String hdfname) {
+    getHdf().removeTree(hdfname);
+  }
+
+  public void setSymLink(String hdf_name_src,
+      String hdf_name_dest) {
+    getHdf().setSymLink(hdf_name_src, hdf_name_dest);
+  }
+
+  public void exportDate(
+      String hdfname, TimeZone timeZone, Date date) {
+    getHdf().exportDate(hdfname, timeZone, date);
+  }
+
+  public void exportDate(
+      String hdfname, String tz, int tt) {
+    getHdf().exportDate(hdfname, tz, tt);
+  }
+
+  public DelegatedHdf getObj(String hdfpath) {
+    HDF hdf = getHdf().getObj(hdfpath);
+    return hdf != null ? newDelegatedHdf(hdf) : null;
+  }
+
+  public DelegatedHdf getChild(String hdfpath) {
+    HDF hdf = getHdf().getChild(hdfpath);
+    return hdf != null ? newDelegatedHdf(hdf) : null;
+  }
+
+  public DelegatedHdf getRootObj() {
+    HDF hdf = getHdf().getRootObj();
+    return hdf != null ? newDelegatedHdf(hdf) : null;
+  }
+
+  public boolean belongsToSameRoot(HDF hdf) {
+    return getFullyUnwrappedHdf(this).belongsToSameRoot(getFullyUnwrappedHdf(hdf));
+  }
+
+  public DelegatedHdf getOrCreateObj(String hdfpath) {
+    HDF hdf = getHdf().getOrCreateObj(hdfpath);
+    return hdf != null ? newDelegatedHdf(hdf) : null;
+  }
+
+  public String objName() {
+    return getHdf().objName();
+  }
+
+  public String objValue() {
+    return getHdf().objValue();
+  }
+
+  public DelegatedHdf objChild() {
+    HDF hdf = getHdf().objChild();
+    return hdf != null ? newDelegatedHdf(hdf) : null;
+  }
+
+  public DelegatedHdf objNext() {
+    HDF hdf = getHdf().objNext();
+    return hdf != null ? newDelegatedHdf(hdf) : null;
+  }
+
+  public void copy(String hdfpath, HDF src) {
+    if (src != null && src instanceof DelegatedHdf) {
+      src = ((DelegatedHdf)src).getHdf();
+    }
+    getHdf().copy(hdfpath, src);
+  }
+
+  public String dump() {
+    return getHdf().dump();
+  }
+}
+
diff --git a/src/org/clearsilver/FactoryLoader.java b/src/org/clearsilver/FactoryLoader.java
new file mode 100644
index 0000000..0d929c0
--- /dev/null
+++ b/src/org/clearsilver/FactoryLoader.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver;
+
+import java.lang.reflect.Constructor;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This class holds static methods for getting and setting the CS and HDF
+ * factory used throughout the Java Clearsilver Framework.
+ * Clients are <strong>strongly encouraged</strong> to not use this class, and
+ * instead directly inject {@link ClearsilverFactory} into the classes that
+ * need to create {@link HDF} and {@link CS} instances.
+ * For now, projects should set the {@link ClearsilverFactory} in FactoryLoader
+ * and use the singleton accessor {@link #getClearsilverFactory()} if proper
+ * dependency injection is not easy to implement.
+ * <p>
+ * Allows the default implementation to be the original JNI version without
+ * requiring users that don't want to use the JNI version to have to link
+ * it in.  The ClearsilverFactory object to use can be either passed into the
+ * {@link #setClearsilverFactory} method or the class name can be specified
+ * in the Java property {@code  org.clearsilver.defaultClearsilverFactory}.
+ */
+public final class FactoryLoader {
+  private static final Logger logger =
+      Logger.getLogger(FactoryLoader.class.getName());
+
+  private static final String DEFAULT_CS_FACTORY_CLASS_PROPERTY_NAME =
+      "org.clearsilver.defaultClearsilverFactory";
+  private static final String DEFAULT_CS_FACTORY_CLASS_NAME =
+      "org.clearsilver.jni.JniClearsilverFactory";
+
+  // ClearsilverFactory to be used when constructing objects.  Allows
+  // applications to subclass the CS and HDF objects used in Java Clearsilver
+  private static ClearsilverFactory clearsilverFactory = null;
+
+  // Read/Write lock for global factory pointer.
+  private static final ReadWriteLock factoryLock = new ReentrantReadWriteLock();
+
+  // Getters and setters
+  /**
+   * Get the {@link org.clearsilver.ClearsilverFactory} object to be used by
+   * disparate parts of the application.
+   */
+  public static ClearsilverFactory getClearsilverFactory() {
+    factoryLock.readLock().lock();
+    if (clearsilverFactory == null) {
+      factoryLock.readLock().unlock();
+      factoryLock.writeLock().lock();
+      try {
+        if (clearsilverFactory == null) {
+          clearsilverFactory = newDefaultClearsilverFactory();
+        }
+        factoryLock.readLock().lock();
+      } finally {
+        factoryLock.writeLock().unlock();
+      }
+    }
+    ClearsilverFactory returned = clearsilverFactory;
+    factoryLock.readLock().unlock();
+    return returned;
+  }
+
+  /**
+   * Set the {@link org.clearsilver.ClearsilverFactory} to be used by
+   * the application. If parameter is {@code null}, then the default factory
+   * implementation will be used the next time {@link #getClearsilverFactory()}
+   * is called.
+   *
+   * @return the previous factory (may return {@code null})
+   */
+  public static ClearsilverFactory setClearsilverFactory(
+      ClearsilverFactory clearsilverFactory) {
+    factoryLock.writeLock().lock();
+    try {
+      ClearsilverFactory previousFactory = FactoryLoader.clearsilverFactory;
+      FactoryLoader.clearsilverFactory = clearsilverFactory;
+      return previousFactory;
+    } finally {
+      factoryLock.writeLock().unlock();
+    }
+  }
+
+  private static ClearsilverFactory newDefaultClearsilverFactory() {
+    String factoryClassName =
+        System.getProperty(DEFAULT_CS_FACTORY_CLASS_PROPERTY_NAME,
+            DEFAULT_CS_FACTORY_CLASS_NAME);
+    try {
+      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+      Class<ClearsilverFactory> clazz =
+          loadClass(factoryClassName, classLoader);
+      Constructor<ClearsilverFactory> constructor = clazz.getConstructor();
+      return constructor.newInstance();
+    } catch (Exception e) {
+      String errMsg = "Unable to load default ClearsilverFactory class: \"" +
+          factoryClassName + "\"";
+      logger.log(Level.SEVERE, errMsg, e);
+      throw new RuntimeException(errMsg, e);
+    }
+  }
+
+  private static Class<ClearsilverFactory> loadClass(String className,
+      ClassLoader classLoader) throws ClassNotFoundException {
+    return (Class<ClearsilverFactory>) Class.forName(className, true,
+        classLoader);
+  }
+}
diff --git a/src/org/clearsilver/HDF.java b/src/org/clearsilver/HDF.java
new file mode 100644
index 0000000..1f70199
--- /dev/null
+++ b/src/org/clearsilver/HDF.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * This interface establishes the API for an HDF data structure used by
+ * Clearsilver templates when rendering content.
+ */
+public interface HDF extends Closeable {
+
+  /**
+   * Clean up CS object state.
+   */
+  void close();
+
+  /**
+   * Loads the contents of the specified HDF file from disk into the current
+   * HDF object.  The loaded contents are merged with the existing contents.
+   */
+  boolean readFile(String filename) throws IOException;
+
+  /**
+   * Get the file loader in use, if any.
+   * @return the file loader in use.
+   */
+  CSFileLoader getFileLoader();
+
+  /**
+   * Set the CS file loader to use
+   * @param fileLoader the file loader that should be used.
+   */
+  void setFileLoader(CSFileLoader fileLoader);
+
+  /**
+   * Serializes HDF contents to a file (readable by readFile)
+   */
+  boolean writeFile(String filename) throws IOException;
+
+  /**
+   * Parses/loads the contents of the given string as HDF into the current
+   * HDF object.  The loaded contents are merged with the existing contents.
+   */
+  boolean readString(String data);
+
+  /**
+   * Serializes HDF contents to a string (readable by readString)
+   */
+  String writeString();
+
+  /**
+   * Retrieves the integer value at the specified path in this HDF node's
+   * subtree.  If the value does not exist, or cannot be converted to an
+   * integer, default_value will be returned.
+   */
+  int getIntValue(String hdfName, int defaultValue);
+
+  /**
+   * Retrieves the value at the specified path in this HDF node's subtree.
+   */
+  String getValue(String hdfName, String defaultValue);
+
+  /**
+   * Sets the value at the specified path in this HDF node's subtree.
+   */
+  void setValue(String hdfName, String value);
+
+  /**
+   * Remove the specified subtree.
+   */
+  void removeTree(String hdfName);
+
+  /**
+   * Links the src hdf name to the dest.
+   */
+  void setSymLink(String hdfNameSrc, String hdfNameDest);
+
+  /**
+   * Export a date to a clearsilver tree using a specified timezone
+   */
+  void exportDate(String hdfName, TimeZone timeZone, Date date);
+
+  /**
+   * Export a date to a clearsilver tree using a specified timezone
+   */
+  void exportDate(String hdfName, String tz, int tt);
+
+  /**
+   * Retrieves the HDF object that is the root of the subtree at hdfpath, or
+   * null if no object exists at that path.
+   */
+  HDF getObj(String hdfpath);
+
+  /**
+   * Retrieves the HDF for the first child of the root of the subtree
+   * at hdfpath, or null if no child exists of that path or if the
+   * path doesn't exist.
+   */
+  HDF getChild(String hdfpath);
+
+  /**
+   * Return the root of the tree where the current node lies.  If the
+   * current node is the root, return this. Implementations may not
+   * necessarily return the same instance of {@code HDF} every time.
+   * Use {@link #belongsToSameRoot(HDF)} to check if two {@code HDF}s
+   * belong to the same root.
+   */
+  HDF getRootObj();
+
+  /**
+   * Checks if the given hdf object belongs to the same root HDF object
+   * as this one.
+   *
+   * @param hdf The hdf object to compare to.
+   * @throws IllegalArgumentException If the supplied hdf object is from
+   *         a different implementation (e.g. mixing JNI and jsilver).
+   */
+  boolean belongsToSameRoot(HDF hdf);
+
+  /**
+   * Retrieves the HDF object that is the root of the subtree at
+   * hdfpath, create the subtree if it doesn't exist
+   */
+  HDF getOrCreateObj(String hdfpath);
+
+  /**
+   * Returns the name of this HDF node.   The root node has no name, so
+   * calling this on the root node will return null.
+   */
+  String objName();
+
+  /**
+   * Returns the value of this HDF node, or null if this node has no value.
+   * Every node in the tree can have a value, a child, and a next peer.
+   */
+  String objValue();
+
+  /**
+   * Returns the child of this HDF node, or null if there is no child.
+   * Use this in conjunction with objNext to walk the HDF tree.  Every node
+   * in the tree can have a value, a child, and a next peer.
+   */
+  HDF objChild();
+
+  /**
+   * Returns the child of this HDF node, or null if there is no child.
+   * Use this in conjunction with objNext to walk the HDF tree.  Every node
+   * in the tree can have a value, a child, and a next peer.
+   */
+  HDF objNext();
+
+  /**
+   * Deep copy of the contents of the source HDF structure to this HDF
+   * starting at the specified HDF path node.
+   * <p>
+   * This method copies over the attributes and value of the node and recurses
+   * through all the children of the source node.  Any symlink in the source
+   * node becomes a symlink in the copy.
+   * <p>
+   * @param hdfpath the node within this HDF where the source structure should
+   * be copied to.
+   * @param src the source HDF to copy over.
+   */
+  void copy(String hdfpath, HDF src);
+
+  /**
+   * Generates a string representing the content of the HDF tree rooted at
+   * this node.
+   */
+  String dump();
+}
+
diff --git a/src/org/clearsilver/jni/JNI.java b/src/org/clearsilver/jni/JNI.java
new file mode 100644
index 0000000..6bc1e42
--- /dev/null
+++ b/src/org/clearsilver/jni/JNI.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver.jni;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/**
+ * Loads the ClearSilver JNI library.
+ *
+ * <p>By default, it attempts to load the library 'clearsilver-jni' from the
+ * path specified in the 'java.library.path' system property. However, this
+ * can be overriden by calling {@link #setLibraryName(String)} and
+ * {@link #setLibrarySearchPaths(String[])}.</p>
+ *
+ * <p>If this fails, the JVM exits with a code of 1. However, this strategy
+ * can be changed using {@link #setFailureCallback(Runnable)}.</p>
+ */
+public final class JNI {
+
+  /**
+   * Failure callback strategy that writes a message to sysout, then calls
+   * System.exit(1).
+   */
+  public static Runnable EXIT_JVM = new Runnable() {
+    public void run() {
+      System.err.println("Could not load '" + libraryName + "'. Searched:");
+      String platformLibraryName = System.mapLibraryName(libraryName);
+      for (String path : librarySearchPaths) {
+        System.err.println("  " +
+            new File(path, platformLibraryName).getAbsolutePath());
+      }
+      System.err.println(
+          "Try specifying -Djava.library.path=[directory] or calling "
+              + JNI.class.getName() + ".setLibrarySearchPaths(String...)");
+      System.exit(1);
+    }
+  };
+
+  /**
+   * Failure callback strategy that throws an UnsatisfiedLinkError, which
+   * should be caught be client code.
+   */
+  public static Runnable THROW_ERROR = new Runnable() {
+    public void run() {
+      throw new UnsatisfiedLinkError("Could not load '" + libraryName + "'");
+    }
+  };
+
+  private static Runnable failureCallback = EXIT_JVM;
+
+  private static Object callbackLock = new Object();
+
+  private static String libraryName = "clearsilver-jni";
+
+  private static String[] librarySearchPaths
+      = System.getProperty("java.library.path", ".").split(
+          Pattern.quote(File.pathSeparator));
+
+  private static volatile boolean successfullyLoadedLibrary;
+
+  /**
+   * Attempts to load the ClearSilver JNI library.
+   *
+   * @see #setFailureCallback(Runnable)
+   */
+  public static void loadLibrary() {
+
+    // Library already loaded? Great - nothing to do.
+    if (successfullyLoadedLibrary) {
+      return;
+    }
+
+    synchronized (callbackLock) {
+
+      // Search librarySearchPaths...
+      String platformLibraryName = System.mapLibraryName(libraryName);
+      for (String path : librarySearchPaths) {
+        try {
+          // Attempt to load the library in that path.
+          System.load(new File(path, platformLibraryName).getAbsolutePath());
+          // If we got here, it worked. We're done.
+          successfullyLoadedLibrary = true;
+          return;
+        } catch (UnsatisfiedLinkError e) {
+          // Library not found. Continue loop.
+        }
+      }
+
+      // Still here? Couldn't load library. Fail.
+      if (failureCallback != null) {
+        failureCallback.run();
+      }
+    }
+
+  }
+
+  /**
+   * Sets a callback for what should happen if the JNI library cannot
+   * be loaded. The default is {@link #EXIT_JVM}.
+   *
+   * @see #EXIT_JVM
+   * @see #THROW_ERROR
+   */
+  public static void setFailureCallback(Runnable failureCallback) {
+    synchronized(callbackLock) {
+      JNI.failureCallback = failureCallback;
+    }
+  }
+
+  /**
+   * Set name of JNI library to load. Default is 'clearsilver-jni'.
+   */
+  public static void setLibraryName(String libraryName) {
+    JNI.libraryName = libraryName;
+  }
+
+  /**
+   * Sets locations where JNI library is searched.
+   */
+  public static void setLibrarySearchPaths(String... paths) {
+    JNI.librarySearchPaths = paths;
+  }
+
+}
diff --git a/src/org/clearsilver/jni/JniClearsilverFactory.java b/src/org/clearsilver/jni/JniClearsilverFactory.java
new file mode 100644
index 0000000..bb12642
--- /dev/null
+++ b/src/org/clearsilver/jni/JniClearsilverFactory.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver.jni;
+
+import org.clearsilver.CS;
+import org.clearsilver.ClearsilverFactory;
+import org.clearsilver.DelegatedHdf;
+import org.clearsilver.HDF;
+
+/**
+ * Factory implementation for the original JNI version of Java Clearsilver
+ */
+public class JniClearsilverFactory implements ClearsilverFactory {
+
+  private final boolean unwrapDelegatedHdfs;
+
+  /**
+   * Default constructor. Any {@link org.clearsilver.DelegatedHdf}s passed to
+   * {@link #newCs} will be fully unwrapped before being passed to CS
+   * implementation constructor.
+   */
+  public JniClearsilverFactory() {
+    this(true);
+  }
+
+  /**
+   * Constructor that takes the option whether to unwrap all
+   * {@link org.clearsilver.DelegatedHdf} objects before passing the
+   * {@link org.clearsilver.HDF} object to the {@link org.clearsilver.CS}
+   * implementation constructor.
+   * <br>
+   * Developers that want strict checking that the HDF passed to newCs matches
+   * HDF objects constructed by newHDF may want to pass in {@code false}.
+   *
+   * @param unwrapDelegatedHdfs true if {@link org.clearsilver.HDF}s passed to
+   * {@link #newCs} should be unwrapped if they are
+   * {@link org.clearsilver.DelegatedHdf} objects, false otherwise.
+   */
+  public JniClearsilverFactory(boolean unwrapDelegatedHdfs) {
+    this.unwrapDelegatedHdfs = unwrapDelegatedHdfs;
+  }
+
+  /**
+  * Create a new CS object.
+  * @param hdf the HDF object to use in constructing the CS object.
+  * @return a new CS object
+  */
+  public CS newCs(HDF hdf) {
+    if (unwrapDelegatedHdfs) {
+      hdf = DelegatedHdf.getFullyUnwrappedHdf(hdf);
+    }
+    return new JniCs(JniHdf.cast(hdf));
+  }
+
+  /**
+   * Create a new CS object.  Also checks and unwraps any DelegatedHdfs
+   * passed into the method.
+   * @param hdf the HDF object to use in constructing the CS object.
+   * @param globalHdf the global HDF object to use in constructing the
+   * CS object.
+   * @return a new CS object
+   */
+  public CS newCs(HDF hdf, HDF globalHdf) {
+    if (unwrapDelegatedHdfs) {
+      hdf = DelegatedHdf.getFullyUnwrappedHdf(hdf);
+      globalHdf = DelegatedHdf.getFullyUnwrappedHdf(globalHdf);
+    }
+    return new JniCs(JniHdf.cast(hdf), JniHdf.cast(globalHdf));
+  }
+
+  /**
+   * Create a new HDF object.
+   * @return a new HDF object
+   */
+  public HDF newHdf() {
+    return new JniHdf();
+  }
+}
diff --git a/src/org/clearsilver/jni/JniCs.java b/src/org/clearsilver/jni/JniCs.java
new file mode 100644
index 0000000..15391a4
--- /dev/null
+++ b/src/org/clearsilver/jni/JniCs.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver.jni;
+
+import org.clearsilver.CS;
+import org.clearsilver.CSFileLoader;
+import org.clearsilver.HDF;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * JNI implementation of the CS interface.
+ */
+public class JniCs implements CS {
+  long csptr;
+
+  protected JniHdf globalHDF;
+  protected JniHdf localHDF;
+
+  static {
+    JNI.loadLibrary();
+  }
+
+  JniCs(JniHdf ho) {
+    this.globalHDF = null;
+    this.localHDF = ho;
+    csptr = _init(ho.hdfptr);
+  }
+
+  JniCs(JniHdf ho, JniHdf global) {
+    this(ho);
+
+    this.globalHDF = global;
+    if (global != null) {
+      _setGlobalHdf(csptr,global.hdfptr);
+    }
+  }
+
+  // Specify a new/different global HDF
+  public void setGlobalHDF(HDF global) {
+    JniHdf globalHdf = JniHdf.cast(global);
+    _setGlobalHdf(csptr, globalHdf.hdfptr);
+    this.globalHDF = globalHdf;
+  }
+
+  // Return global hdf in use
+  public HDF getGlobalHDF() {
+    return this.globalHDF;
+  }
+
+  public void close() {
+    if (csptr != 0) {
+      _dealloc(csptr);
+      csptr = 0;
+    }
+  }
+
+  // Don't rely on this being called.
+  protected void finalize() {
+    close();
+  }
+  
+  /**
+   * Parses the specified file as if it has template content. The file will
+   * be located using the HDF's loadpaths.
+   * @param filename the name of file to read in and parse.
+   * @throws java.io.FileNotFoundException if the specified file does not
+   * exist.
+   * @throws IOException other problems reading the file.
+   */
+  public void parseFile(String filename) throws IOException {
+    if (csptr == 0) {
+     throw new NullPointerException("CS is closed.");
+    }
+    _parseFile(csptr, filename, fileLoader != null);
+  }
+
+  public void parseStr(String content) {
+    if (csptr == 0) {
+      throw new NullPointerException("CS is closed.");
+    }
+    _parseStr(csptr,content);
+  }
+
+  public String render() {
+    if (csptr == 0) {
+      throw new NullPointerException("CS is closed.");
+    }
+    return _render(csptr, fileLoader != null);
+  }
+
+
+  protected String fileLoad(String filename) throws IOException,
+      FileNotFoundException {
+    if (csptr == 0) {
+      throw new NullPointerException("CS is closed.");
+    }
+    CSFileLoader aFileLoader = fileLoader;
+    if (aFileLoader == null) {
+      throw new NullPointerException("No fileLoader specified.");
+    } else {
+      String result = aFileLoader.load(localHDF, filename);
+      if (result == null) {
+        throw new NullPointerException("CSFileLoader.load() returned null");
+      }
+      return result;
+    }
+  }
+
+  // The optional CS file loader to use to read in files
+  private CSFileLoader fileLoader = null;
+
+  /**
+   * Get the file loader in use, if any.
+   * @return the file loader in use.
+   */
+  public CSFileLoader getFileLoader() {
+    return fileLoader;
+  }
+
+  /**
+   * Set the CS file loader to use
+   * @param fileLoader the file loader that should be used.
+   */
+  public void setFileLoader(CSFileLoader fileLoader) {
+    this.fileLoader = fileLoader;
+  }
+
+
+  // Native methods
+  private native long    _init(long ptr);
+  private native void   _dealloc(long ptr);
+  private native void   _parseFile(long ptr, String filename,
+      boolean use_cb) throws IOException;
+  private native void   _parseStr(long ptr, String content);
+  private native String _render(long ptr, boolean use_cb);
+  private native void   _setGlobalHdf(long csptr, long hdfptr);
+}
diff --git a/src/org/clearsilver/jni/JniHdf.java b/src/org/clearsilver/jni/JniHdf.java
new file mode 100644
index 0000000..f1f16aa
--- /dev/null
+++ b/src/org/clearsilver/jni/JniHdf.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.clearsilver.jni;
+
+import org.clearsilver.CSFileLoader;
+import org.clearsilver.HDF;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * This class is a wrapper around the HDF C API.  Many features of the C API
+ * are not yet exposed through this wrapper.
+ */
+public class JniHdf implements HDF {
+
+  long hdfptr;  // stores the C HDF* pointer
+  JniHdf root; // If this is a child HDF node, points at the root node of
+               // the tree.  For root nodes this is null.  A child node needs
+               // to hold a reference on the root to prevent the root from
+               // being GC-ed.
+
+  static {
+    JNI.loadLibrary();
+  }
+
+  static JniHdf cast(HDF hdf) {
+    if (!(hdf instanceof JniHdf)) {
+      throw new IllegalArgumentException("HDF object not of type JniHdf.  "
+          + "Make sure you use the same ClearsilverFactory to construct all "
+          + "related HDF and CS objects.");
+    }
+    return (JniHdf)hdf;
+  }
+
+  /**
+   * Default public constructor.
+   */
+  public JniHdf() {
+    hdfptr = _init();
+    root = null;
+  }
+
+  protected JniHdf(long hdfptr, JniHdf parent) {
+    this.hdfptr = hdfptr;
+    this.root = (parent.root != null) ? parent.root : parent;
+  }
+
+  /** Constructs an HDF child node.  Used by other methods in this class when
+   * a child node needs to be constructed.
+   */
+  protected JniHdf newHdf(long hdfptr, HDF parent) {
+    return new JniHdf(hdfptr, cast(parent));
+  }
+
+  /** Clean up allocated memory if neccesary. close() allows application
+   *  to force clean up.
+   */
+  public void close() {
+    // Only root nodes have ownership of the C HDF pointer, so only a root
+    // node needs to dealloc hdfptr.dir
+    if (root == null) {
+      if (hdfptr != 0) {
+        _dealloc(hdfptr);
+        hdfptr = 0;
+      }
+    }
+  }
+
+  /** Call close() just in case when deallocating Java object.
+   */
+  protected void finalize() throws Throwable {
+    close();
+    super.finalize();
+  }
+
+  /** Loads the contents of the specified HDF file from disk into the current
+   *  HDF object.  The loaded contents are merged with the existing contents.
+   *  @param filename the name of file to read in and parse.
+   *  @throws java.io.FileNotFoundException if the specified file does not
+   *  exist.
+   *  @throws IOException other problems reading the file.
+   */
+  public boolean readFile(String filename) throws IOException {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    return _readFile(hdfptr, filename, fileLoader != null);
+  }
+
+  protected String fileLoad(String filename) throws IOException {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    CSFileLoader aFileLoader = fileLoader;
+    if (aFileLoader == null) {
+      throw new NullPointerException("No fileLoader specified.");
+    } else {
+      String result = aFileLoader.load(this, filename);
+      if (result == null) {
+        throw new NullPointerException("CSFileLoader.load() returned null");
+      }
+      return result;
+    }
+  }
+
+  // The optional CS file loader to use to read in files
+  private CSFileLoader fileLoader = null;
+
+  /**
+   * Get the file loader in use, if any.
+   * @return the file loader in use.
+   */
+  public CSFileLoader getFileLoader() {
+    return fileLoader;
+  }
+
+  /**
+   * Set the CS file loader to use
+   * @param fileLoader the file loader that should be used.
+   */
+  public void setFileLoader(CSFileLoader fileLoader) {
+    this.fileLoader = fileLoader;
+  }
+
+  /** Serializes HDF contents to a file (readable by readFile)
+   */
+  public boolean writeFile(String filename) throws IOException {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    return _writeFile(hdfptr, filename);
+  }
+
+  /** Parses/loads the contents of the given string as HDF into the current
+   *  HDF object.  The loaded contents are merged with the existing contents.
+   */
+  public boolean readString(String data) {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    return _readString(hdfptr, data);
+  }
+
+  /** Serializes HDF contents to a string (readable by readString)
+   */
+  public String writeString() {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    return _writeString(hdfptr);
+  }
+
+  /** Retrieves the integer value at the specified path in this HDF node's
+   *  subtree.  If the value does not exist, or cannot be converted to an
+   *  integer, default_value will be returned. */
+  public int getIntValue(String hdfname, int default_value) {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    return _getIntValue(hdfptr,hdfname,default_value);
+  }
+
+  /** Retrieves the value at the specified path in this HDF node's subtree.
+   */
+  public String getValue(String hdfname, String default_value) {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    return _getValue(hdfptr,hdfname,default_value);
+  }
+
+  /** Sets the value at the specified path in this HDF node's subtree. */
+  public void setValue(String hdfname, String value) {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    _setValue(hdfptr,hdfname,value);
+  }
+
+  /** Remove the specified subtree. */
+  public void removeTree(String hdfname) {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    _removeTree(hdfptr,hdfname);
+  }
+
+  /** Links the src hdf name to the dest. */
+  public void setSymLink(String hdf_name_src, String hdf_name_dest) {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    _setSymLink(hdfptr,hdf_name_src,hdf_name_dest);
+  }
+
+  /** Export a date to a clearsilver tree using a specified timezone */
+  public void exportDate(String hdfname, TimeZone timeZone, Date date) {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+
+    Calendar cal = Calendar.getInstance(timeZone);
+    cal.setTime(date);
+
+    String sec = Integer.toString(cal.get(Calendar.SECOND));
+    setValue(hdfname + ".sec", sec.length() == 1 ? "0" + sec : sec);
+
+    String min = Integer.toString(cal.get(Calendar.MINUTE));
+    setValue(hdfname + ".min", min.length() == 1 ? "0" + min : min);
+
+    setValue(hdfname + ".24hour",
+        Integer.toString(cal.get(Calendar.HOUR_OF_DAY)));
+    // java.util.Calendar uses represents 12 o'clock as 0
+    setValue(hdfname + ".hour",
+        Integer.toString(
+            cal.get(Calendar.HOUR) == 0 ? 12 : cal.get(Calendar.HOUR)));
+    setValue(hdfname + ".am",
+        cal.get(Calendar.AM_PM) == Calendar.AM ? "1" : "0");
+    setValue(hdfname + ".mday",
+        Integer.toString(cal.get(Calendar.DAY_OF_MONTH)));
+    setValue(hdfname + ".mon",
+        Integer.toString(cal.get(Calendar.MONTH)+1));
+    setValue(hdfname + ".year",
+        Integer.toString(cal.get(Calendar.YEAR)));
+    setValue(hdfname + ".2yr",
+        Integer.toString(cal.get(Calendar.YEAR)).substring(2));
+
+    // Java DAY_OF_WEEK puts Sunday .. Saturday as 1 .. 7 respectively
+    // See http://java.sun.com/j2se/1.5.0/docs/api/java/util/Calendar.html#DAY_OF_WEEK
+    // However, C and Python export Sun .. Sat as 0 .. 6, because
+    // POSIX localtime_r produces wday 0 .. 6.  So, adjust.
+    setValue(hdfname + ".wday",
+        Integer.toString(cal.get(Calendar.DAY_OF_WEEK) - 1));
+
+    boolean tzNegative = timeZone.getRawOffset() < 0;
+    int tzAbsolute = java.lang.Math.abs(timeZone.getRawOffset()/1000);
+    String tzHour = Integer.toString(tzAbsolute/3600);
+    String tzMin = Integer.toString(tzAbsolute/60 - (tzAbsolute/3600)*60);
+    String tzString = (tzNegative ? "-" : "+")
+        + (tzHour.length() == 1 ? "0" + tzHour : tzHour)
+        + (tzMin.length() == 1 ? "0" + tzMin : tzMin);
+    setValue(hdfname + ".tzoffset", tzString);
+  }
+
+  /** Export a date to a clearsilver tree using a specified timezone */
+  public void exportDate(String hdfname, String tz, int tt) {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+
+    TimeZone timeZone = TimeZone.getTimeZone(tz);
+
+    if (timeZone == null) {
+      throw new RuntimeException("Unknown timezone: " + tz);
+    }
+
+    Date date = new Date((long)tt * 1000);
+
+    exportDate(hdfname, timeZone, date);
+  }
+
+  /** Retrieves the HDF object that is the root of the subtree at hdfpath, or
+   *  null if no object exists at that path. */
+  public JniHdf getObj(String hdfpath) {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    long obj_ptr = _getObj(hdfptr, hdfpath);
+    if ( obj_ptr == 0 ) {
+      return null;
+    }
+    return newHdf(obj_ptr, this);
+  }
+
+  /** Retrieves the HDF for the first child of the root of the subtree
+   *  at hdfpath, or null if no child exists of that path or if the
+   *  path doesn't exist. */
+  public JniHdf getChild(String hdfpath) {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    long obj_ptr = _getChild(hdfptr, hdfpath);
+    if ( obj_ptr == 0 ) {
+      return null;
+    }
+    return newHdf(obj_ptr, this);
+  }
+
+  /** Return the root of the tree where the current node lies.  If the
+   *  current node is the root, return this. */
+  public JniHdf getRootObj() {
+    return root != null ? root : this;
+  }
+
+  public boolean belongsToSameRoot(HDF hdf) {
+    JniHdf jniHdf = cast(hdf);
+    return this.getRootObj() == jniHdf.getRootObj();
+  }
+
+  /** Retrieves the HDF object that is the root of the subtree at
+   *  hdfpath, create the subtree if it doesn't exist */
+  public JniHdf getOrCreateObj(String hdfpath) {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    long obj_ptr = _getObj(hdfptr, hdfpath);
+    if ( obj_ptr == 0 ) {
+      // Create a node
+      _setValue(hdfptr, hdfpath, "");
+      obj_ptr = _getObj( hdfptr, hdfpath );
+      if ( obj_ptr == 0 ) {
+        return null;
+      }
+    }
+    return newHdf(obj_ptr, this);
+  }
+
+  /** Returns the name of this HDF node.   The root node has no name, so
+   *  calling this on the root node will return null. */
+  public String objName() {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    return _objName(hdfptr);
+  }
+
+  /** Returns the value of this HDF node, or null if this node has no value.
+   *  Every node in the tree can have a value, a child, and a next peer. */
+  public String objValue() {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    return _objValue(hdfptr);
+  }
+
+  /** Returns the child of this HDF node, or null if there is no child.
+   *  Use this in conjunction with objNext to walk the HDF tree.  Every node
+   *  in the tree can have a value, a child, and a next peer.
+   */
+  public JniHdf objChild() {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    long child_ptr = _objChild(hdfptr);
+    if ( child_ptr == 0 ) {
+      return null;
+    }
+    return newHdf(child_ptr, this);
+  }
+
+  /** Returns the next sibling of this HDF node, or null if there is no next
+   *  sibling.  Use this in conjunction with objChild to walk the HDF tree.
+   *  Every node in the tree can have a value, a child, and a next peer.
+   */
+  public JniHdf objNext() {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    long next_ptr = _objNext(hdfptr);
+    if ( next_ptr == 0 ) {
+      return null;
+    }
+    return newHdf(next_ptr, this);
+  }
+
+  public void copy(String hdfpath, HDF src) {
+    JniHdf source = cast(src);
+    if (hdfptr == 0 || source.hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    _copy(hdfptr, hdfpath, source.hdfptr);
+  }
+
+  /**
+   * Generates a string representing the content of the HDF tree rooted at
+   * this node.
+   */
+  public String dump() {
+    if (hdfptr == 0) {
+      throw new NullPointerException("HDF is closed.");
+    }
+    return _dump(hdfptr);
+  }
+
+  private static native long _init();
+  private static native void _dealloc(long ptr);
+  private native boolean _readFile(long ptr, String filename, boolean use_cb)
+      throws IOException;
+  private static native boolean _writeFile(long ptr, String filename);
+  private static native boolean _readString(long ptr, String data);
+  private static native String _writeString(long ptr);
+  private static native int _getIntValue(long ptr, String hdfname,
+      int default_value);
+  private static native String _getValue(long ptr, String hdfname,
+      String default_value);
+  private static native void _setValue(long ptr, String hdfname,
+      String hdf_value);
+  private static native void _removeTree(long ptr, String hdfname);
+  private static native void _setSymLink(long ptr, String hdf_name_src,
+      String hdf_name_dest);
+  private static native long _getObj(long ptr, String hdfpath);
+  private static native long _getChild(long ptr, String hdfpath);
+  private static native long _objChild(long ptr);
+  private static native long _objNext(long ptr);
+  private static native String _objName(long ptr);
+  private static native String _objValue(long ptr);
+  private static native void _copy(long destptr, String hdfpath, long srcptr);
+
+  private static native String _dump(long ptr);
+}