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 <cs var:name ?>").
+ * @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 <cs escape> 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 <cs set> or <cs
+ * call> 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 <cs escape> 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 <cs var:name ?>").
+ * @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)));
+ }
+
+ /**
+ * <?cs var:blah > 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);
+ }
+
+ /**
+ * <?cs uvar:blah > 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())));
+ }
+
+ /**
+ * <?cs set:x='y' > 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)));
+ }
+ }
+
+ /**
+ * <?cs name:blah > 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)));
+ }
+
+ /**
+ * <?cs if:blah > ... <?cs else > ... <?cs /if > 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();
+ }
+
+ /**
+ * <?cs each:x=Stuff > ... <?cs /each > 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());
+ }
+
+ /**
+ * <?cs with:x=Something > ... <?cs /with > 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();
+ }
+
+ /**
+ * <?cs loop:10 > ... <?cs /loop > 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());
+ }
+
+ /**
+ * <?cs loop:0,10 > ... <?cs /loop > 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());
+ }
+
+ /**
+ * <?cs loop:0,10,2 > ... <?cs /loop > 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"));
+ }
+
+ /**
+ * <?cs alt:someValue > ... <?cs /alt > 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();
+ }
+ }
+
+ /**
+ * <?cs escape:'html' > 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"));
+
+ }
+
+ /**
+ * <?cs linclude:'somefile.cs' > 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));
+ }
+
+ /**
+ * <?cs linclude:'somefile.cs' > 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));
+ }
+
+ /**
+ * <?cs include!'somefile.cs' > 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));
+ }
+
+ /**
+ * <?cs include:'somefile.cs' > 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));
+ }
+
+ /**
+ * <?cs lvar:blah > command. Evaluate expression and execute commands within.
+ */
+ @Override
+ public void caseALvarCommand(ALvarCommand node) {
+ capturePosition(node.getPosition());
+ evaluateVariable(node.getExpression(), "[lvar expression]");
+ }
+
+ /**
+ * <?cs evar:blah > 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));
+ }
+
+ /**
+ * <?cs def:someMacro(x,y) > ... <?cs /def > 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();
+ }
+
+ /**
+ * <?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 <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 <cs var:my_function(x, y) >
+ */
+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 <?cs escape ?> command.
+ *
+ * @param name The name with which <?cs escape ?> 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: + - * / % ? ! && || == != < > <= >=, 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 =
+ {"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "", "!", """, "#", "$", "%", "&", "'",
+ "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+ ":", ";", "<", "=", ">", "?", "@", "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 & 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: <a href=<?cs
+ * var: uri ?>>
+ */
+ 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 "<";
+ case '>':
+ return ">";
+ case '&':
+ return "&";
+ 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 > 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 >= 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 < 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 <= 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 <cs var:name >"
+ * @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());
+ }
+
+ /**
+ * <?cs var:blah > 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);
+ }
+
+ /**
+ * <?cs uvar:blah > 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());
+ }
+
+ /**
+ * <?cs lvar:blah > command. Evaluate expression and execute commands within.
+ */
+ @Override
+ public void caseALvarCommand(ALvarCommand node) {
+ setLastPosition(node.getPosition());
+ evaluateVariable(node.getExpression(), "[lvar expression]");
+ }
+
+ /**
+ * <?cs evar:blah > 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());
+ }
+ }
+
+ /**
+ * <?cs linclude!'somefile.cs' > 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);
+ }
+
+ /**
+ * <?cs linclude:'somefile.cs' > 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);
+ }
+
+ /**
+ * <?cs include!'somefile.cs' > command. Throw an error if file does not exist.
+ */
+ @Override
+ public void caseAHardIncludeCommand(AHardIncludeCommand node) {
+ setLastPosition(node.getPosition());
+ include(node.getExpression(), false);
+ }
+
+ /**
+ * <?cs include:'somefile.cs' > command. Silently ignore if the included file does not
+ * exist.
+ */
+ @Override
+ public void caseAIncludeCommand(AIncludeCommand node) {
+ setLastPosition(node.getPosition());
+ include(node.getExpression(), true);
+ }
+
+ /**
+ * <?cs set:x='y' > 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);
+ }
+ }
+
+ /**
+ * <?cs name:blah > 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());
+ }
+ }
+
+ /**
+ * <?cs if:blah > ... <?cs else > ... <?cs /if > 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);
+ }
+ }
+
+
+ /**
+ * <?cs escape:'html' > 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();
+ }
+
+ /**
+ * <?cs with:x=Something > ... <?cs /with > 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();
+ }
+
+ /**
+ * <?cs loop:10 > ... <?cs /loop > 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());
+ }
+
+ /**
+ * <?cs loop:0,10 > ... <?cs /loop > 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());
+ }
+
+ /**
+ * <?cs loop:0,10,2 > ... <?cs /loop > 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());
+ }
+
+ /**
+ * <?cs each:x=Stuff > ... <?cs /each > 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());
+ }
+ }
+ }
+
+ /**
+ * <?cs alt:someValue > ... <?cs /alt > 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
+
+ /**
+ * <?cs def:someMacro(x,y) > ... <?cs /def > 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();
+ }
+
+ /**
+ * <?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 <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 <?cs content-type: "content type" ?> 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 <?cs
+ * content-type: ?> command is not required for all javascript and css templates. If the
+ * template contains a <script> or <style> 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 <script src= > or
+ * <link rel > command, the explicit <?cs content-type: ?> 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. "<?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>
+ * <cs? var:a + b ?>
+ * </pre>
+ * with:
+ *
+ * <pre>
+ * <cs? var:a ?><cs? var:b ?>
+ * </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>
+ * <cs? var:html_escape(foo) ?>
+ * </pre>
+ * are turned into:
+ *
+ * <pre>
+ * <cs? escape:"html" ?>
+ * <cs? var:foo ?>
+ * <?cs /escape ?>
+ * </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>
+ * <cs? html_escape(foo + bar) + baz ?>
+ * </pre>
+ * which is turned into:
+ *
+ * <pre>
+ * <cs? escape:"html" ?>
+ * <cs? var:foo ?>
+ * <cs? var:bar ?>
+ * <?cs /escape ?>
+ * <?cs var:baz ?>
+ * </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>
+ * <cs? escape:"html" ?>
+ * <cs? var:url_escape(foo) ?>
+ * <?cs /escape ?>
+ * </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>
+ * <cs? escape:"html" ?>
+ * <cs? escape:"url" ?>
+ * <cs? var:foo ?>
+ * <?cs /escape ?>
+ * <?cs /escape ?>
+ * </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>
+ * <cs? escape:"html" ?>
+ * <cs? var:url_validate(foo) ?>
+ * <?cs /escape ?>
+ * </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>
+ * <cs? var:foo_escape(bar) ?>
+ * </pre>
+ * with:
+ *
+ * <pre>
+ * <cs? escape:"foo" ?><cs? var:bar ?><?cs /escape ?>
+ * </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 <cs var:name >"
+ * @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 <?cs escape > 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><a href="/bla" ["INCLUDED_TEMPLATE"]></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 "<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>&dd;</code> where dd is a number in decimal (base 10) form.
+ * <li><code>&x|Xyy;</code> where yy is a hex-number (base 16).
+ * <li><code>&<html-entity>;</code> where
+ * <code><html-entity></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("<", "<")
+ .put(">", ">")
+ .put("&", "&")
+ .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>&#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>
+ * <meta http-equiv="refresh" content="5; URL=http://www.google.com">
+ * </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>
+ * <meta http-equiv="refresh" content="5; URL=">
+ * </pre>
+ * <li>Example of a complete {@code meta} tag where the {@code content}
+ * attribute does not contain a URL:
+ * <pre>
+ * <meta http-equiv="content-type" content="text/html">
+ * </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);
+}