Repository: freemarker Updated Branches: refs/heads/3 056635cd6 -> e8e58ffa5
http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java index 644ab25..df61532 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/DefaultTemplateLanguage.java @@ -41,29 +41,37 @@ public final class DefaultTemplateLanguage extends TemplateLanguage { * from the {@link Configuration} (or {@link TemplateConfiguration}). Avoid it, as it's problematic for tooling. */ public static final DefaultTemplateLanguage F3AC = new DefaultTemplateLanguage("f3ac", true, + DefaultDialect.INSTANCE, null, null, TagSyntax.ANGLE_BRACKET, InterpolationSyntax.DOLLAR); public static final DefaultTemplateLanguage F3SC = new DefaultTemplateLanguage("f3sc", true, + DefaultDialect.INSTANCE, null, null, TagSyntax.SQUARE_BRACKET, InterpolationSyntax.SQUARE_BRACKET); public static final DefaultTemplateLanguage F3AH = new DefaultTemplateLanguage("f3ah", true, + DefaultDialect.INSTANCE, HTMLOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT, TagSyntax.ANGLE_BRACKET, InterpolationSyntax.DOLLAR); public static final DefaultTemplateLanguage F3AX = new DefaultTemplateLanguage("f3ax", true, + DefaultDialect.INSTANCE, XMLOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT, TagSyntax.ANGLE_BRACKET, InterpolationSyntax.DOLLAR); public static final DefaultTemplateLanguage F3AU = new DefaultTemplateLanguage("f3au", true, + DefaultDialect.INSTANCE, UndefinedOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT, TagSyntax.ANGLE_BRACKET, InterpolationSyntax.DOLLAR); public static final DefaultTemplateLanguage F3SH = new DefaultTemplateLanguage("f3sh", true, + DefaultDialect.INSTANCE, HTMLOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT, TagSyntax.SQUARE_BRACKET, InterpolationSyntax.SQUARE_BRACKET); public static final DefaultTemplateLanguage F3SX = new DefaultTemplateLanguage("f3sx", true, + DefaultDialect.INSTANCE, XMLOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT, TagSyntax.SQUARE_BRACKET, InterpolationSyntax.SQUARE_BRACKET); public static final DefaultTemplateLanguage F3SU = new DefaultTemplateLanguage("f3su", true, + DefaultDialect.INSTANCE, UndefinedOutputFormat.INSTANCE, AutoEscapingPolicy.ENABLE_IF_DEFAULT, TagSyntax.SQUARE_BRACKET, InterpolationSyntax.SQUARE_BRACKET); @@ -81,11 +89,11 @@ public final class DefaultTemplateLanguage extends TemplateLanguage { * call this yourself; use constants like {@link #F3AH} instead when possible. * * @param fileExtension - * See in {@link TemplateLanguage#TemplateLanguage(String, OutputFormat, AutoEscapingPolicy)} + * See in {@link TemplateLanguage#TemplateLanguage(String, Dialect, OutputFormat, AutoEscapingPolicy)} * @param outputFormat - * See in {@link TemplateLanguage#TemplateLanguage(String, OutputFormat, AutoEscapingPolicy)} + * See in {@link TemplateLanguage#TemplateLanguage(String, Dialect, OutputFormat, AutoEscapingPolicy)} * @param autoEscapingPolicy - * See in {@link TemplateLanguage#TemplateLanguage(String, OutputFormat, AutoEscapingPolicy)} + * See in {@link TemplateLanguage#TemplateLanguage(String, Dialect, OutputFormat, AutoEscapingPolicy)} * @param tagSyntax * The tag syntax used; not {@code null}. * @param interpolationSyntax @@ -93,20 +101,21 @@ public final class DefaultTemplateLanguage extends TemplateLanguage { */ public DefaultTemplateLanguage( String fileExtension, + Dialect dialect, OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy, TagSyntax tagSyntax, InterpolationSyntax interpolationSyntax) { - this(fileExtension, false, outputFormat, autoEscapingPolicy, tagSyntax, interpolationSyntax); + this(fileExtension, false, dialect, outputFormat, autoEscapingPolicy, tagSyntax, interpolationSyntax); } /** - * Used internally to allow extensions starting with "f" + * Used internally to allow extensions starting with "f" (as those are reserved for FreeMarker) */ DefaultTemplateLanguage( - String fileExtension, - boolean allowExtensionStartingWithF, + String fileExtension, boolean allowExtensionStartingWithF, + Dialect dialect, OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy, TagSyntax tagSyntax, InterpolationSyntax interpolationSyntax) { - super(fileExtension, allowExtensionStartingWithF, outputFormat, autoEscapingPolicy); + super(fileExtension, allowExtensionStartingWithF, dialect, outputFormat, autoEscapingPolicy); _NullArgumentException.check("tagSyntax", tagSyntax); _NullArgumentException.check("interpolationSyntax", interpolationSyntax); this.tagSyntax = tagSyntax; http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/Dialect.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Dialect.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Dialect.java new file mode 100644 index 0000000..056bce5 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Dialect.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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.apache.freemarker.core; + +/** + * A {@linkplain TemplateLanguage template language} dialect; specifies what predefined callables (like directives, + * built-ins) are available in the template language, but it doesn't specify the syntax. Unlike callables exposed to + * templates otherwise, callables that are part of the dialect use the same syntax as the callables traditionally + * predefined ones (like {@code <#if ...>}, {@code ?upperCase}", etc.), and they are linked statically (see + * {@link StaticallyLinkedNamespaceEntry} for the advantages). + * + * <p> + * A {@link Dialect} object is usually a static singleton. If you need internal state that's bound to a + * {@link Configuration} instance, you can do that in the {@link ConfiguredDialect} created by + * {@link Dialect#createConfiguredDialect(Configuration)} object. + * + * @see StaticallyLinkedNamespaceEntry + */ +//TODO [FM3] will be public. Also, then move it into core.dialect? +abstract class Dialect { + + private final String name; + private final Version version; + + public Dialect(String name, Version version) { + this.name = name; + this.version = version; + } + + /** + * The informal name of this dialect (maybe used in error messages) + */ + public String getName() { + return name; + } + + /** + * The version number of this dialect (maybe used in error messages). + */ + public Version getVersion() { + return version; + } + + /** + * Creates a {@link ConfiguredDialect} object that's bound to the parameter {@link Configuration} object. FreeMarker + * calls this method of each {@link Dialect} that will be used with the {@link Configuration} when the + * {@link Configuration} object is constructed. + * + * @param cfg + * The fully initialized {@link Configuration}, except that some {@link Dialect}-s in it may not have an + * associated {@link ConfiguredDialect} yet. This {@link Configuration} is not yet "safely published" + * (see in the Java Memory Model), so it must not be exposed to other threads by this method. + * + * @throws ConfigurationException + * If any problem occurs, it should be wrapped into this. + */ + public abstract ConfiguredDialect createConfiguredDialect(Configuration cfg) throws ConfigurationException; + + /** + * A {@link Dialect} that is bound to a {@link Configuration}. While a dialect is in principle a static singleton, + * and so is independent of any particular {@link Configuration} object, some dialects may have internal state + * initialized depending on some {@link Configuration} settings. + * + * <p> + * Instances should be created by {@link Dialect#createConfiguredDialect(Configuration)}. The concrete + * {@link ConfiguredDialect} is usually private and is a nested class in the concrete {@link Dialect}, which + * overrides {@link Dialect#createConfiguredDialect(Configuration)} to return the concrete instance + * + * <p> + * The main function of a {@link ConfiguredDialect} is to expose the namespace of the dialect. The dialect namespace + * contains the entries that are statically linked, and can usually can be accessed without any namespace prefix ( + * usually, as the exact details depend on the template language). For example, for the default dialect the + * namespace contains entries like "if", "list", "upperCase", and so on. + */ + //TODO [FM3] will be public. Also, then move it into core.dialect? + abstract class ConfiguredDialect { + + /** + * Returns a namespace entry of this dialect. or {@code null} if there's no match. + */ + public abstract StaticallyLinkedNamespaceEntry getNamespaceEntry(String name); + + /** + * To iterate through the namespace entries of this dialect. + */ + public abstract Iterable<StaticallyLinkedNamespaceEntry> getNamespaceEntries(); + + /** + * Returns the {@link Dialect} whose {@link Dialect#createConfiguredDialect(Configuration)} method has created this + * instance. + */ + public final Dialect getDialect() { + return Dialect.this; + } + + } + + // Final implementation, as {@link Dialect}-s maybe used as hash keys. + @Override + public final int hashCode() { + return super.hashCode(); + } + + // Final implementation, as {@link Dialect}-s maybe used as hash keys. + @Override + public final boolean equals(Object obj) { + return super.equals(obj); + } + +} http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java index 38a4f17..637901e 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java @@ -63,6 +63,8 @@ import org.apache.freemarker.core.model.TemplateNumberModel; import org.apache.freemarker.core.model.TemplateSequenceModel; import org.apache.freemarker.core.model.TemplateStringModel; import org.apache.freemarker.core.model.impl.SimpleHash; +import org.apache.freemarker.core.outputformat.MarkupOutputFormat; +import org.apache.freemarker.core.outputformat.OutputFormat; import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException; import org.apache.freemarker.core.templateresolver.TemplateResolver; import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat; @@ -70,6 +72,7 @@ import org.apache.freemarker.core.util.CallableUtils; import org.apache.freemarker.core.util.StringToIndexMap; import org.apache.freemarker.core.util._DateUtils; import org.apache.freemarker.core.util._DateUtils.DateToISO8601CalendarFactory; +import org.apache.freemarker.core.util._NullArgumentException; import org.apache.freemarker.core.util._NullWriter; import org.apache.freemarker.core.util._StringUtils; import org.apache.freemarker.core.valueformat.TemplateDateFormat; @@ -280,7 +283,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen clearCachedValues(); try { doAutoImportsAndIncludes(this); - visit(getMainTemplate().getRootASTNode()); + executeElement(getMainTemplate().getRootASTNode()); // It's here as we must not flush if there was an exception. if (getAutoFlush()) { out.flush(); @@ -366,17 +369,18 @@ public final class Environment extends MutableProcessingConfiguration<Environmen /** * "Visit" the template element. */ - void visit(ASTElement element) throws IOException, TemplateException { + // TODO [FM3] will be public + void executeElement(ASTElement element) throws IOException, TemplateException { // ATTENTION: This method body is manually "inlined" into visit(ASTElement[]); keep them in sync! pushElement(element); try { - ASTElement[] templateElementsToVisit = element.accept(this); + ASTElement[] templateElementsToVisit = element.execute(this); if (templateElementsToVisit != null) { for (ASTElement el : templateElementsToVisit) { if (el == null) { break; // Skip unused trailing buffer capacity } - visit(el); + executeElement(el); } } } catch (TemplateException te) { @@ -386,12 +390,20 @@ public final class Environment extends MutableProcessingConfiguration<Environmen } // ATTENTION: This method body above is manually "inlined" into visit(ASTElement[]); keep them in sync! } + + // TODO [FM3] will be public + final void executeNestedContent(ASTDirective parentCall) throws IOException, TemplateException { + executeElements(parentCall.getChildBuffer()); + } /** + * Executes the elements passed in (which is usually the return value of {@link ASTElement#getChildBuffer()}). + * * @param elementBuffer * The elements to visit; might contains trailing {@code null}-s. Can be {@code null}. */ - final void visit(ASTElement[] elementBuffer) throws IOException, TemplateException { + // TODO [FM3] will be public + final void executeElements(ASTElement[] elementBuffer) throws IOException, TemplateException { if (elementBuffer == null) { return; } @@ -404,13 +416,13 @@ public final class Environment extends MutableProcessingConfiguration<Environmen // We don't just let Hotspot to do it, as we want a hard guarantee regarding maximum stack usage. pushElement(element); try { - ASTElement[] templateElementsToVisit = element.accept(this); + ASTElement[] templateElementsToVisit = element.execute(this); if (templateElementsToVisit != null) { for (ASTElement el : templateElementsToVisit) { if (el == null) { break; // Skip unused trailing buffer capacity } - visit(el); + executeElement(el); } } } catch (TemplateException te) { @@ -429,13 +441,27 @@ public final class Environment extends MutableProcessingConfiguration<Environmen private static final TemplateModel[] NO_OUT_ARGS = new TemplateModel[0]; - void visit( - ASTElement[] childBuffer, - final StringToIndexMap nestedContentParamNames, final TemplateModel[] nestedContentParamValues, - Writer out) - throws IOException, TemplateException { + /** + * Executes the nested content of a {@link ASTDirective}. + * + * @param directiveCall + * This is the directive call AST node whose nested content we will execute. + * @param nestedContentParamNames + * The names of the nested content parameters as they were declared by the directive call. For example + * {@code code <#list m as k, v>...</#list>}, a call of the {@code list} directive, declares "k" at index + * 0, and "v" at index 1. + * @param nestedContentParamValues + * The actual values of the nested content parameters, which will be visible for the nested content as + * variables. The caller of this method <em>must</em> ensure that {@code nestedContentParamNames} doesn't + * return an index that's out of bounds in {@code nestedContentParamValues}! + */ + // TODO [FM3] will be public + void executeNestedContent( + ASTDirective directiveCall, + final StringToIndexMap nestedContentParamNames, final TemplateModel[] nestedContentParamValues) throws IOException, TemplateException { + ASTElement[] childBuffer = directiveCall.getChildBuffer(); if (nestedContentParamNames == null) { // This is by far the most frequent case - visit(childBuffer, out); + executeElements(childBuffer); } else { pushLocalContext(new LocalContext() { @Override @@ -450,24 +476,13 @@ public final class Environment extends MutableProcessingConfiguration<Environmen } }); try { - visit(childBuffer, out); + executeElements(childBuffer); } finally { popLocalContext(); } } } - void visit(ASTElement[] childBuffer, Writer out) throws IOException, TemplateException { - // TODO [FM][CF] The plan is that `out` will be the root read only sink, so then it will work differently - Writer prevOut = this.out; - this.out = out; - try { - visit(childBuffer); - } finally { - this.out = prevOut; - } - } - /** * Visit a block using buffering/recovery */ @@ -482,7 +497,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen boolean lastInAttemptBlock = inAttemptBlock; try { inAttemptBlock = true; - visit(attemptedSection); + executeElement(attemptedSection); } catch (TemplateException te) { thrownException = te; } finally { @@ -493,7 +508,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen if (thrownException != null) { try { recoveredErrorStack.add(thrownException); - visit(recoverySection); + executeElement(recoverySection); } finally { recoveredErrorStack.remove(recoveredErrorStack.size() - 1); } @@ -988,11 +1003,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen return _EvalUtils.compare(leftValue, _EvalUtils.CMP_OP_GREATER_THAN_EQUALS, rightValue, this); } - public void setOut(Writer out) { + public final void setOut(Writer out) { this.out = out; } - public Writer getOut() { + public final Writer getOut() { return out; } @@ -1049,6 +1064,67 @@ public final class Environment extends MutableProcessingConfiguration<Environmen return _EvalUtils.coerceModelToPlainText(tm, null, null, this); } + /** + * Evaluates the expression and prints its value as if it was printed with an interpolation (like + * <code>${exp}</code>). + * + * @param outputFormat + * Acts like the output format in a template where an interpolation is called. Not {@code null}. + * @param autoEscapingPolicy + * Acts like the auto escaping policy in a template where an interpolation is called. Not {@code null}. + */ + // TODO [FM3] will be public + @SuppressWarnings({ "rawtypes", "unchecked" }) + void interpolate( + ASTExpression exp, + OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy, + Environment env) throws IOException, TemplateException { + interpolate(exp.eval(env), exp, outputFormat, autoEscapingPolicy, env); + } + + /** + * Same as {@link #interpolate(ASTExpression, OutputFormat, AutoEscapingPolicy, Environment)}, but is used in the + * rare case where you have already evaluated the expression. + * + * @param value + * The value resulting from evaluating {@code exp} + * @param exp + * The expression whose evaluation was resulted in {@code value}. It won't be evaluated again, but is + * still used for error messages. {@code null} is tolerated, but should be avoided due to the resulting + * poorer quality error messages. + */ + void interpolate( + TemplateModel value, ASTExpression exp, + OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy, + Environment env) throws IOException, TemplateException { + _NullArgumentException.check("outputFormat", outputFormat); + _NullArgumentException.check("autoEscapingPolicy", autoEscapingPolicy); + + final Writer out = env.getOut(); + + final Object moOrStr = _EvalUtils.coerceModelToPlainTextOrMarkup(value, exp, null, env); + if (moOrStr instanceof String) { + final String s = (String) moOrStr; + + MarkupOutputFormat markupOutputFormat; + if (outputFormat instanceof MarkupOutputFormat) { + markupOutputFormat = (MarkupOutputFormat) outputFormat; + if (autoEscapingPolicy == AutoEscapingPolicy.ENABLE_IF_SUPPORTED + || autoEscapingPolicy == AutoEscapingPolicy.ENABLE_IF_DEFAULT + && markupOutputFormat.isAutoEscapedByDefault()) { + markupOutputFormat.output(s, out); + } else { + out.write(s); + } + } else { + out.write(s); + } + } else { + final TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr; + _EvalUtils.printTemplateMarkupOutputModel(mo, outputFormat, out, exp); + } + } + String formatBoolean(boolean value, boolean fallbackToTrueFalse) throws TemplateException { TemplateBooleanFormat templateBooleanFormat = getTemplateBooleanFormat(); if (value) { @@ -2135,7 +2211,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen } static void appendInstructionStackItem(ASTElement stackEl, StringBuilder sb) { - sb.append(MessageUtils.shorten(stackEl.getDescription(), 40)); + sb.append(MessageUtils.shorten(stackEl.getLabelWithParameters(), 40)); sb.append(" ["); ASTDirMacroOrFunction enclosingMacro = getEnclosingMacro(stackEl); @@ -2286,7 +2362,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen }; } - private void pushElement(ASTElement element) { + void pushElement(ASTElement element) { final int newSize = ++instructionStackSize; ASTElement[] instructionStack = this.instructionStack; if (newSize > instructionStack.length) { @@ -2300,7 +2376,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen instructionStack[newSize - 1] = element; } - private void popElement() { + void popElement() { instructionStackSize--; } @@ -2481,7 +2557,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen final Template prevTemplate; importMacros(includedTemplate); - visit(includedTemplate.getRootASTNode()); + executeElement(includedTemplate.getRootASTNode()); } /** @@ -2976,7 +3052,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen : callableDefinition.getName(); } - protected void genericExecute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env) + protected void genericExecute(TemplateModel[] args, CallPlace callPlace, Environment env) throws TemplateException, IOException { pushElement(callableDefinition); try { @@ -2996,7 +3072,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen // Note: Default expressions are evaluated here, so namespace, stack, etc. must be already set setLocalsFromArguments(macroCtx, args); - visit(callableDefinition.getChildBuffer(), out); + executeElements(callableDefinition.getChildBuffer()); } catch (ASTDirReturn.Return re) { // Not an error, just a <#return> } catch (TemplateException te) { @@ -3079,7 +3155,14 @@ public final class Environment extends MutableProcessingConfiguration<Environmen if (getCallableDefinition() == ASTDirMacroOrFunction.PASS_MACRO) { return; } - genericExecute(args, callPlace, out, env); + + Writer prevOut = env.getOut(); + try { + env.setOut(out); + genericExecute(args, callPlace, env); + } finally { + env.setOut(prevOut); + } } @Override @@ -3115,11 +3198,15 @@ public final class Environment extends MutableProcessingConfiguration<Environmen public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env) throws TemplateException { env.setLastReturnValue(null); + Writer prevOut = env.getOut(); try { - genericExecute(args, callPlace, _NullWriter.INSTANCE, env); + env.setOut(_NullWriter.INSTANCE); + genericExecute(args, callPlace, env); } catch (IOException e) { // Should not occur throw new TemplateException("Unexpected exception during function execution", e, env); + } finally { + env.setOut(prevOut); } return env.getLastReturnValue(); } http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java index e79bed2..a139a0a 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java @@ -31,6 +31,7 @@ import org.apache.freemarker.core.model.TemplateNumberModel; import org.apache.freemarker.core.model.TemplateSequenceModel; import org.apache.freemarker.core.model.TemplateStringModel; import org.apache.freemarker.core.util.BugException; +import org.apache.freemarker.core.util.StringToIndexMap; import org.apache.freemarker.core.util.TemplateLanguageUtils; import org.apache.freemarker.core.util._StringUtils; import org.apache.freemarker.core.valueformat.TemplateDateFormat; @@ -146,6 +147,9 @@ class MessageUtils { * The truncation is always signaled with a a {@code "..."} at the end of the result string. */ static String shorten(String s, int maxLength) { + if (s == null) { + return null; + } if (maxLength < 5) maxLength = 5; boolean isTruncated = false; @@ -359,4 +363,19 @@ class MessageUtils { return errorDescBuilder; } + static _ErrorDescriptionBuilder newBadNumberOfNestedContentParameterPassedMessage( + StringToIndexMap paramNamesOnCallPlace, + int numberOfParamsAtCallee) { + int numberOfParamsAtCallPlace = paramNamesOnCallPlace != null ? paramNamesOnCallPlace.size() : 0; + return new _ErrorDescriptionBuilder( + "The invocation declares ", (numberOfParamsAtCallPlace != 0 ? numberOfParamsAtCallPlace : "no"), + " nested content parameter(s)", + (numberOfParamsAtCallPlace != 0 + ? new Object[] { " (", new _DelayedJQuotedListing(paramNamesOnCallPlace.getKeys()), ")", } + : ""), + ", but the called object intends to pass ", + numberOfParamsAtCallee, " parameters. You need to declare ", numberOfParamsAtCallee, + " nested content parameters."); + } + } http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java index 983c05d..f17b778 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ParsingConfiguration.java @@ -50,7 +50,10 @@ public interface ParsingConfiguration { * {@link TemplateLanguage#getAutoEscapingPolicy() autoEscapingPolicy}, that overrides the value of * the {@linkplain #getOutputFormat() outputFormat} and {@link #getAutoEscapingPolicy() autoEscapingPolicy} * settings that are coming from {@link Configuration#getTemplateConfigurations templateConfigurations}, from the - * {@link Configuration}, or from any other {@link ParsingConfiguration}. + * {@link Configuration}, or from any other {@link ParsingConfiguration}. Most {@link TemplateLanguage}-s should + * have non-{@code null} for those settings, to prevent confusion on the template author side. There can be + * exceptions from this though, like {@link DefaultTemplateLanguage#F3AC} (where the "C" at the end stands for + * "configurable") has {@code null} for these settings. * * @see ParsingConfiguration#getRecognizeStandardFileExtensions() */ http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/StaticLinkingCheckException.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/StaticLinkingCheckException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/StaticLinkingCheckException.java new file mode 100644 index 0000000..50587b8 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/StaticLinkingCheckException.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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.apache.freemarker.core; + +import org.apache.freemarker.core.util._NullArgumentException; + +/** + * Exception thrown when a {@link Dialect} detects a problem during parsing (such a required parameter to a directive is + * missing). The exception will be converted to a {@link ParseException} by the parser, where the error message and + * cause exception will be the same as of the {@link StaticLinkingCheckException}. + */ +//TODO [FM3][DIALECTS] will be public +@SuppressWarnings("serial") +final class StaticLinkingCheckException extends Exception { + + private final _ErrorDescriptionBuilder messageBuilder; + private String builtMessage; + private final String simpleMessage; + + public StaticLinkingCheckException(_ErrorDescriptionBuilder messageBuilder) { + this(messageBuilder, null); + } + + public StaticLinkingCheckException(_ErrorDescriptionBuilder messageBuilder, Throwable cause) { + _NullArgumentException.check("messageBuilder", messageBuilder); + this.messageBuilder = messageBuilder; + simpleMessage = null; + } + + public StaticLinkingCheckException(String message) { + this(message, null); + } + + public StaticLinkingCheckException(String message, Throwable cause) { + _NullArgumentException.check("message", message); + this.messageBuilder = null; + simpleMessage = message; + } + + @Override + public String getMessage() { + if (simpleMessage != null) { + return simpleMessage; + } + + String builtDescription = this.builtMessage; + if (builtDescription != null) { + return builtDescription; + } + synchronized (this) { + builtDescription = messageBuilder.toString(); + this.builtMessage = builtDescription; + } + return builtDescription; + } + +} http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/StaticallyLinkedNamespaceEntry.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/StaticallyLinkedNamespaceEntry.java b/freemarker-core/src/main/java/org/apache/freemarker/core/StaticallyLinkedNamespaceEntry.java new file mode 100644 index 0000000..87ba7f7 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/StaticallyLinkedNamespaceEntry.java @@ -0,0 +1,117 @@ +package org.apache.freemarker.core; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.freemarker.core.model.TemplateCallableModel; +import org.apache.freemarker.core.model.TemplateDirectiveModel; +import org.apache.freemarker.core.model.TemplateFunctionModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.util.CommonSupplier; +import org.apache.freemarker.core.util._NullArgumentException; + +/** + * A template language namespace entry that's linked statically to the referring template (TODO [FM3] for planned + * Dialects feature, currently unused). + * Static linking allows detecting mistakes during template parsing (as opposed to during the later template + * processing), can make accessing the value faster (by avoiding runtime lookup), and allows callables to have + * parse-time effects. Currently used in {@link Dialect}. + */ +//TODO [FM3][FREEMARKER-99][DIALECTS] will be public. Also move it into core.dialect? +final class StaticallyLinkedNamespaceEntry { + + private final String name; + private final TemplateModel value; + private final CommonSupplier<ASTDirective> directiveCallNodeFactory; + private final CommonSupplier<ASTExpFunctionCall> functionCallNodeFactory; + + /** + * @param name + * Variable name in the namespace of the dialect + * @param value + * The runtime value of the entry. Not {@code null}. + * If this entry is a {@link TemplateCallableModel} that's not callable dynamically (as it has + * parse-time effects or such dependencies), then the corresponding {@code execute} method should to + * throw exception explaining that, but the {@link TemplateCallableModel} will be still used by the + * parser to retrieve meta-data such as + * {@link TemplateDirectiveModel#getDirectiveArgumentArrayLayout()}, + * {@link TemplateDirectiveModel#isNestedContentSupported()}, + * {@link TemplateFunctionModel#getFunctionArgumentArrayLayout()}. + * @param directiveCallNodeFactory + * If this entry is usable as directive, the factory for the corresponding {@linkplain ASTNode AST node}. + * At least one of {@code directiveCallNodeFactory} and {@code functionCallNodeFactory} must be + * non-{@code null}. It must be ensured that the behavior of this node is consistent with argument array + * layout and other meta-data present in the entry {@code value}. + * @param functionCallNodeFactory + * If this entry is usable as function (or like an FM2 "built-in"), the factory for the corresponding + * {@linkplain ASTNode AST node}. Not {@code null}. It must be ensured that the behavior of this node is consistent with argument array + * layout and other meta-data present in the entry {@code value}. + */ + public StaticallyLinkedNamespaceEntry( + String name, TemplateModel value, + CommonSupplier<ASTDirective> directiveCallNodeFactory, + CommonSupplier<ASTExpFunctionCall> functionCallNodeFactory) { + _NullArgumentException.check("name", name); + this.name = name; + if (directiveCallNodeFactory == null && functionCallNodeFactory == null) { + throw new IllegalArgumentException( + "At least one of \"directiveCallNodeFactory\" and \"functionCallNodeFactory\" must be non-null."); + } + this.directiveCallNodeFactory = directiveCallNodeFactory; + this.functionCallNodeFactory = functionCallNodeFactory; + this.value = value; + } + + /** + * See similarly named + * {@linkplain #StaticallyLinkedNamespaceEntry(String, TemplateModel, CommonSupplier, CommonSupplier) constructor} + * parameter. + */ + public String getName() { + return name; + } + + /** + * See similarly named + * {@linkplain #StaticallyLinkedNamespaceEntry(String, TemplateModel, CommonSupplier, CommonSupplier) constructor} + * parameter. + */ + public CommonSupplier<ASTDirective> getDirectiveCallNodeFactory() { + return directiveCallNodeFactory; + } + + /** + * See similarly named + * {@linkplain #StaticallyLinkedNamespaceEntry(String, TemplateModel, CommonSupplier, CommonSupplier) constructor} + * parameter. + */ + public CommonSupplier<ASTExpFunctionCall> getFunctionCallNodeFactory() { + return functionCallNodeFactory; + } + + /** + * See similarly named + * {@linkplain #StaticallyLinkedNamespaceEntry(String, TemplateModel, CommonSupplier, CommonSupplier) constructor} + * parameter. + */ + public TemplateModel getValue() { + return value; + } + +} http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java index 42e2686..c184281 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java @@ -122,7 +122,7 @@ public class Template implements ProcessingConfiguration, CustomStateScope { private AutoEscapingPolicy autoEscapingPolicy; // Values from template content that are detected automatically: private Charset actualSourceEncoding; - TagSyntax actualTagSyntax; // TODO [FM3][CF] Should be private + TagSyntax actualTagSyntax; // TODO [FM3][FREEMARKER-99] Should be private private InterpolationSyntax interpolationSyntax; // Custom state: http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java index 8627059..1b1b9a9 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java @@ -36,10 +36,11 @@ import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; /** * A partial set of configuration settings used for customizing the {@link Configuration}-level settings for individual * {@link Template}-s (or rather, for a group of templates). That it's partial means that you should call the - * corresponding {@code isXxxSet()} before getting a settings, or else you may cause - * {@link CoreSettingValueNotSetException}. (There's no fallback to the {@link Configuration}-level settings to keep the - * dependency graph of configuration related beans non-cyclic. As user code seldom reads settings directly from - * {@link TemplateConfiguration}-s anyway, this compromise was chosen.) + * corresponding {@code isXxxSet()} before getting a setting, or else you may cause + * {@link CoreSettingValueNotSetException}. (There's no fallback to the {@link Configuration}-level settings implemented + * in this class, to keep the dependency graph of configuration related beans non-cyclic. Instead, the caller of this + * API must implement the fallback itself. As user code hardly ever reads directly from {@link TemplateConfiguration}-s, + * this compromise was chosen.) * <p> * Note on the {@code locale} setting: When used with the standard template loading/caching mechanism ({@link * Configuration#getTemplate(String)} and its overloads), localized template lookup happens before the {@code locale} http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java index 4bc9a4f..734622e 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java @@ -83,18 +83,5 @@ class TemplateElements { } } } - - /** - * Used for some backward compatibility hacks. - */ - ASTImplicitParent asMixedContent() { - ASTImplicitParent mixedContent = new ASTImplicitParent(); - if (count != 0) { - ASTElement first = buffer[0]; - mixedContent.setChildren(this); - mixedContent.setLocation(first.getTemplate(), first, getLast()); - } - return mixedContent; - } } http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java index 145916c..defcf5d 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java @@ -22,9 +22,9 @@ import java.util.Collection; import java.util.Collections; /** - * Used as the return value of {@link ASTElement#accept(Environment)} when the invoked element has nested elements + * Used as the return value of {@link ASTElement#execute(Environment)} when the invoked element has nested elements * to invoke. It would be more natural to invoke child elements before returning from - * {@link ASTElement#accept(Environment)}, however, if there's nothing to do after the child elements were invoked, + * {@link ASTElement#execute(Environment)}, however, if there's nothing to do after the child elements were invoked, * that would mean wasting stack space. */ class TemplateElementsToVisit { http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java index 3b1a812..98002c9 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateLanguage.java @@ -29,9 +29,8 @@ import org.apache.freemarker.core.util._NullArgumentException; import org.apache.freemarker.core.util._StringUtils; /** - * Represents a template language; a template language specifies the syntax, and usually also the {@link OutputFormat} - * and {@link AutoEscapingPolicy} of the template. In the future (TODO [FM3]) custom template languages may also - * specify the dialect (which is the set of core directives and functions). + * Represents a template language; a template language specifies the syntax, the {@link Dialect}, and usually also the + * {@link OutputFormat} and {@link AutoEscapingPolicy}. * * <p><em>Currently this class is not mature, so it can't be implemented outside FreeMarker, * also its methods shouldn't be called from outside FreeMarker.</em> @@ -48,6 +47,7 @@ public abstract class TemplateLanguage { private final String fileExtension; private final OutputFormat outputFormat; private final AutoEscapingPolicy autoEscapingPolicy; + private final Dialect dialect; // Package visibility to prevent user implementations until this API is mature. @@ -70,14 +70,14 @@ public abstract class TemplateLanguage { * If the {@code #fileExtension} argument contains upper case letter or dot, or if it starts with "f" * and the language isn't defined by the FreeMarker project. */ - public TemplateLanguage(String fileExtension, OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy) { - this(fileExtension, false, outputFormat, autoEscapingPolicy); + public TemplateLanguage(String fileExtension, Dialect dialect, OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy) { + this(fileExtension, false, dialect, outputFormat, autoEscapingPolicy); } /** - * Non-public constructor used for languages defined by the FreeMarker project. + * Used internally to allow extensions starting with "f" (as those are reserved for FreeMarker) */ - TemplateLanguage(String fileExtension, boolean allowExtensionStartingWithF, + TemplateLanguage(String fileExtension, boolean allowExtensionStartingWithF, Dialect dialect, OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy) { _NullArgumentException.check("fileExtension", fileExtension); for (int i = 0; i < fileExtension.length(); i++) { @@ -94,6 +94,10 @@ public abstract class TemplateLanguage { "The \"fileExtension\" argument can't start with 'f' for an user-defined language."); } this.fileExtension = fileExtension; + + _NullArgumentException.check("dialect", dialect); + this.dialect = dialect; + this.outputFormat = outputFormat; this.autoEscapingPolicy = autoEscapingPolicy; } @@ -123,13 +127,15 @@ public abstract class TemplateLanguage { public String getFileExtension() { return fileExtension; } - - @Override - public final String toString() { - return "TemplateLanguage(" + _StringUtils.jQuote(fileExtension) + ")"; - } /** + * Returns the {@link Dialect} used by this template language; not {@code null}. + */ + public Dialect getDialect() { + return dialect; + } + + /** * The {@link OutputFormat} that this language enforces, or else {@code null} (and it comes from the * {@link ParsingConfiguration}). * @@ -151,4 +157,9 @@ public abstract class TemplateLanguage { return autoEscapingPolicy; } + @Override + public final String toString() { + return "TemplateLanguage(" + _StringUtils.jQuote(fileExtension) + ")"; + } + } http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java index be7877c..874a896 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java @@ -56,7 +56,7 @@ class ThreadInterruptionSupportTemplatePostProcessor extends TemplatePostProcess final int childCount = te.getChildCount(); for (int i = 0; i < childCount; i++) { - addInterruptionChecks(te.getChild(i)); + addInterruptionChecks(te.fastGetChild(i)); } if (te.isNestedBlockRepeater()) { @@ -79,7 +79,7 @@ class ThreadInterruptionSupportTemplatePostProcessor extends TemplatePostProcess } @Override - ASTElement[] accept(Environment env) throws TemplateException, IOException { + ASTElement[] execute(Environment env) throws TemplateException, IOException { // As the API doesn't allow throwing InterruptedException here (nor anywhere else, most importantly, // Template.process can't throw it), we must not clear the "interrupted" flag of the thread. if (Thread.currentThread().isInterrupted()) { @@ -89,12 +89,12 @@ class ThreadInterruptionSupportTemplatePostProcessor extends TemplatePostProcess } @Override - protected String dump(boolean canonical) { - return canonical ? "" : "<#--" + getASTNodeDescriptor() + "--#>"; + String dump(boolean canonical) { + return canonical ? "" : "<#--" + getLabelWithoutParameters() + "--#>"; } @Override - String getASTNodeDescriptor() { + public String getLabelWithoutParameters() { return "##threadInterruptionCheck"; } http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/UnparsedTemplateLanguage.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/UnparsedTemplateLanguage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/UnparsedTemplateLanguage.java index 2119ee3..6fe58f5 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/UnparsedTemplateLanguage.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/UnparsedTemplateLanguage.java @@ -45,7 +45,8 @@ public final class UnparsedTemplateLanguage extends TemplateLanguage { private UnparsedTemplateLanguage(String fileExtension, boolean allowExtensionStartingWithF, OutputFormat outputFormat) { - super(fileExtension, allowExtensionStartingWithF, outputFormat, AutoEscapingPolicy.ENABLE_IF_DEFAULT); + super(fileExtension, allowExtensionStartingWithF, DefaultDialect.INSTANCE, + outputFormat, AutoEscapingPolicy.ENABLE_IF_DEFAULT); } /** http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/UnsupportedFM2TemplateLanguage.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/UnsupportedFM2TemplateLanguage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/UnsupportedFM2TemplateLanguage.java index 158a35a..e67091d 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/UnsupportedFM2TemplateLanguage.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/UnsupportedFM2TemplateLanguage.java @@ -41,7 +41,7 @@ class UnsupportedFM2TemplateLanguage extends TemplateLanguage { private UnsupportedFM2TemplateLanguage(String fileExtension, OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy) { - super(fileExtension, true, outputFormat, autoEscapingPolicy); + super(fileExtension, true, DefaultDialect.INSTANCE, outputFormat, autoEscapingPolicy); } @Override http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java index 542a2f2..a4132c0 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java @@ -57,7 +57,7 @@ public class _CallableUtils { throws TemplateException { if (namedArgs != null) { throw new TemplateException(env, - getNamedArgumentsNotSupportedMessage(callable, namedArgs[0], calledAsFunction)); + getNamedArgumentsNotSupportedMessage(callable, namedArgs[0].name, calledAsFunction)); } TemplateModel[] execArgs; @@ -77,8 +77,8 @@ public class _CallableUtils { ASTExpression[] positionalArgs, NamedArgument[] namedArgs, ArgumentArrayLayout argsLayout, TemplateCallableModel callable, boolean calledAsFunction, Environment env) throws TemplateException { - int predefPosArgCnt = argsLayout.getPredefinedPositionalArgumentCount(); - int posVarargsArgIdx = argsLayout.getPositionalVarargsArgumentIndex(); + final int predefPosArgCnt = argsLayout.getPredefinedPositionalArgumentCount(); + final int posVarargsArgIdx = argsLayout.getPositionalVarargsArgumentIndex(); TemplateModel[] execArgs = new TemplateModel[argsLayout.getTotalLength()]; @@ -104,44 +104,14 @@ public class _CallableUtils { } execArgs[posVarargsArgIdx] = varargsSeq; } else if (positionalArgs != null && positionalArgs.length > predefPosArgCnt) { - checkSupportsAnyParameters(callable, argsLayout, calledAsFunction); - List<String> validPredefNames = argsLayout.getPredefinedNamedArgumentsMap().getKeys(); - _ErrorDescriptionBuilder errorDesc = new _ErrorDescriptionBuilder( - getMessagePartWhenCallingSomethingColon(callable, calledAsFunction), - "This ", getCallableTypeName(callable, calledAsFunction), - " ", - (predefPosArgCnt != 0 - ? new Object[]{ "can only have ", predefPosArgCnt } - : "can't have" - ), - " arguments passed by position, but the invocation has ", - positionalArgs.length, " such arguments.", - (!argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported() ? - new Object[] { - " Try to pass arguments by name (as in ", - (callable instanceof TemplateDirectiveModel - ? "<@example x=1 y=2 />" - : "example(x=1, y=2)"), - ")", - (!validPredefNames.isEmpty() - ? new Object[] { " The supported parameter names are: ", - new _DelayedJQuotedListing(validPredefNames)} - : _CollectionUtils.EMPTY_OBJECT_ARRAY)} - : "") - ); - if (callable instanceof Environment.TemplateLanguageDirective - && !argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported()) { - errorDesc.tip("You can pass a parameter by position (i.e., without specifying its name, as you" - + " have tried now) when the macro has defined that parameter to be a positional parameter. " - + "See in the documentation how, and when that's a good practice."); - } - throw new TemplateException(env, errorDesc); + throw new TemplateException(env, + newPositionalArgDoesNotFitArgLayoutErrorDesc(argsLayout, callable, calledAsFunction)); } int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex(); NativeHashEx namedVarargsHash = null; if (namedArgs != null) { - StringToIndexMap predefNamedArgsMap = argsLayout.getPredefinedNamedArgumentsMap(); + final StringToIndexMap predefNamedArgsMap = argsLayout.getPredefinedNamedArgumentsMap(); for (NamedArgument namedArg : namedArgs) { int argIdx = predefNamedArgsMap.get(namedArg.name); if (argIdx != -1) { @@ -149,20 +119,9 @@ public class _CallableUtils { } else { if (namedVarargsHash == null) { if (namedVarargsArgumentIndex == -1) { - checkSupportsAnyParameters(callable, argsLayout, calledAsFunction); - Collection<String> validNames = predefNamedArgsMap.getKeys(); throw new TemplateException(env, - validNames == null || validNames.isEmpty() - ? getNamedArgumentsNotSupportedMessage( - callable, namedArg, calledAsFunction) - : new Object[] { - getMessagePartWhenCallingSomethingColon(callable, calledAsFunction), - "This ", getCallableTypeName(callable, calledAsFunction), - " has no parameter that's passed by name and is called ", - new _DelayedJQuote(namedArg.name), - ". The supported parameter names are:\n", - new _DelayedJQuotedListing(validNames) - }); + newNamedArgumentDoesNotArgLayoutErrorDesc( + argsLayout, namedArg.name, callable, calledAsFunction)); } namedVarargsHash = new NativeHashEx(); @@ -177,13 +136,88 @@ public class _CallableUtils { return execArgs; } + static _ErrorDescriptionBuilder newPositionalArgDoesNotFitArgLayoutErrorDesc( + ArgumentArrayLayout argsLayout, TemplateCallableModel callable, boolean calledAsFunction) { + _ErrorDescriptionBuilder noParamsED = checkSupportsAnyParameters(callable, argsLayout, calledAsFunction); + if (noParamsED != null) { + return noParamsED; + } + + List<String> validPredefNames; + _ErrorDescriptionBuilder errorDesc = new _ErrorDescriptionBuilder( + getMessagePartWhenCallingSomethingColon(callable, calledAsFunction), + "This ", getCallableTypeName(callable, calledAsFunction), + " ", + (argsLayout.getPredefinedPositionalArgumentCount() != 0 + ? new Object[]{ "can only have ", argsLayout.getPredefinedPositionalArgumentCount() } + : "can't have" + ), + " arguments passed by position, but the invocation tries to pass in more.", + (!argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported() ? + new Object[] { + " Try to pass arguments by name (as in ", + (callable instanceof TemplateDirectiveModel + ? "<@example x=1 y=2 />" + : "example(x=1, y=2)"), + ")", + (!(validPredefNames = argsLayout.getPredefinedNamedArgumentsMap().getKeys()) + .isEmpty() + ? new Object[] { + " The supported parameter names are: ", + new _DelayedJQuotedListing(validPredefNames) + } + : _CollectionUtils.EMPTY_OBJECT_ARRAY)} + : "") + ); + if (callable instanceof Environment.TemplateLanguageDirective + && !argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported()) { + errorDesc.tip("You can pass a parameter by position (i.e., without specifying its name, as you" + + " have tried now) when the directuve (the macro) has defined that parameter to be a " + + "positional parameter. See in the documentation how, and when that's a good practice."); + } + return errorDesc; + } + + static _ErrorDescriptionBuilder newNamedArgumentDoesNotArgLayoutErrorDesc(ArgumentArrayLayout argsLayout, + String argName, TemplateCallableModel callable, boolean calledAsFunction) { + _ErrorDescriptionBuilder noParamsED = checkSupportsAnyParameters(callable, argsLayout, calledAsFunction); + if (noParamsED != null) { + return noParamsED; + } + + Collection<String> validNames = argsLayout.getPredefinedNamedArgumentsMap().getKeys(); + _ErrorDescriptionBuilder errorDesc = new _ErrorDescriptionBuilder( + validNames == null || validNames.isEmpty() + ? getNamedArgumentsNotSupportedMessage( + callable, argName, calledAsFunction) + : new Object[] { + getMessagePartWhenCallingSomethingColon(callable, calledAsFunction), + "This ", getCallableTypeName(callable, calledAsFunction), + " has no parameter that's passed by name and is called ", + new _DelayedJQuote(argName), + ". The supported parameter names are:\n", + new _DelayedJQuotedListing(validNames) + }); + return errorDesc; + } + + static private _ErrorDescriptionBuilder checkSupportsAnyParameters( + TemplateCallableModel callable, ArgumentArrayLayout argsLayout, boolean calledAsFunction) { + return argsLayout.getTotalLength() == 0 + ? new _ErrorDescriptionBuilder( + getMessagePartWhenCallingSomethingColon(callable, calledAsFunction), + "This ", getCallableTypeName(callable, calledAsFunction), + " doesn't support any parameters.") + : null; + } + private static Object[] getNamedArgumentsNotSupportedMessage(TemplateCallableModel callable, - _CallableUtils.NamedArgument namedArg, boolean calledAsFunction) { + String argName, boolean calledAsFunction) { return new Object[] { getMessagePartWhenCallingSomethingColon(callable, calledAsFunction), "This ", getCallableTypeName(callable, calledAsFunction), " can't have arguments that are passed by name (like ", - new _DelayedJQuote(namedArg.name), "). Try to pass arguments by position " + new _DelayedJQuote(argName), "). Try to pass arguments by position " + "(i.e, without name, as in ", (callable instanceof TemplateDirectiveModel ? "<@example arg1, arg2, arg3 />" @@ -192,17 +226,6 @@ public class _CallableUtils { }; } - static private void checkSupportsAnyParameters( - TemplateCallableModel callable, ArgumentArrayLayout argsLayout, boolean calledAsFunction) - throws TemplateException { - if (argsLayout.getTotalLength() == 0) { - throw new TemplateException( - getMessagePartWhenCallingSomethingColon(callable, calledAsFunction), - "This ", getCallableTypeName(callable, calledAsFunction), - " doesn't support any parameters."); - } - } - /** * Something like {@code "When calling function \"lib.f3ah:foo\": " or "When calling ?leftPad: "} */ http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java index e15374b..c94e369 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java @@ -19,7 +19,6 @@ package org.apache.freemarker.core; import java.util.ArrayList; -import java.util.Enumeration; import java.util.List; /** @@ -42,7 +41,7 @@ public final class _Debug { // TODO: Ensure there always is a parent by making sure // that the root element in the template is always a ASTImplicitParent // Also make sure it doesn't conflict with anyone's code. - parent.setChildAt(parent.getIndex(te), db); + parent.setChildAt(indexOfChild(parent, te), db); } public static void removeDebugBreak(Template t, int line) { @@ -62,7 +61,16 @@ public final class _Debug { return; } ASTElement parent = db.getParent(); - parent.setChildAt(parent.getIndex(db), db.getChild(0)); + parent.setChildAt(indexOfChild(parent, db), db.fastGetChild(0)); + } + + private static final int indexOfChild(ASTElement parent, ASTElement node) { + for (int i = 0; i < parent.getChildCount(); i++) { + if (parent.getChild(i) == node) { + return i; + } + } + return -1; } private static ASTElement findTemplateElement(ASTElement te, int line) { @@ -70,9 +78,8 @@ public final class _Debug { return null; } // Find the narrowest match - List childMatches = new ArrayList(); - for (Enumeration children = te.children(); children.hasMoreElements(); ) { - ASTElement child = (ASTElement) children.nextElement(); + List<ASTElement> childMatches = new ArrayList<>(); + for (ASTElement child : te.getChildren()) { ASTElement childmatch = findTemplateElement(child, line); if (childmatch != null) { childMatches.add(childmatch); @@ -81,7 +88,7 @@ public final class _Debug { //find a match that exactly matches the begin/end line ASTElement bestMatch = null; for (int i = 0; i < childMatches.size(); i++) { - ASTElement e = (ASTElement) childMatches.get(i); + ASTElement e = childMatches.get(i); if ( bestMatch == null ) { bestMatch = e; @@ -110,9 +117,9 @@ public final class _Debug { private static void removeDebugBreaks(ASTElement te) { int count = te.getChildCount(); for (int i = 0; i < count; ++i) { - ASTElement child = te.getChild(i); + ASTElement child = te.fastGetChild(i); while (child instanceof ASTDebugBreak) { - ASTElement dbchild = child.getChild(0); + ASTElement dbchild = child.fastGetChild(0); te.setChildAt(i, dbchild); child = dbchild; } http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java index 7d96e3d..f00da1a 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java @@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory; * Note that this class isn't serializable, thus the containing exception should render the message before it's * serialized. */ +//TODO [FM3] will be public public class _ErrorDescriptionBuilder { private static final Logger LOG = LoggerFactory.getLogger(_ErrorDescriptionBuilder.class); @@ -77,7 +78,7 @@ public class _ErrorDescriptionBuilder { Blaming blaming = findBlaming(parentElement, blamed, 0); if (blaming != null) { sb.append("For "); - String nss = blaming.blamer.getASTNodeDescriptor(); + String nss = blaming.blamer.getLabelWithoutParameters(); char q = nss.indexOf('"') == -1 ? '\"' : '`'; sb.append(q).append(nss).append(q); sb.append(" ").append(blaming.roleOfblamed).append(": "); @@ -117,7 +118,8 @@ public class _ErrorDescriptionBuilder { } sb.append(" ["); - sb.append(blamed.getStartLocation()); + sb.append(MessageUtils.formatLocationForEvaluationError( + blamed.getTemplate(), blamed.getBeginLine(), blamed.getEndLine())); sb.append(']'); http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java index 525ed82..150bb6e 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java @@ -21,6 +21,8 @@ package org.apache.freemarker.core; import static org.apache.freemarker.core.MessageUtils.*; +import java.io.IOException; +import java.io.Writer; import java.util.Date; import org.apache.freemarker.core.arithmetic.ArithmeticEngine; @@ -33,6 +35,7 @@ import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateNumberModel; import org.apache.freemarker.core.model.TemplateStringModel; import org.apache.freemarker.core.outputformat.MarkupOutputFormat; +import org.apache.freemarker.core.outputformat.OutputFormat; import org.apache.freemarker.core.util.BugException; import org.apache.freemarker.core.util._ClassUtils; import org.apache.freemarker.core.valueformat.TemplateDateFormat; @@ -338,6 +341,9 @@ public class _EvalUtils { * * @param seqTip * Tip to display if the value type is not coercable, but it's iterable. + * @param exp + * The expression that was evaluated to {@code tm}. This can be {@code null}, however that may results + * in poor quality error messages. * * @return Never {@code null} */ @@ -540,5 +546,29 @@ public class _EvalUtils { ? env.getArithmeticEngine() : tObj.getTemplate().getParsingConfiguration().getArithmeticEngine(); } + + public static void printTemplateMarkupOutputModel(final TemplateMarkupOutputModel mo, OutputFormat outputFormat, + final Writer out, ASTExpression exp) throws TemplateException, IOException { + final MarkupOutputFormat moOF = mo.getOutputFormat(); + // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic! + if (moOF != outputFormat && !outputFormat.isOutputFormatMixingAllowed()) { + final String srcPlainText; + // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic! + srcPlainText = moOF.getSourcePlainText(mo); + if (srcPlainText == null) { + throw new TemplateException(exp, + "The value to print is in ", new _DelayedToString(moOF), + " format, which differs from the current output format, ", + new _DelayedToString(outputFormat), ". Format conversion wasn't possible."); + } + if (outputFormat instanceof MarkupOutputFormat) { + ((MarkupOutputFormat) outputFormat).output(srcPlainText, out); + } else { + out.write(srcPlainText); + } + } else { + moOF.output(mo, out); + } + } } http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java index b653f7f..95c06bc 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java @@ -51,7 +51,7 @@ public class _ArrayAdapterList<E> extends AbstractList<E> { @Override public Iterator<E> iterator() { - return new _ArrayIterator(array); + return new _ArrayIterator<E>(array); } @Override http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayEnumeration.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayEnumeration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayEnumeration.java deleted file mode 100644 index 1c82658..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayEnumeration.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES 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.apache.freemarker.core.util; - -import java.util.Enumeration; -import java.util.NoSuchElementException; - -/** Don't use this; used internally by FreeMarker, might changes without notice. */ -public class _ArrayEnumeration implements Enumeration { - - private final Object[] array; - private final int size; - private int nextIndex; - - public _ArrayEnumeration(Object[] array, int size) { - this.array = array; - this.size = size; - nextIndex = 0; - } - - @Override - public boolean hasMoreElements() { - return nextIndex < size; - } - - @Override - public Object nextElement() { - if (nextIndex >= size) { - throw new NoSuchElementException(); - } - return array[nextIndex++]; - } - -} http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayIterator.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayIterator.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayIterator.java index 7e02449..0ff9241 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayIterator.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayIterator.java @@ -23,12 +23,12 @@ import java.util.Iterator; import java.util.NoSuchElementException; /** Don't use this; used internally by FreeMarker, might changes without notice. */ -public class _ArrayIterator implements Iterator { +public class _ArrayIterator<T> implements Iterator<T> { - private final Object[] array; + private final T[] array; private int nextIndex; - public _ArrayIterator(Object[] array) { + public _ArrayIterator(T[] array) { this.array = array; nextIndex = 0; } @@ -39,7 +39,7 @@ public class _ArrayIterator implements Iterator { } @Override - public Object next() { + public T next() { if (nextIndex >= array.length) { throw new NoSuchElementException(); } http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/java/org/apache/freemarker/core/util/_NullArgumentException.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_NullArgumentException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_NullArgumentException.java index 4e7f777..d9cdad9 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_NullArgumentException.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_NullArgumentException.java @@ -22,6 +22,7 @@ package org.apache.freemarker.core.util; /** * Indicates that an argument that must be non-{@code null} was {@code null}. */ +@SuppressWarnings("serial") public class _NullArgumentException extends IllegalArgumentException { public _NullArgumentException() { @@ -46,6 +47,7 @@ public class _NullArgumentException extends IllegalArgumentException { } /** + * Convenience method to protect against a {@code null} argument. */ public static void check(Object argumentValue) { if (argumentValue == null) { http://git-wip-us.apache.org/repos/asf/freemarker/blob/e8e58ffa/freemarker-core/src/main/javacc/FTL.jj ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/javacc/FTL.jj b/freemarker-core/src/main/javacc/FTL.jj index aff3750..11e7ed2 100644 --- a/freemarker-core/src/main/javacc/FTL.jj +++ b/freemarker-core/src/main/javacc/FTL.jj @@ -30,6 +30,7 @@ PARSER_BEGIN(FMParser) package org.apache.freemarker.core; import org.apache.freemarker.core.*; +import org.apache.freemarker.core.Dialect.ConfiguredDialect; import org.apache.freemarker.core.outputformat.*; import org.apache.freemarker.core.outputformat.impl.*; import org.apache.freemarker.core.model.*; @@ -84,6 +85,7 @@ public class FMParser { private boolean autoEscaping; private ParsingConfiguration pCfg; DefaultTemplateLanguage templateLanguage; + ConfiguredDialect configuredDialect; private InputStream streamToUnmarkWhenEncEstabd; /** Keeps track of #list nesting. */ @@ -113,6 +115,7 @@ public class FMParser { private StringToIndexMap.Entry[] topNestedContentParamNamesBuffer; private int topNestedContentParamNamesLength; + FMParser(Template template, Reader reader, ParsingConfiguration pCfg, InputStream streamToUnmarkWhenEncEstabd) { @@ -143,6 +146,7 @@ public class FMParser { token_source.incompatibleImprovements = incompatibleImprovements; this.incompatibleImprovements = incompatibleImprovements; + // We know that it's a DefaultTemplateLanguage, as this is the parser of the DefaultTemplateLanguage. templateLanguage = (DefaultTemplateLanguage) pCfg.getTemplateLanguage(); outputFormat = pCfg.getOutputFormat(); @@ -165,6 +169,8 @@ public class FMParser { token_source.interpolationSyntax = templateLanguage.getInterpolationSyntax(); this.stripWhitespace = pCfg.getWhitespaceStripping(); + + configuredDialect = template.getConfiguration().getConfiguredDialect(templateLanguage.getDialect()); } void setupStringLiteralMode(FMParserTokenManager parentTokenSource, OutputFormat outputFormat) { @@ -3315,7 +3321,7 @@ ASTElement DynamicTopLevelCall() : ) | ( - // This could be part of the positional paramter choice, but we can give better error messages this way. + // This could be part of the positional parameter choice, but we can give better error messages this way. t = <COMMA> { if (prevChoiceComma != null) {
