diff options
author | Ben Dodson <bjdodson@google.com> | 2010-08-17 13:42:20 -0700 |
---|---|---|
committer | Ben Dodson <bjdodson@google.com> | 2010-08-17 13:42:20 -0700 |
commit | 712c262ddf6790c16a8567053f616c71da7e1856 (patch) | |
tree | 2e50f392d10cfcaa37d825d2766bc651d4a14fa4 | |
parent | b175b218264f1f4bd254151612e4c3309e240564 (diff) | |
download | jsilver-712c262ddf6790c16a8567053f616c71da7e1856.tar.gz |
Adding JSilver codebase
Change-Id: I20291c251e147a85bd4cd37163abecd3456776f1
328 files changed, 47914 insertions, 0 deletions
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..4f9b8f3 --- /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 = "/" + cls.getPackage().getName().replace('.', '/'); + } + + /** + * 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/ClassResourceLoader.java~ b/src/com/google/clearsilver/jsilver/resourceloader/ClassResourceLoader.java~ new file mode 100644 index 0000000..63eb4a6 --- /dev/null +++ b/src/com/google/clearsilver/jsilver/resourceloader/ClassResourceLoader.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.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 = "/" + cls.getPackage().getName().replace('.', '/'); + } + + /** + * 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 { + System.out.println("FRM " + basePath); + 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 Binary files differnew file mode 100644 index 0000000..469c2d5 --- /dev/null +++ b/src/com/google/clearsilver/jsilver/syntax/lexer/lexer.dat 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 Binary files differnew file mode 100644 index 0000000..8e87199 --- /dev/null +++ b/src/com/google/clearsilver/jsilver/syntax/parser/parser.dat 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); +} |