freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [02/54] [partial] incubator-freemarker git commit: Top level package name change to org.apache.freemarker, and some of of the internal package structure changes. Other smaller cleanup. To be continued...
Date Thu, 16 Feb 2017 23:08:27 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/Assignment.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/Assignment.java b/src/main/java/org/apache/freemarker/core/ast/Assignment.java
new file mode 100644
index 0000000..e861a8e
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/Assignment.java
@@ -0,0 +1,279 @@
+/*
+ * 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.ast;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.ast.FMParserConstants;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+/**
+ * An instruction that makes a single assignment, like [#local x=1].
+ * This is also used as the child of {@link AssignmentInstruction}, if there are multiple assignments in the same tag,
+ * like in [#local x=1 x=2].
+ */
+final class Assignment extends TemplateElement {
+
+    // These must not clash with ArithmeticExpression.TYPE_... constants: 
+    private static final int OPERATOR_TYPE_EQUALS = 0x10000;
+    private static final int OPERATOR_TYPE_PLUS_EQUALS = 0x10001;
+    private static final int OPERATOR_TYPE_PLUS_PLUS = 0x10002;
+    private static final int OPERATOR_TYPE_MINUS_MINUS = 0x10003;
+    
+    private final int/*enum*/ scope;
+    private final String variableName;
+    private final int operatorType;
+    private final Expression valueExp;
+    private Expression namespaceExp;
+
+    static final int NAMESPACE = 1;
+    static final int LOCAL = 2;
+    static final int GLOBAL = 3;
+    
+    private static final Number ONE = Integer.valueOf(1);
+
+    /**
+     * @param variableName the variable name to assign to.
+     * @param valueExp the expression to assign.
+     * @param scope the scope of the assignment, one of NAMESPACE, LOCAL, or GLOBAL
+     */
+    Assignment(String variableName,
+            int operator,
+            Expression valueExp,
+            int scope) {
+        this.scope = scope;
+        
+        this.variableName = variableName;
+        
+        if (operator == FMParserConstants.EQUALS) {
+            operatorType = OPERATOR_TYPE_EQUALS;
+        } else {
+            switch (operator) {
+            case FMParserConstants.PLUS_PLUS:
+                operatorType = OPERATOR_TYPE_PLUS_PLUS;
+                break;
+            case FMParserConstants.MINUS_MINUS:
+                operatorType = OPERATOR_TYPE_MINUS_MINUS;
+                break;
+            case FMParserConstants.PLUS_EQUALS:
+                operatorType = OPERATOR_TYPE_PLUS_EQUALS;
+                break;
+            case FMParserConstants.MINUS_EQUALS:
+                operatorType = ArithmeticExpression.TYPE_SUBSTRACTION;
+                break;
+            case FMParserConstants.TIMES_EQUALS:
+                operatorType = ArithmeticExpression.TYPE_MULTIPLICATION;
+                break;
+            case FMParserConstants.DIV_EQUALS:
+                operatorType = ArithmeticExpression.TYPE_DIVISION;
+                break;
+            case FMParserConstants.MOD_EQUALS:
+                operatorType = ArithmeticExpression.TYPE_MODULO;
+                break;
+            default:
+                throw new BugException();
+            }
+        }
+        
+        this.valueExp = valueExp;
+    }
+    
+    void setNamespaceExp(Expression namespaceExp) {
+        if (scope != NAMESPACE && namespaceExp != null) throw new BugException();
+        this.namespaceExp =  namespaceExp;
+    }
+
+    @Override
+    TemplateElement[] accept(Environment env) throws TemplateException {
+        final Environment.Namespace namespace;
+        if (namespaceExp == null) {
+            switch (scope) {
+            case LOCAL:
+                namespace = null;
+                break;
+            case GLOBAL:
+                namespace = env.getGlobalNamespace();
+                break;
+            case NAMESPACE:
+                namespace = env.getCurrentNamespace();
+                break;
+            default:
+                throw new BugException("Unexpected scope type: " + scope);
+            }
+        } else {
+            TemplateModel namespaceTM = namespaceExp.eval(env);
+            try {
+                namespace = (Environment.Namespace) namespaceTM;
+            } catch (ClassCastException e) {
+                throw new NonNamespaceException(namespaceExp, namespaceTM, env);
+            }
+            if (namespace == null) {
+                throw InvalidReferenceException.getInstance(namespaceExp, env);
+            }
+        }
+        
+        TemplateModel value;
+        if (operatorType == OPERATOR_TYPE_EQUALS) {
+            value = valueExp.eval(env);
+            valueExp.assertNonNull(value, env);
+        } else {
+            TemplateModel lhoValue;
+            if (namespace == null) {
+                lhoValue = env.getLocalVariable(variableName);
+            } else {
+                lhoValue = namespace.get(variableName);
+            }
+            
+            if (operatorType == OPERATOR_TYPE_PLUS_EQUALS) {  // Add or concat operation
+                if (lhoValue == null) {
+                    throw InvalidReferenceException.getInstance(
+                            variableName, getOperatorTypeAsString(), env);
+                }
+                
+                value = valueExp.eval(env);
+                valueExp.assertNonNull(value, env);
+                value = AddConcatExpression._eval(env, namespaceExp, null, lhoValue, valueExp, value);
+            } else {  // Numerical operation
+                Number lhoNumber;
+                if (lhoValue instanceof TemplateNumberModel) {
+                    lhoNumber = EvalUtil.modelToNumber((TemplateNumberModel) lhoValue, null);
+                } else if (lhoValue == null) {
+                    throw InvalidReferenceException.getInstance(variableName, getOperatorTypeAsString(), env);
+                } else {
+                    throw new NonNumericalException(variableName, lhoValue, null, env);
+                }
+
+                if (operatorType == OPERATOR_TYPE_PLUS_PLUS) {
+                    value  = AddConcatExpression._evalOnNumbers(env, getParentElement(), lhoNumber, ONE);
+                } else if (operatorType == OPERATOR_TYPE_MINUS_MINUS) {
+                    value = ArithmeticExpression._eval(
+                            env, getParentElement(), lhoNumber, ArithmeticExpression.TYPE_SUBSTRACTION, ONE);
+                } else { // operatorType == ArithmeticExpression.TYPE_...
+                    Number rhoNumber = valueExp.evalToNumber(env);
+                    value = ArithmeticExpression._eval(env, this, lhoNumber, operatorType, rhoNumber);
+                }
+            }
+        }
+        
+        if (namespace == null) {
+            env.setLocalVariable(variableName, value);
+        } else {
+            namespace.put(variableName, value);
+        }
+        return null;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder buf = new StringBuilder();
+        String dn = getParentElement() instanceof AssignmentInstruction ? null : getNodeTypeSymbol();
+        if (dn != null) {
+            if (canonical) buf.append("<");
+            buf.append(dn);
+            buf.append(' ');
+        }
+        
+        buf.append(_CoreStringUtils.toFTLTopLevelTragetIdentifier(variableName));
+        
+        if (valueExp != null) {
+            buf.append(' ');
+        }
+        buf.append(getOperatorTypeAsString());
+        if (valueExp != null) {
+            buf.append(' ');
+            buf.append(valueExp.getCanonicalForm());
+        }
+        if (dn != null) {
+            if (namespaceExp != null) {
+                buf.append(" in ");
+                buf.append(namespaceExp.getCanonicalForm());
+            }
+            if (canonical) buf.append(">");
+        }
+        String result = buf.toString();
+        return result;
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return getDirectiveName(scope);
+    }
+    
+    static String getDirectiveName(int scope) {
+        if (scope == Assignment.LOCAL) {
+            return "#local";
+        } else if (scope == Assignment.GLOBAL) {
+            return "#global";
+        } else if (scope == Assignment.NAMESPACE) {
+            return "#assign";
+        } else {
+            return "#{unknown_assignment_type}";
+        }
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 5;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return variableName;
+        case 1: return getOperatorTypeAsString();
+        case 2: return valueExp;
+        case 3: return Integer.valueOf(scope);
+        case 4: return namespaceExp;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.ASSIGNMENT_TARGET;
+        case 1: return ParameterRole.ASSIGNMENT_OPERATOR;
+        case 2: return ParameterRole.ASSIGNMENT_SOURCE;
+        case 3: return ParameterRole.VARIABLE_SCOPE;
+        case 4: return ParameterRole.NAMESPACE;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+    private String getOperatorTypeAsString() {
+        if (operatorType == OPERATOR_TYPE_EQUALS) {
+            return "=";
+        } else if (operatorType == OPERATOR_TYPE_PLUS_EQUALS) {
+            return "+=";
+        } else if (operatorType == OPERATOR_TYPE_PLUS_PLUS) {
+            return "++";
+        } else if (operatorType == OPERATOR_TYPE_MINUS_MINUS) {
+            return "--";
+        } else {
+            return ArithmeticExpression.getOperatorSymbol(operatorType) + "=";
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/AssignmentInstruction.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/AssignmentInstruction.java b/src/main/java/org/apache/freemarker/core/ast/AssignmentInstruction.java
new file mode 100644
index 0000000..caff0bb
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/AssignmentInstruction.java
@@ -0,0 +1,117 @@
+/*
+ * 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.ast;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.TemplateException;
+
+/**
+ * An instruction that does multiple assignments, like [#local x=1 x=2].
+ * Each assignment is represented by a {@link Assignment} child element.
+ * If there's only one assignment, its usually just a {@link Assignment} without parent {@link AssignmentInstruction}.
+ */
+final class AssignmentInstruction extends TemplateElement {
+
+    private int scope;
+    private Expression namespaceExp;
+
+    AssignmentInstruction(int scope) {
+        this.scope = scope;
+        setChildBufferCapacity(1);
+    }
+
+    void addAssignment(Assignment assignment) {
+        addChild(assignment);
+    }
+    
+    void setNamespaceExp(Expression namespaceExp) {
+        this.namespaceExp = namespaceExp;
+        int ln = getChildCount();
+        for (int i = 0; i < ln; i++) {
+            ((Assignment) getChild(i)).setNamespaceExp(namespaceExp);
+        }
+    }
+
+    @Override
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder buf = new StringBuilder();
+        if (canonical) buf.append('<');
+        buf.append(Assignment.getDirectiveName(scope));
+        if (canonical) {
+            buf.append(' ');
+            int ln = getChildCount();
+            for (int i = 0; i < ln; i++) {
+                if (i != 0) {
+                    buf.append(", ");
+                }
+                Assignment assignment = (Assignment) getChild(i);
+                buf.append(assignment.getCanonicalForm());
+            }
+        } else {
+            buf.append("-container");
+        }
+        if (namespaceExp != null) {
+            buf.append(" in ");
+            buf.append(namespaceExp.getCanonicalForm());
+        }
+        if (canonical) buf.append(">");
+        return buf.toString();
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 2;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return Integer.valueOf(scope);
+        case 1: return namespaceExp;
+        default: return null;
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.VARIABLE_SCOPE;
+        case 1: return ParameterRole.NAMESPACE;
+        default: return null;
+        }
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return Assignment.getDirectiveName(scope);
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/AttemptBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/AttemptBlock.java b/src/main/java/org/apache/freemarker/core/ast/AttemptBlock.java
new file mode 100644
index 0000000..ee39820
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/AttemptBlock.java
@@ -0,0 +1,89 @@
+/*
+ * 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.ast;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.TemplateException;
+
+/**
+ * Holder for the attempted section of the #attempt element and of the nested #recover element ({@link RecoveryBlock}).
+ */
+final class AttemptBlock extends TemplateElement {
+    
+    private TemplateElement attemptedSection;
+    private RecoveryBlock recoverySection;
+    
+    AttemptBlock(TemplateElements attemptedSectionChildren, RecoveryBlock recoverySection) {
+        TemplateElement attemptedSection = attemptedSectionChildren.asSingleElement();
+        this.attemptedSection = attemptedSection;
+        this.recoverySection = recoverySection;
+        setChildBufferCapacity(2);
+        addChild(attemptedSection); // for backward compatibility
+        addChild(recoverySection);
+    }
+
+    @Override
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        env.visitAttemptRecover(this, attemptedSection, recoverySection);
+        return null;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        if (!canonical) {
+            return getNodeTypeSymbol();
+        } else {
+            StringBuilder buf = new StringBuilder();
+            buf.append("<").append(getNodeTypeSymbol()).append(">");
+            buf.append(getChildrenCanonicalForm());            
+            buf.append("</").append(getNodeTypeSymbol()).append(">");
+            return buf.toString();
+        }
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 1;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        if (idx != 0) throw new IndexOutOfBoundsException();
+        return recoverySection;
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        if (idx != 0) throw new IndexOutOfBoundsException();
+        return ParameterRole.ERROR_HANDLER;
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#attempt";
+    }
+    
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/AutoEscBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/AutoEscBlock.java b/src/main/java/org/apache/freemarker/core/ast/AutoEscBlock.java
new file mode 100644
index 0000000..beb0bee
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/AutoEscBlock.java
@@ -0,0 +1,79 @@
+/*
+ * 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.ast;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.TemplateException;
+
+/**
+ * An #autoEsc element
+ */
+final class AutoEscBlock extends TemplateElement {
+    
+    AutoEscBlock(TemplateElements children) { 
+        setChildren(children);
+    }
+
+    @Override
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        if (canonical) {
+            return "<" + getNodeTypeSymbol() + "\">" + getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">";
+        } else {
+            return getNodeTypeSymbol();
+        }
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#autoesc";
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    boolean isIgnorable(boolean stripWhitespace) {
+        return getChildCount() == 0;
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BackwardCompatibleTemplateNumberFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BackwardCompatibleTemplateNumberFormat.java b/src/main/java/org/apache/freemarker/core/ast/BackwardCompatibleTemplateNumberFormat.java
new file mode 100644
index 0000000..448dc26
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BackwardCompatibleTemplateNumberFormat.java
@@ -0,0 +1,30 @@
+/*
+ * 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.ast;
+
+/**
+ * Only exists for emulating pre-2.3.24-IcI {@code ?string} behavior. 
+ * 
+ * @since 2.3.24
+ */
+abstract class BackwardCompatibleTemplateNumberFormat extends TemplateNumberFormat {
+
+    abstract String format(Number number) throws UnformattableValueException;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BlockAssignment.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BlockAssignment.java b/src/main/java/org/apache/freemarker/core/ast/BlockAssignment.java
new file mode 100644
index 0000000..effff2e
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BlockAssignment.java
@@ -0,0 +1,183 @@
+/*
+ * 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.ast;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Map;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateTransformModel;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+
+/**
+ * Like [#local x]...[/#local].
+ */
+final class BlockAssignment extends TemplateElement {
+
+    private final String varName;
+    private final Expression namespaceExp;
+    private final int scope;
+    private final MarkupOutputFormat<?> markupOutputFormat;
+
+    BlockAssignment(TemplateElements children, String varName, int scope, Expression namespaceExp, MarkupOutputFormat<?> markupOutputFormat) {
+        setChildren(children);
+        this.varName = varName;
+        this.namespaceExp = namespaceExp;
+        this.scope = scope;
+        this.markupOutputFormat = markupOutputFormat;
+    }
+
+    @Override
+    TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+        TemplateElement[] children = getChildBuffer();
+        if (children != null) {
+            env.visitAndTransform(children, new CaptureOutput(env), null);
+        } else {
+            TemplateModel value = capturedStringToModel("");
+            if (namespaceExp != null) {
+                Environment.Namespace ns = (Environment.Namespace) namespaceExp.eval(env);
+                ns.put(varName, value);
+            } else if (scope == Assignment.NAMESPACE) {
+                env.setVariable(varName, value);
+            } else if (scope == Assignment.GLOBAL) {
+                env.setGlobalVariable(varName, value);
+            } else if (scope == Assignment.LOCAL) {
+                env.setLocalVariable(varName, value);
+            }
+        }
+        return null;
+    }
+
+    private TemplateModel capturedStringToModel(String s) throws TemplateModelException {
+        return markupOutputFormat == null ? new SimpleScalar(s) : markupOutputFormat.fromMarkup(s);
+    }
+
+    private class CaptureOutput implements TemplateTransformModel {
+        private final Environment env;
+        private final Environment.Namespace fnsModel;
+        
+        CaptureOutput(Environment env) throws TemplateException {
+            this.env = env;
+            TemplateModel nsModel = null;
+            if (namespaceExp != null) {
+                nsModel = namespaceExp.eval(env);
+                if (!(nsModel instanceof Environment.Namespace)) {
+                    throw new NonNamespaceException(namespaceExp, nsModel, env);
+                }
+            }
+            fnsModel = (Environment.Namespace ) nsModel; 
+        }
+        
+        public Writer getWriter(Writer out, Map args) {
+            return new StringWriter() {
+                @Override
+                public void close() throws IOException {
+                    TemplateModel result;
+                    try {
+                        result = capturedStringToModel(toString());
+                    } catch (TemplateModelException e) {
+                        // [Java 1.6] e to cause
+                        throw new IOException("Failed to create FTL value from captured string: " + e);
+                    }
+                    switch(scope) {
+                        case Assignment.NAMESPACE: {
+                            if (fnsModel != null) {
+                                fnsModel.put(varName, result);
+                            } else {
+                                env.setVariable(varName, result);
+                            }
+                            break;
+                        }
+                        case Assignment.LOCAL: {
+                            env.setLocalVariable(varName, result);
+                            break;
+                        }
+                        case Assignment.GLOBAL: {
+                            env.setGlobalVariable(varName, result);
+                            break;
+                        }
+                    }
+                }
+            };
+        }
+    }
+    
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder sb = new StringBuilder();
+        if (canonical) sb.append("<");
+        sb.append(getNodeTypeSymbol());
+        sb.append(' ');
+        sb.append(varName);
+        if (namespaceExp != null) {
+            sb.append(" in ");
+            sb.append(namespaceExp.getCanonicalForm());
+        }
+        if (canonical) {
+            sb.append('>');
+            sb.append(getChildrenCanonicalForm());
+            sb.append("</");
+            sb.append(getNodeTypeSymbol());
+            sb.append('>');
+        } else {
+            sb.append(" = .nested_output");
+        }
+        return sb.toString();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return Assignment.getDirectiveName(scope);
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 3;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return varName;
+        case 1: return Integer.valueOf(scope);
+        case 2: return namespaceExp;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.ASSIGNMENT_TARGET;
+        case 1: return ParameterRole.VARIABLE_SCOPE;
+        case 2: return ParameterRole.NAMESPACE;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BodyInstruction.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BodyInstruction.java b/src/main/java/org/apache/freemarker/core/ast/BodyInstruction.java
new file mode 100644
index 0000000..bcdac8b
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BodyInstruction.java
@@ -0,0 +1,158 @@
+/*
+ * 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.ast;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * An instruction that processes the nested block within a macro instruction.
+ */
+final class BodyInstruction extends TemplateElement {
+    
+    
+    private List bodyParameters;
+    
+    
+    BodyInstruction(List bodyParameters) {
+        this.bodyParameters = bodyParameters;
+    }
+    
+    List getBodyParameters() {
+        return bodyParameters;
+    }
+
+    /**
+     * There is actually a subtle but essential point in the code below.
+     * A macro operates in the context in which it's defined. However, 
+     * a nested block within a macro instruction is defined in the 
+     * context in which the macro was invoked. So, we actually need to
+     * temporarily switch the namespace and macro context back to
+     * what it was before macro invocation to implement this properly.
+     * I (JR) realized this thanks to some incisive comments from Daniel Dekany.
+     */
+    @Override
+    TemplateElement[] accept(Environment env) throws IOException, TemplateException {
+        Context bodyContext = new Context(env);
+        env.invokeNestedContent(bodyContext);
+        return null;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder sb = new StringBuilder();
+        if (canonical) sb.append('<');
+        sb.append(getNodeTypeSymbol());
+        if (bodyParameters != null) {
+            for (int i = 0; i < bodyParameters.size(); i++) {
+                sb.append(' ');
+                sb.append(((Expression) bodyParameters.get(i)).getCanonicalForm());
+            }
+        }
+        if (canonical) sb.append('>');
+        return sb.toString();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#nested";
+    }
+    
+    @Override
+    int getParameterCount() {
+        return bodyParameters != null ? bodyParameters.size() : 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        checkIndex(idx);
+        return bodyParameters.get(idx);
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        checkIndex(idx);
+        return ParameterRole.PASSED_VALUE;
+    }
+
+    private void checkIndex(int idx) {
+        if (bodyParameters == null || idx >= bodyParameters.size()) {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+    
+    /*
+    boolean heedsOpeningWhitespace() {
+        return true;
+    }
+
+    boolean heedsTrailingWhitespace() {
+        return true;
+    }
+    */
+    
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
+
+    class Context implements LocalContext {
+        Macro.Context invokingMacroContext;
+        Environment.Namespace bodyVars;
+        
+        Context(Environment env) throws TemplateException {
+            invokingMacroContext = env.getCurrentMacroContext();
+            List bodyParameterNames = invokingMacroContext.nestedContentParameterNames;
+            if (bodyParameters != null) {
+                for (int i = 0; i < bodyParameters.size(); i++) {
+                    Expression exp = (Expression) bodyParameters.get(i);
+                    TemplateModel tm = exp.eval(env);
+                    if (bodyParameterNames != null && i < bodyParameterNames.size()) {
+                        String bodyParameterName = (String) bodyParameterNames.get(i);
+                        if (bodyVars == null) {
+                            bodyVars = env.new Namespace();
+                        }
+                        bodyVars.put(bodyParameterName, tm);
+                    }
+                }
+            }
+        }
+        
+        public TemplateModel getLocalVariable(String name) throws TemplateModelException {
+            return bodyVars == null ? null : bodyVars.get(name);
+        }
+        
+        public Collection getLocalVariableNames() {
+            List bodyParameterNames = invokingMacroContext.nestedContentParameterNames;
+            return bodyParameterNames == null ? Collections.EMPTY_LIST : bodyParameterNames;
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BooleanExpression.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BooleanExpression.java b/src/main/java/org/apache/freemarker/core/ast/BooleanExpression.java
new file mode 100644
index 0000000..08e1dc7
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BooleanExpression.java
@@ -0,0 +1,32 @@
+/*
+ * 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.ast;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+abstract class BooleanExpression extends Expression {
+
+    @Override
+    TemplateModel _eval(Environment env) throws TemplateException {
+        return evalToBoolean(env) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BooleanLiteral.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BooleanLiteral.java b/src/main/java/org/apache/freemarker/core/ast/BooleanLiteral.java
new file mode 100644
index 0000000..82cfb8f
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BooleanLiteral.java
@@ -0,0 +1,88 @@
+/*
+ * 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.ast;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+final class BooleanLiteral extends Expression {
+
+    private final boolean val;
+
+    public BooleanLiteral(boolean val) {
+        this.val = val;
+    }
+
+    static TemplateBooleanModel getTemplateModel(boolean b) {
+        return b? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+    }
+
+    @Override
+    boolean evalToBoolean(Environment env) {
+        return val;
+    }
+
+    @Override
+    public String getCanonicalForm() {
+        return val ? MiscUtil.C_TRUE : MiscUtil.C_FALSE;
+    }
+
+    @Override
+    String getNodeTypeSymbol() {
+        return getCanonicalForm();
+    }
+    
+    @Override
+    public String toString() {
+        return val ? MiscUtil.C_TRUE : MiscUtil.C_FALSE;
+    }
+
+    @Override
+    TemplateModel _eval(Environment env) {
+        return val ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+    }
+    
+    @Override
+    boolean isLiteral() {
+        return true;
+    }
+
+    @Override
+    protected Expression deepCloneWithIdentifierReplaced_inner(
+            String replacedIdentifier, Expression replacement, ReplacemenetState replacementState) {
+    	return new BooleanLiteral(val);
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BoundedRangeModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BoundedRangeModel.java b/src/main/java/org/apache/freemarker/core/ast/BoundedRangeModel.java
new file mode 100644
index 0000000..65a8821
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BoundedRangeModel.java
@@ -0,0 +1,69 @@
+/*
+ * 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.ast;
+
+
+/**
+ * A range between two integers (maybe 0 long).
+ */
+final class BoundedRangeModel extends RangeModel {
+
+    private final int step, size;
+    private final boolean rightAdaptive;
+    private final boolean affectedByStringSlicingBug;
+    
+    /**
+     * @param inclusiveEnd Tells if the {@code end} index is part of the range. 
+     * @param rightAdaptive Tells if the right end of the range adapts to the size of the sliced value, if otherwise
+     *     it would be bigger than that. 
+     */
+    BoundedRangeModel(int begin, int end, boolean inclusiveEnd, boolean rightAdaptive) {
+        super(begin);
+        step = begin <= end ? 1 : -1;
+        size = Math.abs(end - begin) + (inclusiveEnd ? 1 : 0);
+        this.rightAdaptive = rightAdaptive;
+        this.affectedByStringSlicingBug = inclusiveEnd;
+    }
+
+    public int size() {
+        return size;
+    }
+    
+    @Override
+    int getStep() {
+        return step;
+    }
+
+    @Override
+    boolean isRightUnbounded() {
+        return false;
+    }
+
+    @Override
+    boolean isRightAdaptive() {
+        return rightAdaptive;
+    }
+
+    @Override
+    boolean isAffactedByStringSlicingBug() {
+        return affectedByStringSlicingBug;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BreakInstruction.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BreakInstruction.java b/src/main/java/org/apache/freemarker/core/ast/BreakInstruction.java
new file mode 100644
index 0000000..67a770e
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BreakInstruction.java
@@ -0,0 +1,70 @@
+/*
+ * 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.ast;
+
+/**
+ * Represents a &lt;break&gt; instruction to break out of a loop.
+ */
+final class BreakInstruction extends TemplateElement {
+
+    @Override
+    TemplateElement[] accept(Environment env) {
+        throw Break.INSTANCE;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        return canonical ? "<" + getNodeTypeSymbol() + "/>" : getNodeTypeSymbol();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#break";
+    }
+
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+    
+    static class Break extends RuntimeException {
+        static final Break INSTANCE = new Break();
+        private Break() {
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}
+
+

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BugException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BugException.java b/src/main/java/org/apache/freemarker/core/ast/BugException.java
new file mode 100644
index 0000000..9850b74
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BugException.java
@@ -0,0 +1,52 @@
+/*
+ * 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.ast;
+
+/**
+ * An unexpected state was reached that is certainly caused by a bug in FreeMarker.
+ * 
+ * @since 2.3.21
+ */
+public class BugException extends RuntimeException {
+
+    private static final String COMMON_MESSAGE
+        = "A bug was detected in FreeMarker; please report it with stack-trace";
+
+    public BugException() {
+        this((Throwable) null);
+    }
+
+    public BugException(String message) {
+        this(message, null);
+    }
+
+    public BugException(Throwable cause) {
+        super(COMMON_MESSAGE, cause);
+    }
+
+    public BugException(String message, Throwable cause) {
+        super(COMMON_MESSAGE + ": " + message, cause);
+    }
+    
+    public BugException(int value) {
+        this(String.valueOf(value));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltIn.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltIn.java b/src/main/java/org/apache/freemarker/core/ast/BuiltIn.java
new file mode 100644
index 0000000..3c8fc42
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BuiltIn.java
@@ -0,0 +1,495 @@
+/*
+ * 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.ast;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.ast.FMParserTokenManager;
+import org.apache.freemarker.core.ast.Token;
+import org.apache.freemarker.core.ast.BuiltInsForDates.iso_BI;
+import org.apache.freemarker.core.ast.BuiltInsForDates.iso_utc_or_local_BI;
+import org.apache.freemarker.core.ast.BuiltInsForMarkupOutputs.markup_stringBI;
+import org.apache.freemarker.core.ast.BuiltInsForMultipleTypes.is_dateLikeBI;
+import org.apache.freemarker.core.ast.BuiltInsForNodes.ancestorsBI;
+import org.apache.freemarker.core.ast.BuiltInsForNodes.childrenBI;
+import org.apache.freemarker.core.ast.BuiltInsForNodes.nextSiblingBI;
+import org.apache.freemarker.core.ast.BuiltInsForNodes.node_nameBI;
+import org.apache.freemarker.core.ast.BuiltInsForNodes.node_namespaceBI;
+import org.apache.freemarker.core.ast.BuiltInsForNodes.node_typeBI;
+import org.apache.freemarker.core.ast.BuiltInsForNodes.parentBI;
+import org.apache.freemarker.core.ast.BuiltInsForNodes.previousSiblingBI;
+import org.apache.freemarker.core.ast.BuiltInsForNodes.rootBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.absBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.byteBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.ceilingBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.doubleBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.floatBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.floorBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.intBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.is_infiniteBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.is_nanBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.longBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.number_to_dateBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.roundBI;
+import org.apache.freemarker.core.ast.BuiltInsForNumbers.shortBI;
+import org.apache.freemarker.core.ast.BuiltInsForOutputFormatRelated.escBI;
+import org.apache.freemarker.core.ast.BuiltInsForOutputFormatRelated.no_escBI;
+import org.apache.freemarker.core.ast.BuiltInsForSequences.chunkBI;
+import org.apache.freemarker.core.ast.BuiltInsForSequences.firstBI;
+import org.apache.freemarker.core.ast.BuiltInsForSequences.lastBI;
+import org.apache.freemarker.core.ast.BuiltInsForSequences.reverseBI;
+import org.apache.freemarker.core.ast.BuiltInsForSequences.seq_containsBI;
+import org.apache.freemarker.core.ast.BuiltInsForSequences.seq_index_ofBI;
+import org.apache.freemarker.core.ast.BuiltInsForSequences.sortBI;
+import org.apache.freemarker.core.ast.BuiltInsForSequences.sort_byBI;
+import org.apache.freemarker.core.ast.BuiltInsForStringsMisc.evalBI;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.util.DateUtil;
+import org.apache.freemarker.core.util.StringUtil;
+
+/**
+ * The {@code ?} operator used for things like {@code foo?upper_case}.
+ */
+abstract class BuiltIn extends Expression implements Cloneable {
+    
+    protected Expression target;
+    protected String key;
+
+    static final Set<String> CAMEL_CASE_NAMES = new TreeSet<String>();
+    static final Set<String> SNAKE_CASE_NAMES = new TreeSet<String>();
+    static final int NUMBER_OF_BIS = 263;
+    static final HashMap<String, BuiltIn> BUILT_INS_BY_NAME = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
+
+    static {
+        // Note that you must update NUMBER_OF_BIS if you add new items here!
+        
+        putBI("abs", new absBI());
+        putBI("ancestors", new ancestorsBI());
+        putBI("api", new BuiltInsForMultipleTypes.apiBI());
+        putBI("boolean", new BuiltInsForStringsMisc.booleanBI());
+        putBI("byte", new byteBI());
+        putBI("c", new BuiltInsForMultipleTypes.cBI());
+        putBI("cap_first", "capFirst", new BuiltInsForStringsBasic.cap_firstBI());
+        putBI("capitalize", new BuiltInsForStringsBasic.capitalizeBI());
+        putBI("ceiling", new ceilingBI());
+        putBI("children", new childrenBI());
+        putBI("chop_linebreak", "chopLinebreak", new BuiltInsForStringsBasic.chop_linebreakBI());
+        putBI("contains", new BuiltInsForStringsBasic.containsBI());        
+        putBI("date", new BuiltInsForMultipleTypes.dateBI(TemplateDateModel.DATE));
+        putBI("date_if_unknown", "dateIfUnknown", new BuiltInsForDates.dateType_if_unknownBI(TemplateDateModel.DATE));
+        putBI("datetime", new BuiltInsForMultipleTypes.dateBI(TemplateDateModel.DATETIME));
+        putBI("datetime_if_unknown", "datetimeIfUnknown", new BuiltInsForDates.dateType_if_unknownBI(TemplateDateModel.DATETIME));
+        putBI("default", new ExistenceBuiltins.defaultBI());
+        putBI("double", new doubleBI());
+        putBI("ends_with", "endsWith", new BuiltInsForStringsBasic.ends_withBI());
+        putBI("ensure_ends_with", "ensureEndsWith", new BuiltInsForStringsBasic.ensure_ends_withBI());
+        putBI("ensure_starts_with", "ensureStartsWith", new BuiltInsForStringsBasic.ensure_starts_withBI());
+        putBI("esc", new escBI());
+        putBI("eval", new evalBI());
+        putBI("exists", new ExistenceBuiltins.existsBI());
+        putBI("first", new firstBI());
+        putBI("float", new floatBI());
+        putBI("floor", new floorBI());
+        putBI("chunk", new chunkBI());
+        putBI("counter", new BuiltInsForLoopVariables.counterBI());
+        putBI("item_cycle", "itemCycle", new BuiltInsForLoopVariables.item_cycleBI());
+        putBI("has_api", "hasApi", new BuiltInsForMultipleTypes.has_apiBI());
+        putBI("has_content", "hasContent", new ExistenceBuiltins.has_contentBI());
+        putBI("has_next", "hasNext", new BuiltInsForLoopVariables.has_nextBI());
+        putBI("html", new BuiltInsForStringsEncoding.htmlBI());
+        putBI("if_exists", "ifExists", new ExistenceBuiltins.if_existsBI());
+        putBI("index", new BuiltInsForLoopVariables.indexBI());
+        putBI("index_of", "indexOf", new BuiltInsForStringsBasic.index_ofBI(false));
+        putBI("int", new intBI());
+        putBI("interpret", new Interpret());
+        putBI("is_boolean", "isBoolean", new BuiltInsForMultipleTypes.is_booleanBI());
+        putBI("is_collection", "isCollection", new BuiltInsForMultipleTypes.is_collectionBI());
+        putBI("is_collection_ex", "isCollectionEx", new BuiltInsForMultipleTypes.is_collection_exBI());
+        is_dateLikeBI bi = new BuiltInsForMultipleTypes.is_dateLikeBI();
+        putBI("is_date", "isDate", bi);  // misnomer
+        putBI("is_date_like", "isDateLike", bi);
+        putBI("is_date_only", "isDateOnly", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.DATE));
+        putBI("is_even_item", "isEvenItem", new BuiltInsForLoopVariables.is_even_itemBI());
+        putBI("is_first", "isFirst", new BuiltInsForLoopVariables.is_firstBI());
+        putBI("is_last", "isLast", new BuiltInsForLoopVariables.is_lastBI());
+        putBI("is_unknown_date_like", "isUnknownDateLike", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.UNKNOWN));
+        putBI("is_datetime", "isDatetime", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.DATETIME));
+        putBI("is_directive", "isDirective", new BuiltInsForMultipleTypes.is_directiveBI());
+        putBI("is_enumerable", "isEnumerable", new BuiltInsForMultipleTypes.is_enumerableBI());
+        putBI("is_hash_ex", "isHashEx", new BuiltInsForMultipleTypes.is_hash_exBI());
+        putBI("is_hash", "isHash", new BuiltInsForMultipleTypes.is_hashBI());
+        putBI("is_infinite", "isInfinite", new is_infiniteBI());
+        putBI("is_indexable", "isIndexable", new BuiltInsForMultipleTypes.is_indexableBI());
+        putBI("is_macro", "isMacro", new BuiltInsForMultipleTypes.is_macroBI());
+        putBI("is_markup_output", "isMarkupOutput", new BuiltInsForMultipleTypes.is_markup_outputBI());
+        putBI("is_method", "isMethod", new BuiltInsForMultipleTypes.is_methodBI());
+        putBI("is_nan", "isNan", new is_nanBI());
+        putBI("is_node", "isNode", new BuiltInsForMultipleTypes.is_nodeBI());
+        putBI("is_number", "isNumber", new BuiltInsForMultipleTypes.is_numberBI());
+        putBI("is_odd_item", "isOddItem", new BuiltInsForLoopVariables.is_odd_itemBI());
+        putBI("is_sequence", "isSequence", new BuiltInsForMultipleTypes.is_sequenceBI());
+        putBI("is_string", "isString", new BuiltInsForMultipleTypes.is_stringBI());
+        putBI("is_time", "isTime", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.TIME));
+        putBI("is_transform", "isTransform", new BuiltInsForMultipleTypes.is_transformBI());
+        
+        putBI("iso_utc", "isoUtc", new iso_utc_or_local_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_SECONDS, /* useUTC = */ true));
+        putBI("iso_utc_fz", "isoUtcFZ", new iso_utc_or_local_BI(
+                /* showOffset = */ Boolean.TRUE, DateUtil.ACCURACY_SECONDS, /* useUTC = */ true));
+        putBI("iso_utc_nz", "isoUtcNZ", new iso_utc_or_local_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_SECONDS, /* useUTC = */ true));
+        
+        putBI("iso_utc_ms", "isoUtcMs", new iso_utc_or_local_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ true));
+        putBI("iso_utc_ms_nz", "isoUtcMsNZ", new iso_utc_or_local_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ true));
+        
+        putBI("iso_utc_m", "isoUtcM", new iso_utc_or_local_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_MINUTES, /* useUTC = */ true));
+        putBI("iso_utc_m_nz", "isoUtcMNZ", new iso_utc_or_local_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MINUTES, /* useUTC = */ true));
+        
+        putBI("iso_utc_h", "isoUtcH", new iso_utc_or_local_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_HOURS, /* useUTC = */ true));
+        putBI("iso_utc_h_nz", "isoUtcHNZ", new iso_utc_or_local_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_HOURS, /* useUTC = */ true));
+        
+        putBI("iso_local", "isoLocal", new iso_utc_or_local_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_SECONDS, /* useUTC = */ false));
+        putBI("iso_local_nz", "isoLocalNZ", new iso_utc_or_local_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_SECONDS, /* useUTC = */ false));
+        
+        putBI("iso_local_ms", "isoLocalMs", new iso_utc_or_local_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ false));
+        putBI("iso_local_ms_nz", "isoLocalMsNZ", new iso_utc_or_local_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ false));
+        
+        putBI("iso_local_m", "isoLocalM", new iso_utc_or_local_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_MINUTES, /* useUTC = */ false));
+        putBI("iso_local_m_nz", "isoLocalMNZ", new iso_utc_or_local_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MINUTES, /* useUTC = */ false));
+        
+        putBI("iso_local_h", "isoLocalH", new iso_utc_or_local_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_HOURS, /* useUTC = */ false));
+        putBI("iso_local_h_nz", "isoLocalHNZ", new iso_utc_or_local_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_HOURS, /* useUTC = */ false));
+        
+        putBI("iso", new iso_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_SECONDS));
+        putBI("iso_nz", "isoNZ", new iso_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_SECONDS));
+        
+        putBI("iso_ms", "isoMs", new iso_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_MILLISECONDS));
+        putBI("iso_ms_nz", "isoMsNZ", new iso_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MILLISECONDS));
+        
+        putBI("iso_m", "isoM", new iso_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_MINUTES));
+        putBI("iso_m_nz", "isoMNZ", new iso_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MINUTES));
+        
+        putBI("iso_h", "isoH", new iso_BI(
+                /* showOffset = */ null, DateUtil.ACCURACY_HOURS));
+        putBI("iso_h_nz", "isoHNZ", new iso_BI(
+                /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_HOURS));
+        
+        putBI("j_string", "jString", new BuiltInsForStringsEncoding.j_stringBI());
+        putBI("join", new BuiltInsForSequences.joinBI());
+        putBI("js_string", "jsString", new BuiltInsForStringsEncoding.js_stringBI());
+        putBI("json_string", "jsonString", new BuiltInsForStringsEncoding.json_stringBI());
+        putBI("keep_after", "keepAfter", new BuiltInsForStringsBasic.keep_afterBI());
+        putBI("keep_before", "keepBefore", new BuiltInsForStringsBasic.keep_beforeBI());
+        putBI("keep_after_last", "keepAfterLast", new BuiltInsForStringsBasic.keep_after_lastBI());
+        putBI("keep_before_last", "keepBeforeLast", new BuiltInsForStringsBasic.keep_before_lastBI());
+        putBI("keys", new BuiltInsForHashes.keysBI());
+        putBI("last_index_of", "lastIndexOf", new BuiltInsForStringsBasic.index_ofBI(true));
+        putBI("last", new lastBI());
+        putBI("left_pad", "leftPad", new BuiltInsForStringsBasic.padBI(true));
+        putBI("length", new BuiltInsForStringsBasic.lengthBI());
+        putBI("long", new longBI());
+        putBI("lower_abc", "lowerAbc", new BuiltInsForNumbers.lower_abcBI());
+        putBI("lower_case", "lowerCase", new BuiltInsForStringsBasic.lower_caseBI());
+        putBI("namespace", new BuiltInsForMultipleTypes.namespaceBI());
+        putBI("new", new NewBI());
+        putBI("markup_string", "markupString", new markup_stringBI());
+        putBI("node_name", "nodeName", new node_nameBI());
+        putBI("node_namespace", "nodeNamespace", new node_namespaceBI());
+        putBI("node_type", "nodeType", new node_typeBI());
+        putBI("no_esc", "noEsc", new no_escBI());
+        putBI("number", new BuiltInsForStringsMisc.numberBI());
+        putBI("number_to_date", "numberToDate", new number_to_dateBI(TemplateDateModel.DATE));
+        putBI("number_to_time", "numberToTime", new number_to_dateBI(TemplateDateModel.TIME));
+        putBI("number_to_datetime", "numberToDatetime", new number_to_dateBI(TemplateDateModel.DATETIME));
+        putBI("parent", new parentBI());
+        putBI("previous_sibling", "previousSibling", new previousSiblingBI());
+        putBI("next_sibling", "nextSibling", new nextSiblingBI());
+        putBI("item_parity", "itemParity", new BuiltInsForLoopVariables.item_parityBI());
+        putBI("item_parity_cap", "itemParityCap", new BuiltInsForLoopVariables.item_parity_capBI());
+        putBI("reverse", new reverseBI());
+        putBI("right_pad", "rightPad", new BuiltInsForStringsBasic.padBI(false));
+        putBI("root", new rootBI());
+        putBI("round", new roundBI());
+        putBI("remove_ending", "removeEnding", new BuiltInsForStringsBasic.remove_endingBI());
+        putBI("remove_beginning", "removeBeginning", new BuiltInsForStringsBasic.remove_beginningBI());
+        putBI("rtf", new BuiltInsForStringsEncoding.rtfBI());
+        putBI("seq_contains", "seqContains", new seq_containsBI());
+        putBI("seq_index_of", "seqIndexOf", new seq_index_ofBI(1));
+        putBI("seq_last_index_of", "seqLastIndexOf", new seq_index_ofBI(-1));
+        putBI("short", new shortBI());
+        putBI("size", new BuiltInsForMultipleTypes.sizeBI());
+        putBI("sort_by", "sortBy", new sort_byBI());
+        putBI("sort", new sortBI());
+        putBI("split", new BuiltInsForStringsBasic.split_BI());
+        putBI("switch", new BuiltInsWithParseTimeParameters.switch_BI());
+        putBI("starts_with", "startsWith", new BuiltInsForStringsBasic.starts_withBI());
+        putBI("string", new BuiltInsForMultipleTypes.stringBI());
+        putBI("substring", new BuiltInsForStringsBasic.substringBI());
+        putBI("then", new BuiltInsWithParseTimeParameters.then_BI());
+        putBI("time", new BuiltInsForMultipleTypes.dateBI(TemplateDateModel.TIME));
+        putBI("time_if_unknown", "timeIfUnknown", new BuiltInsForDates.dateType_if_unknownBI(TemplateDateModel.TIME));
+        putBI("trim", new BuiltInsForStringsBasic.trimBI());
+        putBI("uncap_first", "uncapFirst", new BuiltInsForStringsBasic.uncap_firstBI());
+        putBI("upper_abc", "upperAbc", new BuiltInsForNumbers.upper_abcBI());
+        putBI("upper_case", "upperCase", new BuiltInsForStringsBasic.upper_caseBI());
+        putBI("url", new BuiltInsForStringsEncoding.urlBI());
+        putBI("url_path", "urlPath", new BuiltInsForStringsEncoding.urlPathBI());
+        putBI("values", new BuiltInsForHashes.valuesBI());
+        putBI("web_safe", "webSafe", BUILT_INS_BY_NAME.get("html"));  // deprecated; use ?html instead
+        putBI("word_list", "wordList", new BuiltInsForStringsBasic.word_listBI());
+        putBI("xhtml", new BuiltInsForStringsEncoding.xhtmlBI());
+        putBI("xml", new BuiltInsForStringsEncoding.xmlBI());
+        putBI("matches", new BuiltInsForStringsRegexp.matchesBI());
+        putBI("groups", new BuiltInsForStringsRegexp.groupsBI());
+        putBI("replace", new BuiltInsForStringsRegexp.replace_reBI());
+
+        
+        if (NUMBER_OF_BIS < BUILT_INS_BY_NAME.size()) {
+            throw new AssertionError("Update NUMBER_OF_BIS! Should be: " + BUILT_INS_BY_NAME.size());
+        }
+    }
+    
+    private static void putBI(String name, BuiltIn bi) {
+        BUILT_INS_BY_NAME.put(name, bi);
+        SNAKE_CASE_NAMES.add(name);
+        CAMEL_CASE_NAMES.add(name);
+    }
+
+    private static void putBI(String nameSnakeCase, String nameCamelCase, BuiltIn bi) {
+        BUILT_INS_BY_NAME.put(nameSnakeCase, bi);
+        BUILT_INS_BY_NAME.put(nameCamelCase, bi);
+        SNAKE_CASE_NAMES.add(nameSnakeCase);
+        CAMEL_CASE_NAMES.add(nameCamelCase);
+    }
+    
+    /**
+     * @param target
+     *            Left-hand-operand expression
+     * @param keyTk
+     *            Built-in name token
+     */
+    static BuiltIn newBuiltIn(int incompatibleImprovements, Expression target, Token keyTk,
+            FMParserTokenManager tokenManager) throws ParseException {
+        String key = keyTk.image;
+        BuiltIn bi = BUILT_INS_BY_NAME.get(key);
+        if (bi == null) {
+            StringBuilder buf = new StringBuilder("Unknown built-in: ").append(StringUtil.jQuote(key)).append(". ");
+            
+            buf.append(
+                    "Help (latest version): http://freemarker.org/docs/ref_builtins.html; "
+                    + "you're using FreeMarker ").append(Configuration.getVersion()).append(".\n" 
+                    + "The alphabetical list of built-ins:");
+            List names = new ArrayList(BUILT_INS_BY_NAME.keySet().size());
+            names.addAll(BUILT_INS_BY_NAME.keySet());
+            Collections.sort(names);
+            char lastLetter = 0;
+            
+            int shownNamingConvention;
+            {
+                int namingConvention = tokenManager.namingConvention;
+                shownNamingConvention = namingConvention != Configuration.AUTO_DETECT_NAMING_CONVENTION
+                        ? namingConvention : Configuration.LEGACY_NAMING_CONVENTION /* [2.4] CAMEL_CASE */; 
+            }
+            
+            boolean first = true;
+            for (Iterator it = names.iterator(); it.hasNext(); ) {
+                String correctName = (String) it.next();
+                int correctNameNamingConvetion = _CoreStringUtils.getIdentifierNamingConvention(correctName);
+                if (shownNamingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION 
+                        ? correctNameNamingConvetion != Configuration.LEGACY_NAMING_CONVENTION
+                        : correctNameNamingConvetion != Configuration.CAMEL_CASE_NAMING_CONVENTION) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        buf.append(", ");
+                    }
+                    
+                    char firstChar = correctName.charAt(0);
+                    if (firstChar != lastLetter) {
+                        lastLetter = firstChar;
+                        buf.append('\n');
+                    }
+                    buf.append(correctName);
+                }
+            }
+                
+            throw new ParseException(buf.toString(), null, keyTk);
+        }
+        
+        while (bi instanceof ICIChainMember
+                && incompatibleImprovements < ((ICIChainMember) bi).getMinimumICIVersion()) {
+            bi = (BuiltIn) ((ICIChainMember) bi).getPreviousICIChainMember();
+        }
+        
+        try {
+            bi = (BuiltIn) bi.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new InternalError();
+        }
+        bi.key = key;
+        bi.target = target;
+        return bi;
+    }
+
+    @Override
+    public String getCanonicalForm() {
+        return target.getCanonicalForm() + "?" + key;
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "?" + key;
+    }
+
+    @Override
+    boolean isLiteral() {
+        return false; // be on the safe side.
+    }
+    
+    protected final void checkMethodArgCount(List args, int expectedCnt) throws TemplateModelException {
+        checkMethodArgCount(args.size(), expectedCnt);
+    }
+    
+    protected final void checkMethodArgCount(int argCnt, int expectedCnt) throws TemplateModelException {
+        if (argCnt != expectedCnt) {
+            throw MessageUtil.newArgCntError("?" + key, argCnt, expectedCnt);
+        }
+    }
+
+    protected final void checkMethodArgCount(List args, int minCnt, int maxCnt) throws TemplateModelException {
+        checkMethodArgCount(args.size(), minCnt, maxCnt);
+    }
+    
+    protected final void checkMethodArgCount(int argCnt, int minCnt, int maxCnt) throws TemplateModelException {
+        if (argCnt < minCnt || argCnt > maxCnt) {
+            throw MessageUtil.newArgCntError("?" + key, argCnt, minCnt, maxCnt);
+        }
+    }
+
+    /**
+     * Same as {@link #getStringMethodArg}, but checks if {@code args} is big enough, and returns {@code null} if it
+     * isn't.
+     */
+    protected final String getOptStringMethodArg(List args, int argIdx)
+            throws TemplateModelException {
+        return args.size() > argIdx ? getStringMethodArg(args, argIdx) : null;
+    }
+    
+    /**
+     * Gets a method argument and checks if it's a string; it does NOT check if {@code args} is big enough.
+     */
+    protected final String getStringMethodArg(List args, int argIdx)
+            throws TemplateModelException {
+        TemplateModel arg = (TemplateModel) args.get(argIdx);
+        if (!(arg instanceof TemplateScalarModel)) {
+            throw MessageUtil.newMethodArgMustBeStringException("?" + key, argIdx, arg);
+        } else {
+            return EvalUtil.modelToString((TemplateScalarModel) arg, null, null);
+        }
+    }
+
+    /**
+     * Gets a method argument and checks if it's a number; it does NOT check if {@code args} is big enough.
+     */
+    protected final Number getNumberMethodArg(List args, int argIdx)
+            throws TemplateModelException {
+        TemplateModel arg = (TemplateModel) args.get(argIdx);
+        if (!(arg instanceof TemplateNumberModel)) {
+            throw MessageUtil.newMethodArgMustBeNumberException("?" + key, argIdx, arg);
+        } else {
+            return EvalUtil.modelToNumber((TemplateNumberModel) arg, null);
+        }
+    }
+    
+    protected final TemplateModelException newMethodArgInvalidValueException(int argIdx, Object[] details) {
+        return MessageUtil.newMethodArgInvalidValueException("?" + key, argIdx, details);
+    }
+
+    protected final TemplateModelException newMethodArgsInvalidValueException(Object[] details) {
+        return MessageUtil.newMethodArgsInvalidValueException("?" + key, details);
+    }
+    
+    @Override
+    protected Expression deepCloneWithIdentifierReplaced_inner(
+            String replacedIdentifier, Expression replacement, ReplacemenetState replacementState) {
+    	try {
+	    	BuiltIn clone = (BuiltIn) clone();
+	    	clone.target = target.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState);
+	    	return clone;
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException("Internal error: " + e);
+        }
+    }
+
+    @Override
+    int getParameterCount() {
+        return 2;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return target;
+        case 1: return key;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.LEFT_HAND_OPERAND;
+        case 1: return ParameterRole.RIGHT_HAND_OPERAND;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInBannedWhenAutoEscaping.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInBannedWhenAutoEscaping.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInBannedWhenAutoEscaping.java
new file mode 100644
index 0000000..c8701cf
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BuiltInBannedWhenAutoEscaping.java
@@ -0,0 +1,27 @@
+/*
+ * 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.ast;
+
+/**
+ * A string built-in whose usage is banned when auto-escaping with a markup-output format is active.
+ * This is just a marker; the actual checking is in {@code FTL.jj}. 
+ */
+abstract class BuiltInBannedWhenAutoEscaping extends SpecialBuiltIn {
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInForDate.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInForDate.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInForDate.java
new file mode 100644
index 0000000..9dc8ee6
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BuiltInForDate.java
@@ -0,0 +1,57 @@
+/*
+ * 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.ast;
+
+import java.util.Date;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+abstract class BuiltInForDate extends BuiltIn {
+    @Override
+    TemplateModel _eval(Environment env)
+            throws TemplateException {
+        TemplateModel model = target.eval(env);
+        if (model instanceof TemplateDateModel) {
+            TemplateDateModel tdm = (TemplateDateModel) model;
+            return calculateResult(EvalUtil.modelToDate(tdm, target), tdm.getDateType(), env);
+        } else {
+            throw newNonDateException(env, model, target);
+        }
+    }
+
+    /** Override this to implement the built-in. */
+    protected abstract TemplateModel calculateResult(
+            Date date, int dateType, Environment env)
+    throws TemplateException;
+    
+    static TemplateException newNonDateException(Environment env, TemplateModel model, Expression target)
+            throws InvalidReferenceException {
+        TemplateException e;
+        if (model == null) {
+            e = InvalidReferenceException.getInstance(target, env);
+        } else {
+            e = new NonDateException(target, model, "date", env);
+        }
+        return e;
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInForHashEx.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInForHashEx.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInForHashEx.java
new file mode 100644
index 0000000..f67925f
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BuiltInForHashEx.java
@@ -0,0 +1,56 @@
+/*
+ * 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.ast;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+abstract class BuiltInForHashEx extends BuiltIn {
+
+    @Override
+    TemplateModel _eval(Environment env) throws TemplateException {
+        TemplateModel model = target.eval(env);
+        if (model instanceof TemplateHashModelEx) {
+            return calculateResult((TemplateHashModelEx) model, env);
+        }
+        throw new NonExtendedHashException(target, model, env);
+    }
+    
+    abstract TemplateModel calculateResult(TemplateHashModelEx hashExModel, Environment env)
+            throws TemplateModelException, InvalidReferenceException;
+    
+    protected InvalidReferenceException newNullPropertyException(
+            String propertyName, TemplateModel tm, Environment env) {
+        if (env.getFastInvalidReferenceExceptions()) {
+            return InvalidReferenceException.FAST_INSTANCE;
+        } else {
+            return new InvalidReferenceException(
+                    new _ErrorDescriptionBuilder(
+                        "The exteneded hash (of class ", tm.getClass().getName(), ") has returned null for its \"",
+                        propertyName,
+                        "\" property. This is maybe a bug. The extended hash was returned by this expression:")
+                    .blame(target),
+                    env, this);
+        }
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInForLegacyEscaping.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInForLegacyEscaping.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInForLegacyEscaping.java
new file mode 100644
index 0000000..4c79a9c
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BuiltInForLegacyEscaping.java
@@ -0,0 +1,48 @@
+/*
+ * 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.ast;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * A string built-in whose usage is banned when auto-escaping with a markup-output format is active.
+ * This is just a marker; the actual checking is in {@code FTL.jj}. 
+ */
+abstract class BuiltInForLegacyEscaping extends BuiltInBannedWhenAutoEscaping {
+    
+    @Override
+    TemplateModel _eval(Environment env)
+    throws TemplateException {
+        TemplateModel tm = target.eval(env);
+        Object moOrStr = EvalUtil.coerceModelToStringOrMarkup(tm, target, null, env);
+        if (moOrStr instanceof String) {
+            return calculateResult((String) moOrStr, env);
+        } else {
+            TemplateMarkupOutputModel<?> mo = (TemplateMarkupOutputModel<?>) moOrStr;
+            if (mo.getOutputFormat().isLegacyBuiltInBypassed(key)) {
+                return mo;
+            }
+            throw new NonStringException(target, tm, env);
+        }
+    }
+    
+    abstract TemplateModel calculateResult(String s, Environment env) throws TemplateException;
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInForLoopVariable.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInForLoopVariable.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInForLoopVariable.java
new file mode 100644
index 0000000..a55f82c
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BuiltInForLoopVariable.java
@@ -0,0 +1,49 @@
+/*
+ * 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.ast;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.ast.IteratorBlock.IterationContext;
+import org.apache.freemarker.core.model.TemplateModel;
+
+abstract class BuiltInForLoopVariable extends SpecialBuiltIn {
+    
+    private String loopVarName;
+    
+    void bindToLoopVariable(String loopVarName) {
+        this.loopVarName = loopVarName;
+    }
+    
+    @Override
+    TemplateModel _eval(Environment env) throws TemplateException {
+        IterationContext iterCtx = IteratorBlock.findEnclosingIterationContext(env, loopVarName);
+        if (iterCtx == null) {
+            // The parser should prevent this situation
+            throw new _MiscTemplateException(
+                    this, env,
+                    "There's no iteration in context that uses loop variable ", new _DelayedJQuote(loopVarName), ".");
+        }
+        
+        return calculateResult(iterCtx, env);
+    }
+
+    abstract TemplateModel calculateResult(IterationContext iterCtx, Environment env) throws TemplateException;
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInForMarkupOutput.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInForMarkupOutput.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInForMarkupOutput.java
new file mode 100644
index 0000000..15f0f4b
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ast/BuiltInForMarkupOutput.java
@@ -0,0 +1,40 @@
+/*
+ * 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.ast;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+abstract class BuiltInForMarkupOutput extends BuiltIn {
+    
+    @Override
+    TemplateModel _eval(Environment env)
+            throws TemplateException {
+        TemplateModel model = target.eval(env);
+        if (!(model instanceof TemplateMarkupOutputModel)) {
+            throw new NonMarkupOutputException(target, model, env);
+        }
+        return calculateResult((TemplateMarkupOutputModel) model);
+    }
+    
+    protected abstract TemplateModel calculateResult(TemplateMarkupOutputModel model) throws TemplateModelException;
+    
+}
\ No newline at end of file



Mime
View raw message