freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [50/54] [partial] incubator-freemarker git commit: Unifying the o.a.f.core and o.a.f.core.ast
Date Thu, 23 Feb 2017 21:36:17 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java b/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java
new file mode 100644
index 0000000..10e60c9
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java
@@ -0,0 +1,75 @@
+/*
+ * 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 java.io.IOException;
+
+/**
+ * AST directive node: {@code #else} inside a {@code  #list}.
+ */
+final class ASTDirElseOfList extends _ASTElement {
+    
+    ASTDirElseOfList(TemplateElements children) {
+        setChildren(children);
+    }
+
+    @Override
+    _ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        if (canonical) {
+            StringBuilder buf = new StringBuilder();
+            buf.append('<').append(getNodeTypeSymbol()).append('>');
+            buf.append(getChildrenCanonicalForm());            
+            return buf.toString();
+        } else {
+            return getNodeTypeSymbol();
+        }
+    }
+
+    @Override
+    String getNodeTypeSymbol() {
+        return "#else";
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirEscape.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirEscape.java b/src/main/java/org/apache/freemarker/core/ASTDirEscape.java
new file mode 100644
index 0000000..721ddf6
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirEscape.java
@@ -0,0 +1,111 @@
+/*
+ * 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 java.io.IOException;
+
+import org.apache.freemarker.core.ASTExpression.ReplacemenetState;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * AST directive node: {@code #escape}.
+ */
+class ASTDirEscape extends _ASTElement {
+
+    private final String variable;
+    private final ASTExpression expr;
+    private ASTExpression escapedExpr;
+
+
+    ASTDirEscape(String variable, ASTExpression expr, ASTExpression escapedExpr) {
+        this.variable = variable;
+        this.expr = expr;
+        this.escapedExpr = escapedExpr;
+    }
+
+    void setContent(TemplateElements children) {
+        setChildren(children);
+        // We don't need it anymore at this point
+        escapedExpr = null;
+    }
+
+    @Override
+    _ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
+    }
+
+    ASTExpression doEscape(ASTExpression expression) {
+        return escapedExpr.deepCloneWithIdentifierReplaced(variable, expression, new ReplacemenetState());
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder sb = new StringBuilder();
+        if (canonical) sb.append('<');
+        sb.append(getNodeTypeSymbol())
+                .append(' ').append(_StringUtil.toFTLTopLevelIdentifierReference(variable))
+                .append(" as ").append(expr.getCanonicalForm());
+        if (canonical) {
+            sb.append('>');
+            sb.append(getChildrenCanonicalForm());
+            sb.append("</").append(getNodeTypeSymbol()).append('>');
+        }
+        return sb.toString();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#escape";
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 2;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return variable;
+        case 1: return expr;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.PLACEHOLDER_VARIABLE;
+        case 1: return ParameterRole.EXPRESSION_TEMPLATE;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }    
+
+    @Override
+    boolean isOutputCacheable() {
+        return true;
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirFallback.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirFallback.java b/src/main/java/org/apache/freemarker/core/ASTDirFallback.java
new file mode 100644
index 0000000..2318325
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirFallback.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;
+
+import java.io.IOException;
+
+/**
+ * AST directive node: {@code #fallback}.
+ */
+final class ASTDirFallback extends _ASTElement {
+
+    @Override
+    _ASTElement[] accept(Environment env) throws IOException, TemplateException {
+        env.fallback();
+        return null;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        return canonical ? "<" + getNodeTypeSymbol() + "/>" : getNodeTypeSymbol();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#fallback";
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirFlush.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirFlush.java b/src/main/java/org/apache/freemarker/core/ASTDirFlush.java
new file mode 100644
index 0000000..ed06d73
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirFlush.java
@@ -0,0 +1,65 @@
+/*
+ * 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 java.io.IOException;
+
+/**
+ * AST directive node: {@code #flush} 
+ */
+final class ASTDirFlush extends _ASTElement {
+
+    @Override
+    _ASTElement[] accept(Environment env) throws IOException {
+        env.getOut().flush();
+        return null;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        return canonical ? "<" + getNodeTypeSymbol() + "/>" : getNodeTypeSymbol();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#flush";
+    }
+ 
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirIfElseIfElseContainer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirIfElseIfElseContainer.java b/src/main/java/org/apache/freemarker/core/ASTDirIfElseIfElseContainer.java
new file mode 100644
index 0000000..fc803e6
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirIfElseIfElseContainer.java
@@ -0,0 +1,107 @@
+/*
+ * 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 java.io.IOException;
+
+/**
+ * AST directive node: Container for a group of related {@code #if}, {@code #elseif} and {@code #else} directives.
+ * Each such block is a nested {@link ASTDirIfOrElseOrElseIf}. Note that if an {@code #if} stands alone,
+ * {@link ASTDirIfOrElseOrElseIf} doesn't need this parent element.
+ */
+final class ASTDirIfElseIfElseContainer extends _ASTElement {
+
+    ASTDirIfElseIfElseContainer(ASTDirIfOrElseOrElseIf block) {
+        setChildBufferCapacity(1);
+        addBlock(block);
+    }
+
+    void addBlock(ASTDirIfOrElseOrElseIf block) {
+        addChild(block);
+    }
+
+    @Override
+    _ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        int ln  = getChildCount();
+        for (int i = 0; i < ln; i++) {
+            ASTDirIfOrElseOrElseIf cblock = (ASTDirIfOrElseOrElseIf) getChild(i);
+            ASTExpression condition = cblock.condition;
+            env.replaceElementStackTop(cblock);
+            if (condition == null || condition.evalToBoolean(env)) {
+                return cblock.getChildBuffer();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    _ASTElement postParseCleanup(boolean stripWhitespace)
+        throws ParseException {
+        if (getChildCount() == 1) {
+            ASTDirIfOrElseOrElseIf cblock = (ASTDirIfOrElseOrElseIf) getChild(0);
+            cblock.setLocation(getTemplate(), cblock, this);
+            return cblock.postParseCleanup(stripWhitespace);
+        } else {
+            return super.postParseCleanup(stripWhitespace);
+        }
+    }
+    
+    @Override
+    protected String dump(boolean canonical) {
+        if (canonical) {
+            StringBuilder buf = new StringBuilder();
+            int ln = getChildCount();
+            for (int i = 0; i < ln; i++) {
+                ASTDirIfOrElseOrElseIf cblock = (ASTDirIfOrElseOrElseIf) getChild(i);
+                buf.append(cblock.dump(canonical));
+            }
+            buf.append("</#if>");
+            return buf.toString();
+        } else {
+            return getNodeTypeSymbol();
+        }
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#if-#elseif-#else-container";
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirIfOrElseOrElseIf.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirIfOrElseOrElseIf.java b/src/main/java/org/apache/freemarker/core/ASTDirIfOrElseOrElseIf.java
new file mode 100644
index 0000000..d604fd5
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirIfOrElseOrElseIf.java
@@ -0,0 +1,114 @@
+/*
+ * 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 java.io.IOException;
+
+import org.apache.freemarker.core.util.BugException;
+
+/**
+ * AST directive node: An element that represents a conditionally executed block: {@code #if}, {@code #elseif} or
+ * {@code #elseif}. Note that when an {@code #if} has related {@code #elseif}-s or {@code #else}, an
+ * {@link ASTDirIfElseIfElseContainer} parent must be used. For a lonely {@code #if}, no such parent is needed. 
+ */
+final class ASTDirIfOrElseOrElseIf extends _ASTElement {
+
+    static final int TYPE_IF = 0;
+    static final int TYPE_ELSE = 1;
+    static final int TYPE_ELSE_IF = 2;
+    
+    final ASTExpression condition;
+    private final int type;
+
+    ASTDirIfOrElseOrElseIf(ASTExpression condition, TemplateElements children, int type) {
+        this.condition = condition;
+        setChildren(children);
+        this.type = type;
+    }
+
+    @Override
+    _ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        if (condition == null || condition.evalToBoolean(env)) {
+            return getChildBuffer();
+        }
+        return null;
+    }
+    
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder buf = new StringBuilder();
+        if (canonical) buf.append('<');
+        buf.append(getNodeTypeSymbol());
+        if (condition != null) {
+            buf.append(' ');
+            buf.append(condition.getCanonicalForm());
+        }
+        if (canonical) {
+            buf.append(">");
+            buf.append(getChildrenCanonicalForm());
+            if (!(getParentElement() instanceof ASTDirIfElseIfElseContainer)) {
+                buf.append("</#if>");
+            }
+        }
+        return buf.toString();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        if (type == TYPE_ELSE) {
+            return "#else";
+        } else if (type == TYPE_IF) {
+            return "#if";
+        } else if (type == TYPE_ELSE_IF) {
+            return "#elseif";
+        } else {
+            throw new BugException("Unknown type");
+        }
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 2;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return condition;
+        case 1: return Integer.valueOf(type);
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.CONDITION;
+        case 1: return ParameterRole.AST_NODE_SUBTYPE;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirImport.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirImport.java b/src/main/java/org/apache/freemarker/core/ASTDirImport.java
new file mode 100644
index 0000000..fb5b3c9
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirImport.java
@@ -0,0 +1,125 @@
+/*
+ * 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 java.io.IOException;
+
+import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * AST directive node: {@code #import}
+ */
+final class ASTDirImport extends _ASTElement {
+
+    private ASTExpression importedTemplateNameExp;
+    private String targetNsVarName;
+
+    /**
+     * @param template the template that this directive is a part of.
+     * @param importedTemplateNameExp the name of the template to be included.
+     * @param targetNsVarName the name of the  variable to assign this library's namespace to
+     */
+    ASTDirImport(Template template,
+            ASTExpression importedTemplateNameExp,
+            String targetNsVarName) {
+        this.targetNsVarName = targetNsVarName;
+        this.importedTemplateNameExp = importedTemplateNameExp;
+    }
+
+    @Override
+    _ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        final String importedTemplateName = importedTemplateNameExp.evalAndCoerceToPlainText(env);
+        final String fullImportedTemplateName;
+        try {
+            fullImportedTemplateName = env.toFullTemplateName(getTemplate().getName(), importedTemplateName);
+        } catch (MalformedTemplateNameException e) {
+            throw new _MiscTemplateException(e, env,
+                    "Malformed template name ", new _DelayedJQuote(e.getTemplateName()), ":\n",
+                    e.getMalformednessDescription());
+        }
+        
+        try {
+            env.importLib(fullImportedTemplateName, targetNsVarName);
+        } catch (IOException e) {
+            throw new _MiscTemplateException(e, env,
+                    "Template importing failed (for parameter value ",
+                    new _DelayedJQuote(importedTemplateName),
+                    "):\n", new _DelayedGetMessage(e));
+        }
+        return null;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder buf = new StringBuilder();
+        if (canonical) buf.append('<');
+        buf.append(getNodeTypeSymbol());
+        buf.append(' ');
+        buf.append(importedTemplateNameExp.getCanonicalForm());
+        buf.append(" as ");
+        buf.append(_StringUtil.toFTLTopLevelTragetIdentifier(targetNsVarName));
+        if (canonical) buf.append("/>");
+        return buf.toString();
+    }
+
+    @Override
+    String getNodeTypeSymbol() {
+        return "#import";
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 2;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return importedTemplateNameExp;
+        case 1: return targetNsVarName;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.TEMPLATE_NAME;
+        case 1: return ParameterRole.NAMESPACE;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }    
+    
+    public String getTemplateName() {
+        return importedTemplateNameExp.toString();
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirInclude.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirInclude.java b/src/main/java/org/apache/freemarker/core/ASTDirInclude.java
new file mode 100644
index 0000000..4fec5c1
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirInclude.java
@@ -0,0 +1,257 @@
+/*
+ * 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 java.io.IOException;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util._StringUtil;
+
+
+/**
+ * AST directive node: {@code #include} 
+ */
+final class ASTDirInclude extends _ASTElement {
+
+    private final ASTExpression includedTemplateNameExp, encodingExp, parseExp, ignoreMissingExp;
+    private final String encoding;
+    private final Boolean parse;
+    private final Boolean ignoreMissingExpPrecalcedValue;
+
+    /**
+     * @param template the template that this <tt>#include</tt> is a part of.
+     * @param includedTemplateNameExp the path of the template to be included.
+     * @param encodingExp the encoding to be used or null, if it's the default.
+     * @param parseExp whether the template should be parsed (or is raw text)
+     */
+    ASTDirInclude(Template template,
+            ASTExpression includedTemplateNameExp,
+            ASTExpression encodingExp, ASTExpression parseExp, ASTExpression ignoreMissingExp) throws ParseException {
+        this.includedTemplateNameExp = includedTemplateNameExp;
+        
+        this.encodingExp = encodingExp;
+        if (encodingExp == null) {
+            encoding = null;
+        } else {
+            if (encodingExp.isLiteral()) {
+                try {
+                    TemplateModel tm = encodingExp.eval(null);
+                    if (!(tm instanceof TemplateScalarModel)) {
+                        throw new ParseException("Expected a string as the value of the \"encoding\" argument",
+                                encodingExp);
+                    }
+                    encoding = ((TemplateScalarModel) tm).getAsString();
+                } catch (TemplateException e) {
+                    // evaluation of literals must not throw a TemplateException
+                    throw new BugException(e);
+                }
+            } else {
+                encoding = null;
+            }
+        }
+        
+        this.parseExp = parseExp;
+        if (parseExp == null) {
+            parse = Boolean.TRUE;
+        } else {
+            if (parseExp.isLiteral()) {
+                try {
+                    if (parseExp instanceof ASTExpStringLiteral) {
+                        // Legacy
+                        parse = Boolean.valueOf(_StringUtil.getYesNo(parseExp.evalAndCoerceToPlainText(null)));
+                    } else {
+                        try {
+                            parse = Boolean.valueOf(parseExp.evalToBoolean(template.getConfiguration()));
+                        } catch (NonBooleanException e) {
+                            throw new ParseException("Expected a boolean or string as the value of the parse attribute",
+                                    parseExp, e);
+                        }
+                    }
+                } catch (TemplateException e) {
+                    // evaluation of literals must not throw a TemplateException
+                    throw new BugException(e);
+                }
+            } else {
+                parse = null;
+            }
+        }
+        
+        this.ignoreMissingExp = ignoreMissingExp;
+        if (ignoreMissingExp != null && ignoreMissingExp.isLiteral()) {
+            try {
+                try {
+                    ignoreMissingExpPrecalcedValue = Boolean.valueOf(
+                            ignoreMissingExp.evalToBoolean(template.getConfiguration()));
+                } catch (NonBooleanException e) {
+                    throw new ParseException("Expected a boolean as the value of the \"ignore_missing\" attribute",
+                            ignoreMissingExp, e);
+                }
+            } catch (TemplateException e) {
+                // evaluation of literals must not throw a TemplateException
+                throw new BugException(e);
+            }
+        } else {
+            ignoreMissingExpPrecalcedValue = null;
+        }
+    }
+    
+    @Override
+    _ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        final String includedTemplateName = includedTemplateNameExp.evalAndCoerceToPlainText(env);
+        final String fullIncludedTemplateName;
+        try {
+            fullIncludedTemplateName = env.toFullTemplateName(getTemplate().getName(), includedTemplateName);
+        } catch (MalformedTemplateNameException e) {
+            throw new _MiscTemplateException(e, env,
+                    "Malformed template name ", new _DelayedJQuote(e.getTemplateName()), ":\n",
+                    e.getMalformednessDescription());
+        }
+        
+        final String encoding = this.encoding != null
+                ? this.encoding
+                : (encodingExp != null
+                        ? encodingExp.evalAndCoerceToPlainText(env)
+                        : null);
+        
+        final boolean parse;
+        if (this.parse != null) {
+            parse = this.parse.booleanValue();
+        } else {
+            TemplateModel tm = parseExp.eval(env);
+            if (tm instanceof TemplateScalarModel) {
+                // Legacy
+                parse = getYesNo(parseExp, EvalUtil.modelToString((TemplateScalarModel) tm, parseExp, env));
+            } else {
+                parse = parseExp.modelToBoolean(tm, env);
+            }
+        }
+        
+        final boolean ignoreMissing;
+        if (ignoreMissingExpPrecalcedValue != null) {
+            ignoreMissing = ignoreMissingExpPrecalcedValue.booleanValue();
+        } else if (ignoreMissingExp != null) {
+            ignoreMissing = ignoreMissingExp.evalToBoolean(env);
+        } else {
+            ignoreMissing = false;
+        }
+        
+        final Template includedTemplate;
+        try {
+            includedTemplate = env.getTemplateForInclusion(fullIncludedTemplateName, encoding, parse, ignoreMissing);
+        } catch (IOException e) {
+            throw new _MiscTemplateException(e, env,
+                    "Template inclusion failed (for parameter value ",
+                    new _DelayedJQuote(includedTemplateName),
+                    "):\n", new _DelayedGetMessage(e));
+        }
+        
+        if (includedTemplate != null) {
+            env.include(includedTemplate);
+        }
+        return null;
+    }
+    
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder buf = new StringBuilder();
+        if (canonical) buf.append('<');
+        buf.append(getNodeTypeSymbol());
+        buf.append(' ');
+        buf.append(includedTemplateNameExp.getCanonicalForm());
+        if (encodingExp != null) {
+            buf.append(" encoding=").append(encodingExp.getCanonicalForm());
+        }
+        if (parseExp != null) {
+            buf.append(" parse=").append(parseExp.getCanonicalForm());
+        }
+        if (ignoreMissingExp != null) {
+            buf.append(" ignore_missing=").append(ignoreMissingExp.getCanonicalForm());
+        }
+        if (canonical) buf.append("/>");
+        return buf.toString();
+    }
+
+    @Override
+    String getNodeTypeSymbol() {
+        return "#include";
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 3;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return includedTemplateNameExp;
+        case 1: return parseExp;
+        case 2: return encodingExp;
+        case 3: return ignoreMissingExp;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.TEMPLATE_NAME;
+        case 1: return ParameterRole.PARSE_PARAMETER;
+        case 2: return ParameterRole.ENCODING_PARAMETER;
+        case 3: return ParameterRole.IGNORE_MISSING_PARAMETER;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+
+    private boolean getYesNo(ASTExpression exp, String s) throws TemplateException {
+        try {
+           return _StringUtil.getYesNo(s);
+        } catch (IllegalArgumentException iae) {
+            throw new _MiscTemplateException(exp,
+                     "Value must be boolean (or one of these strings: "
+                     + "\"n\", \"no\", \"f\", \"false\", \"y\", \"yes\", \"t\", \"true\"), but it was ",
+                     new _DelayedJQuote(s), ".");
+        }
+    }
+
+/*
+    boolean heedsOpeningWhitespace() {
+        return true;
+    }
+
+    boolean heedsTrailingWhitespace() {
+        return true;
+    }
+*/
+    
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirItems.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirItems.java b/src/main/java/org/apache/freemarker/core/ASTDirItems.java
new file mode 100644
index 0000000..a716c6d
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirItems.java
@@ -0,0 +1,120 @@
+/*
+ * 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 java.io.IOException;
+
+import org.apache.freemarker.core.ASTDirList.IterationContext;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * AST directive node: {@code #items}
+ */
+class ASTDirItems extends _ASTElement {
+
+    private final String loopVarName;
+    private final String loopVar2Name;
+
+    /**
+     * @param loopVar2Name
+     *            For non-hash listings always {@code null}, for hash listings {@code loopVarName} and
+     *            {@code loopVarName2} holds the key- and value loop variable names.
+     */
+    ASTDirItems(String loopVarName, String loopVar2Name, TemplateElements children) {
+        this.loopVarName = loopVarName;
+        this.loopVar2Name = loopVar2Name;
+        setChildren(children);
+    }
+
+    @Override
+    _ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        final IterationContext iterCtx = ASTDirList.findEnclosingIterationContext(env, null);
+        if (iterCtx == null) {
+            // The parser should prevent this situation
+            throw new _MiscTemplateException(env,
+                    getNodeTypeSymbol(), " without iteration in context");
+        }
+        
+        iterCtx.loopForItemsElement(env, getChildBuffer(), loopVarName, loopVar2Name);
+        return null;
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return true;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder sb = new StringBuilder();
+        if (canonical) sb.append('<');
+        sb.append(getNodeTypeSymbol());
+        sb.append(" as ");
+        sb.append(_StringUtil.toFTLTopLevelIdentifierReference(loopVarName));
+        if (loopVar2Name != null) {
+            sb.append(", ");
+            sb.append(_StringUtil.toFTLTopLevelIdentifierReference(loopVar2Name));
+        }
+        if (canonical) {
+            sb.append('>');
+            sb.append(getChildrenCanonicalForm());
+            sb.append("</");
+            sb.append(getNodeTypeSymbol());
+            sb.append('>');
+        }
+        return sb.toString();
+    }
+
+    @Override
+    String getNodeTypeSymbol() {
+        return "#items";
+    }
+
+    @Override
+    int getParameterCount() {
+        return loopVar2Name != null ? 2 : 1;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0:
+            if (loopVarName == null) throw new IndexOutOfBoundsException();
+            return loopVarName;
+        case 1:
+            if (loopVar2Name == null) throw new IndexOutOfBoundsException();
+            return loopVar2Name;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0:
+            if (loopVarName == null) throw new IndexOutOfBoundsException();
+            return ParameterRole.TARGET_LOOP_VARIABLE;
+        case 1:
+            if (loopVar2Name == null) throw new IndexOutOfBoundsException();
+            return ParameterRole.TARGET_LOOP_VARIABLE;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirList.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirList.java b/src/main/java/org/apache/freemarker/core/ASTDirList.java
new file mode 100644
index 0000000..c56a676
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirList.java
@@ -0,0 +1,476 @@
+/*
+ * 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 java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateHashModelEx2;
+import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePair;
+import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePairIterator;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * AST directive node: {@code #list} (or {@code #foreach}) element, or pre-{@code #else} section of it inside a
+ * {@link ASTDirListElseContainer}.
+ */
+final class ASTDirList extends _ASTElement {
+
+    private final ASTExpression listedExp;
+    private final String loopVarName;
+    private final String loopVar2Name;
+    private final boolean hashListing;
+    private final boolean forEach;
+
+    /**
+     * @param listedExp
+     *            a variable referring to a sequence or collection or extended hash to list
+     * @param loopVarName
+     *            The name of the variable that will hold the value of the current item when looping through listed value,
+     *            or {@code null} if we have a nested {@code #items}. If this is a hash listing then this variable will holds the value
+     *            of the hash key.
+     * @param loopVar2Name
+     *            The name of the variable that will hold the value of the current item when looping through the list,
+     *            or {@code null} if we have a nested {@code #items}. If this is a hash listing then it variable will hold the value
+     *            from the key-value pair.
+     * @param childrenBeforeElse
+     *            The nested content to execute if the listed value wasn't empty; can't be {@code null}. If the loop variable
+     *            was specified in the start tag, this is also what we will iterate over.
+     * @param hashListing
+     *            Whether this is a key-value pair listing, or a usual listing. This is properly set even if we have
+     *            a nested {@code #items}.
+     * @param forEach
+     *            Whether this is {@code #foreach} or a {@code #list}.
+     */
+    ASTDirList(ASTExpression listedExp,
+                  String loopVarName,
+                  String loopVar2Name,
+                  TemplateElements childrenBeforeElse,
+                  boolean hashListing,
+                  boolean forEach) {
+        this.listedExp = listedExp;
+        this.loopVarName = loopVarName;
+        this.loopVar2Name = loopVar2Name;
+        setChildren(childrenBeforeElse);
+        this.hashListing = hashListing;
+        this.forEach = forEach;
+    }
+    
+    boolean isHashListing() {
+        return hashListing;
+    }
+
+    @Override
+    _ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        acceptWithResult(env);
+        return null;
+    }
+    
+    boolean acceptWithResult(Environment env) throws TemplateException, IOException {
+        TemplateModel listedValue = listedExp.eval(env);
+        if (listedValue == null) {
+            listedExp.assertNonNull(null, env);
+        }
+
+        return env.visitIteratorBlock(new IterationContext(listedValue, loopVarName, loopVar2Name));
+    }
+
+    /**
+     * @param loopVariableName
+     *            Then name of the loop variable whose context we are looking for, or {@code null} if we simply look for
+     *            the innermost context.
+     * @return The matching context or {@code null} if no such context exists.
+     */
+    static IterationContext findEnclosingIterationContext(Environment env, String loopVariableName)
+            throws _MiscTemplateException {
+        LocalContextStack ctxStack = env.getLocalContextStack();
+        if (ctxStack != null) {
+            for (int i = ctxStack.size() - 1; i >= 0; i--) {
+                Object ctx = ctxStack.get(i);
+                if (ctx instanceof IterationContext
+                        && (loopVariableName == null
+                            || loopVariableName.equals(((IterationContext) ctx).getLoopVariableName())
+                            || loopVariableName.equals(((IterationContext) ctx).getLoopVariable2Name())
+                            )) {
+                    return (IterationContext) ctx;
+                }
+            }
+        }
+        return null;
+    }
+    
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder buf = new StringBuilder();
+        if (canonical) buf.append('<');
+        buf.append(getNodeTypeSymbol());
+        buf.append(' ');
+        if (forEach) {
+            buf.append(_StringUtil.toFTLTopLevelIdentifierReference(loopVarName));
+            buf.append(" in ");
+            buf.append(listedExp.getCanonicalForm());
+        } else {
+            buf.append(listedExp.getCanonicalForm());
+            if (loopVarName != null) {
+                buf.append(" as ");
+                buf.append(_StringUtil.toFTLTopLevelIdentifierReference(loopVarName));
+                if (loopVar2Name != null) {
+                    buf.append(", ");
+                    buf.append(_StringUtil.toFTLTopLevelIdentifierReference(loopVar2Name));
+                }
+            }
+        }
+        if (canonical) {
+            buf.append(">");
+            buf.append(getChildrenCanonicalForm());
+            if (!(getParentElement() instanceof ASTDirListElseContainer)) {
+                buf.append("</");
+                buf.append(getNodeTypeSymbol());
+                buf.append('>');
+            }
+        }
+        return buf.toString();
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 1 + (loopVarName != null ? 1 : 0) + (loopVar2Name != null ? 1 : 0);
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0:
+            return listedExp;
+        case 1:
+            if (loopVarName == null) throw new IndexOutOfBoundsException();
+            return loopVarName;
+        case 2:
+            if (loopVar2Name == null) throw new IndexOutOfBoundsException();
+            return loopVar2Name;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0:
+            return ParameterRole.LIST_SOURCE;
+        case 1:
+            if (loopVarName == null) throw new IndexOutOfBoundsException();
+            return ParameterRole.TARGET_LOOP_VARIABLE;
+        case 2:
+            if (loopVar2Name == null) throw new IndexOutOfBoundsException();
+            return ParameterRole.TARGET_LOOP_VARIABLE;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }    
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return forEach ? "#foreach" : "#list";
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return loopVarName != null;
+    }
+
+    /**
+     * Holds the context of a #list (or #forEach) directive.
+     */
+    class IterationContext implements LocalContext {
+        
+        private static final String LOOP_STATE_HAS_NEXT = "_has_next"; // lenght: 9
+        private static final String LOOP_STATE_INDEX = "_index"; // length 6
+        
+        private Object openedIterator;
+        private boolean hasNext;
+        private TemplateModel loopVar;
+        private TemplateModel loopVar2;
+        private int index;
+        private boolean alreadyEntered;
+        private Collection localVarNames = null;
+        
+        /** If the {@code #list} has nested {@code #items}, it's {@code null} outside the {@code #items}. */
+        private String loopVarName;
+        /** Used if we list key-value pairs */
+        private String loopVar2Name;
+        
+        private final TemplateModel listedValue;
+        
+        public IterationContext(TemplateModel listedValue, String loopVarName, String loopVar2Name) {
+            this.listedValue = listedValue;
+            this.loopVarName = loopVarName;
+            this.loopVar2Name = loopVar2Name;
+        }
+        
+        boolean accept(Environment env) throws TemplateException, IOException {
+            return executeNestedContent(env, getChildBuffer());
+        }
+
+        void loopForItemsElement(Environment env, _ASTElement[] childBuffer, String loopVarName, String loopVar2Name)
+                    throws NonSequenceOrCollectionException, TemplateModelException, InvalidReferenceException,
+                    TemplateException, IOException {
+            try {
+                if (alreadyEntered) {
+                    throw new _MiscTemplateException(env,
+                            "The #items directive was already entered earlier for this listing.");
+                }
+                alreadyEntered = true;
+                this.loopVarName = loopVarName;
+                this.loopVar2Name = loopVar2Name;
+                executeNestedContent(env, childBuffer);
+            } finally {
+                this.loopVarName = null;
+                this.loopVar2Name = null;
+            }
+        }
+
+        /**
+         * Executes the given block for the {@link #listedValue}: if {@link #loopVarName} is non-{@code null}, then for
+         * each list item once, otherwise once if {@link #listedValue} isn't empty.
+         */
+        private boolean executeNestedContent(Environment env, _ASTElement[] childBuffer)
+                throws TemplateModelException, TemplateException, IOException, NonSequenceOrCollectionException,
+                InvalidReferenceException {
+            return !hashListing
+                    ? executedNestedContentForCollOrSeqListing(env, childBuffer)
+                    : executedNestedContentForHashListing(env, childBuffer);
+        }
+
+        private boolean executedNestedContentForCollOrSeqListing(Environment env, _ASTElement[] childBuffer)
+                throws TemplateModelException, IOException, TemplateException,
+                NonSequenceOrCollectionException, InvalidReferenceException {
+            final boolean listNotEmpty;
+            if (listedValue instanceof TemplateCollectionModel) {
+                final TemplateCollectionModel collModel = (TemplateCollectionModel) listedValue;
+                final TemplateModelIterator iterModel
+                        = openedIterator == null ? collModel.iterator()
+                                : ((TemplateModelIterator) openedIterator);
+                listNotEmpty = iterModel.hasNext();
+                if (listNotEmpty) {
+                    if (loopVarName != null) {
+                        try {
+                            do {
+                                loopVar = iterModel.next();
+                                hasNext = iterModel.hasNext();
+                                env.visit(childBuffer);
+                                index++;
+                            } while (hasNext);
+                        } catch (ASTDirBreak.Break br) {
+                            // Silently exit loop
+                        }
+                        openedIterator = null;
+                    } else {
+                        // We must reuse this later, because TemplateCollectionModel-s that wrap an Iterator only
+                        // allow one iterator() call.
+                        openedIterator = iterModel;
+                        env.visit(childBuffer);
+                    }
+                }
+            } else if (listedValue instanceof TemplateSequenceModel) {
+                final TemplateSequenceModel seqModel = (TemplateSequenceModel) listedValue;
+                final int size = seqModel.size();
+                listNotEmpty = size != 0;
+                if (listNotEmpty) {
+                    if (loopVarName != null) {
+                        try {
+                            for (index = 0; index < size; index++) {
+                                loopVar = seqModel.get(index);
+                                hasNext = (size > index + 1);
+                                env.visit(childBuffer);
+                            }
+                        } catch (ASTDirBreak.Break br) {
+                            // Silently exit loop
+                        }
+                    } else {
+                        env.visit(childBuffer);
+                    }
+                }
+            } else if (listedValue instanceof TemplateHashModelEx
+                    && !NonSequenceOrCollectionException.isWrappedIterable(listedValue)) {
+                throw new NonSequenceOrCollectionException(env,
+                        new _ErrorDescriptionBuilder("The value you try to list is ",
+                                new _DelayedAOrAn(new _DelayedFTLTypeDescription(listedValue)),
+                                ", thus you must specify two loop variables after the \"as\"; one for the key, and "
+                                + "another for the value, like ", "<#... as k, v>", ")."
+                                ));
+            } else {
+                throw new NonSequenceOrCollectionException(
+                        listedExp, listedValue, env);
+            }
+            return listNotEmpty;
+        }
+
+        private boolean executedNestedContentForHashListing(Environment env, _ASTElement[] childBuffer)
+                throws TemplateModelException, IOException, TemplateException {
+            final boolean hashNotEmpty;
+            if (listedValue instanceof TemplateHashModelEx) {
+                TemplateHashModelEx listedHash = (TemplateHashModelEx) listedValue; 
+                if (listedHash instanceof TemplateHashModelEx2) {
+                    KeyValuePairIterator kvpIter
+                            = openedIterator == null ? ((TemplateHashModelEx2) listedHash).keyValuePairIterator()
+                                    : (KeyValuePairIterator) openedIterator;
+                    hashNotEmpty = kvpIter.hasNext();
+                    if (hashNotEmpty) {
+                        if (loopVarName != null) {
+                            try {
+                                do {
+                                    KeyValuePair kvp = kvpIter.next();
+                                    loopVar = kvp.getKey();
+                                    loopVar2 = kvp.getValue();
+                                    hasNext = kvpIter.hasNext();
+                                    env.visit(childBuffer);
+                                    index++;
+                                } while (hasNext);
+                            } catch (ASTDirBreak.Break br) {
+                                // Silently exit loop
+                            }
+                            openedIterator = null;
+                        } else {
+                            // We will reuse this at the #iterms
+                            openedIterator = kvpIter;
+                            env.visit(childBuffer);
+                        }
+                    }
+                } else { //  not a TemplateHashModelEx2, but still a TemplateHashModelEx
+                    TemplateModelIterator keysIter = listedHash.keys().iterator();
+                    hashNotEmpty = keysIter.hasNext();
+                    if (hashNotEmpty) {
+                        if (loopVarName != null) {
+                            try {
+                                do {
+                                    loopVar = keysIter.next();
+                                    if (!(loopVar instanceof TemplateScalarModel)) {
+                                        throw new NonStringException(env,
+                                                new _ErrorDescriptionBuilder(
+                                                        "When listing key-value pairs of traditional hash "
+                                                        + "implementations, all keys must be strings, but one of them "
+                                                        + "was ",
+                                                        new _DelayedAOrAn(new _DelayedFTLTypeDescription(loopVar)), "."
+                                                        ).tip("The listed value's TemplateModel class was ",
+                                                                new _DelayedShortClassName(listedValue.getClass()),
+                                                                ", which doesn't implement ",
+                                                                new _DelayedShortClassName(TemplateHashModelEx2.class),
+                                                                ", which leads to this restriction."));
+                                    }
+                                    loopVar2 = listedHash.get(((TemplateScalarModel) loopVar).getAsString());
+                                    hasNext = keysIter.hasNext();
+                                    env.visit(childBuffer);
+                                    index++;
+                                } while (hasNext);
+                            } catch (ASTDirBreak.Break br) {
+                                // Silently exit loop
+                            }
+                        } else {
+                            env.visit(childBuffer);
+                        }
+                    }
+                }
+            } else if (listedValue instanceof TemplateCollectionModel
+                    || listedValue instanceof TemplateSequenceModel) {
+                throw new NonSequenceOrCollectionException(env,
+                        new _ErrorDescriptionBuilder("The value you try to list is ",
+                                new _DelayedAOrAn(new _DelayedFTLTypeDescription(listedValue)),
+                                ", thus you must specify only one loop variable after the \"as\" (there's no separate "
+                                + "key and value)."
+                                ));
+            } else {
+                throw new NonExtendedHashException(
+                        listedExp, listedValue, env);
+            }
+            return hashNotEmpty;
+        }
+
+        String getLoopVariableName() {
+            return loopVarName;
+        }
+
+        String getLoopVariable2Name() {
+            return loopVar2Name;
+        }
+        
+        @Override
+        public TemplateModel getLocalVariable(String name) {
+            String loopVarName = this.loopVarName;
+            if (loopVarName != null && name.startsWith(loopVarName)) {
+                switch(name.length() - loopVarName.length()) {
+                    case 0: 
+                        return loopVar;
+                    case 6: 
+                        if (name.endsWith(LOOP_STATE_INDEX)) {
+                            return new SimpleNumber(index);
+                        }
+                        break;
+                    case 9: 
+                        if (name.endsWith(LOOP_STATE_HAS_NEXT)) {
+                            return hasNext ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+                        }
+                        break;
+                }
+            }
+            
+            if (name.equals(loopVar2Name)) {
+                return loopVar2;
+            }
+            
+            return null;
+        }
+        
+        @Override
+        public Collection getLocalVariableNames() {
+            String loopVarName = this.loopVarName;
+            if (loopVarName != null) {
+                if (localVarNames == null) {
+                    localVarNames = new ArrayList(3);
+                    localVarNames.add(loopVarName);
+                    localVarNames.add(loopVarName + LOOP_STATE_INDEX);
+                    localVarNames.add(loopVarName + LOOP_STATE_HAS_NEXT);
+                }
+                return localVarNames;
+            } else {
+                return Collections.EMPTY_LIST;
+            }
+        }
+
+        boolean hasNext() {
+            return hasNext;
+        }
+        
+        int getIndex() {
+            return index;
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirListElseContainer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirListElseContainer.java b/src/main/java/org/apache/freemarker/core/ASTDirListElseContainer.java
new file mode 100644
index 0000000..dd3985a
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirListElseContainer.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;
+
+import java.io.IOException;
+
+/**
+ * AST directive node: When a {@code #list} has an {@code #else}, this is the parent of the two nodes.
+ */
+class ASTDirListElseContainer extends _ASTElement {
+
+    private final ASTDirList listPart;
+    private final ASTDirElseOfList elsePart;
+
+    public ASTDirListElseContainer(ASTDirList listPart, ASTDirElseOfList elsePart) {
+        setChildBufferCapacity(2);
+        addChild(listPart);
+        addChild(elsePart);
+        this.listPart = listPart;
+        this.elsePart = elsePart;
+    }
+
+    @Override
+    _ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        if (!listPart.acceptWithResult(env)) {
+            return elsePart.accept(env);
+        }
+        return null;
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        if (canonical) {
+            StringBuilder buf = new StringBuilder();
+            int ln = getChildCount();
+            for (int i = 0; i < ln; i++) {
+                _ASTElement element = getChild(i);
+                buf.append(element.dump(canonical));
+            }
+            buf.append("</#list>");
+            return buf.toString();
+        } else {
+            return getNodeTypeSymbol();
+        }
+    }
+
+    @Override
+    String getNodeTypeSymbol() {
+        return "#list-#else-container";
+    }
+
+    @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/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirMacro.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirMacro.java b/src/main/java/org/apache/freemarker/core/ASTDirMacro.java
new file mode 100644
index 0000000..4b22428
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirMacro.java
@@ -0,0 +1,325 @@
+/*
+ * 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 java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * AST directive node: {@code #macro}
+ */
+final class ASTDirMacro extends _ASTElement implements TemplateModel {
+
+    static final ASTDirMacro DO_NOTHING_MACRO = new ASTDirMacro(".pass", 
+            Collections.EMPTY_LIST, 
+            Collections.EMPTY_MAP,
+            null, false,
+            TemplateElements.EMPTY);
+    
+    final static int TYPE_MACRO = 0;
+    final static int TYPE_FUNCTION = 1;
+    
+    private final String name;
+    private final String[] paramNames;
+    private final Map paramDefaults;
+    private final String catchAllParamName;
+    private final boolean function;
+
+    ASTDirMacro(String name, List argumentNames, Map args, 
+            String catchAllParamName, boolean function,
+            TemplateElements children) {
+        this.name = name;
+        paramNames = (String[]) argumentNames.toArray(new String[argumentNames.size()]);
+        paramDefaults = args;
+        
+        this.function = function;
+        this.catchAllParamName = catchAllParamName;
+
+        setChildren(children);
+    }
+
+    public String getCatchAll() {
+        return catchAllParamName;
+    }
+    
+    public String[] getArgumentNames() {
+        return paramNames.clone();
+    }
+
+    String[] getArgumentNamesInternal() {
+        return paramNames;
+    }
+
+    boolean hasArgNamed(String name) {
+        return paramDefaults.containsKey(name);
+    }
+    
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    _ASTElement[] accept(Environment env) {
+        env.visitMacroDef(this);
+        return null;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder sb = new StringBuilder();
+        if (canonical) sb.append('<');
+        sb.append(getNodeTypeSymbol());
+        sb.append(' ');
+        sb.append(_StringUtil.toFTLTopLevelTragetIdentifier(name));
+        if (function) sb.append('(');
+        int argCnt = paramNames.length;
+        for (int i = 0; i < argCnt; i++) {
+            if (function) {
+                if (i != 0) {
+                    sb.append(", ");
+                }
+            } else {
+                sb.append(' ');
+            }
+            String argName = paramNames[i];
+            sb.append(_StringUtil.toFTLTopLevelIdentifierReference(argName));
+            if (paramDefaults != null && paramDefaults.get(argName) != null) {
+                sb.append('=');
+                ASTExpression defaultExpr = (ASTExpression) paramDefaults.get(argName);
+                if (function) {
+                    sb.append(defaultExpr.getCanonicalForm());
+                } else {
+                    MessageUtil.appendExpressionAsUntearable(sb, defaultExpr);
+                }
+            }
+        }
+        if (catchAllParamName != null) {
+            if (function) {
+                if (argCnt != 0) {
+                    sb.append(", ");
+                }
+            } else {
+                sb.append(' ');
+            }
+            sb.append(catchAllParamName);
+            sb.append("...");
+        }
+        if (function) sb.append(')');
+        if (canonical) {
+            sb.append('>');
+            sb.append(getChildrenCanonicalForm());
+            sb.append("</").append(getNodeTypeSymbol()).append('>');
+        }
+        return sb.toString();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return function ? "#function" : "#macro";
+    }
+    
+    public boolean isFunction() {
+        return function;
+    }
+
+    class Context implements LocalContext {
+        final Environment.Namespace localVars; 
+        final _ASTElement[] nestedContentBuffer;
+        final Environment.Namespace nestedContentNamespace;
+        final List nestedContentParameterNames;
+        final LocalContextStack prevLocalContextStack;
+        final Context prevMacroContext;
+        
+        Context(Environment env, 
+                _ASTElement[] nestedContentBuffer,
+                List nestedContentParameterNames) {
+            localVars = env.new Namespace();
+            this.nestedContentBuffer = nestedContentBuffer;
+            nestedContentNamespace = env.getCurrentNamespace();
+            this.nestedContentParameterNames = nestedContentParameterNames;
+            prevLocalContextStack = env.getLocalContextStack();
+            prevMacroContext = env.getCurrentMacroContext();
+        }
+                
+        
+        ASTDirMacro getMacro() {
+            return ASTDirMacro.this;
+        }
+
+        // Set default parameters, check if all the required parameters are defined.
+        void sanityCheck(Environment env) throws TemplateException {
+            boolean resolvedAnArg, hasUnresolvedArg;
+            ASTExpression firstUnresolvedExpression;
+            InvalidReferenceException firstReferenceException;
+            do {
+                firstUnresolvedExpression = null;
+                firstReferenceException = null;
+                resolvedAnArg = hasUnresolvedArg = false;
+                for (int i = 0; i < paramNames.length; ++i) {
+                    String argName = paramNames[i];
+                    if (localVars.get(argName) == null) {
+                        ASTExpression valueExp = (ASTExpression) paramDefaults.get(argName);
+                        if (valueExp != null) {
+                            try {
+                                TemplateModel tm = valueExp.eval(env);
+                                if (tm == null) {
+                                    if (!hasUnresolvedArg) {
+                                        firstUnresolvedExpression = valueExp;
+                                        hasUnresolvedArg = true;
+                                    }
+                                } else {
+                                    localVars.put(argName, tm);
+                                    resolvedAnArg = true;
+                                }
+                            } catch (InvalidReferenceException e) {
+                                if (!hasUnresolvedArg) {
+                                    hasUnresolvedArg = true;
+                                    firstReferenceException = e;
+                                }
+                            }
+                        } else {
+                            boolean argWasSpecified = localVars.containsKey(argName);
+                            throw new _MiscTemplateException(env,
+                                    new _ErrorDescriptionBuilder(
+                                            "When calling macro ", new _DelayedJQuote(name), 
+                                            ", required parameter ", new _DelayedJQuote(argName),
+                                            " (parameter #", Integer.valueOf(i + 1), ") was ", 
+                                            (argWasSpecified
+                                                    ? "specified, but had null/missing value."
+                                                    : "not specified.") 
+                                    ).tip(argWasSpecified
+                                            ? new Object[] {
+                                                    "If the parameter value expression on the caller side is known to "
+                                                    + "be legally null/missing, you may want to specify a default "
+                                                    + "value for it with the \"!\" operator, like "
+                                                    + "paramValue!defaultValue." }
+                                            : new Object[] { 
+                                                    "If the omission was deliberate, you may consider making the "
+                                                    + "parameter optional in the macro by specifying a default value "
+                                                    + "for it, like ", "<#macro macroName paramName=defaultExpr>", ")" }
+                                            ));
+                        }
+                    }
+                }
+            } while (resolvedAnArg && hasUnresolvedArg);
+            if (hasUnresolvedArg) {
+                if (firstReferenceException != null) {
+                    throw firstReferenceException;
+                } else {
+                    throw InvalidReferenceException.getInstance(firstUnresolvedExpression, env);
+                }
+            }
+        }
+
+        /**
+         * @return the local variable of the given name
+         * or null if it doesn't exist.
+         */ 
+        @Override
+        public TemplateModel getLocalVariable(String name) throws TemplateModelException {
+             return localVars.get(name);
+        }
+
+        Environment.Namespace getLocals() {
+            return localVars;
+        }
+        
+        /**
+         * Set a local variable in this macro 
+         */
+        void setLocalVar(String name, TemplateModel var) {
+            localVars.put(name, var);
+        }
+
+        @Override
+        public Collection getLocalVariableNames() throws TemplateModelException {
+            HashSet result = new HashSet();
+            for (TemplateModelIterator it = localVars.keys().iterator(); it.hasNext(); ) {
+                result.add(it.next().toString());
+            }
+            return result;
+        }
+    }
+
+    @Override
+    int getParameterCount() {
+        return 1/*name*/ + paramNames.length * 2/*name=default*/ + 1/*catchAll*/ + 1/*type*/;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        if (idx == 0) {
+            return name;
+        } else {
+            final int argDescsEnd = paramNames.length * 2 + 1;
+            if (idx < argDescsEnd) {
+                String paramName = paramNames[(idx - 1) / 2];
+                if (idx % 2 != 0) {
+                    return paramName;
+                } else {
+                    return paramDefaults.get(paramName);
+                }
+            } else if (idx == argDescsEnd) {
+                return catchAllParamName;
+            } else if (idx == argDescsEnd + 1) {
+                return Integer.valueOf(function ? TYPE_FUNCTION : TYPE_MACRO);
+            } else {
+                throw new IndexOutOfBoundsException();
+            }
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        if (idx == 0) {
+            return ParameterRole.ASSIGNMENT_TARGET;
+        } else {
+            final int argDescsEnd = paramNames.length * 2 + 1;
+            if (idx < argDescsEnd) {
+                if (idx % 2 != 0) {
+                    return ParameterRole.PARAMETER_NAME;
+                } else {
+                    return ParameterRole.PARAMETER_DEFAULT;
+                }
+            } else if (idx == argDescsEnd) {
+                return ParameterRole.CATCH_ALL_PARAMETER_NAME;
+            } else if (idx == argDescsEnd + 1) {
+                return ParameterRole.AST_NODE_SUBTYPE;
+            } else {
+                throw new IndexOutOfBoundsException();
+            }
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        // Because of recursive calls
+        return true;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirNested.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirNested.java b/src/main/java/org/apache/freemarker/core/ASTDirNested.java
new file mode 100644
index 0000000..c9270b8
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirNested.java
@@ -0,0 +1,159 @@
+/*
+ * 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 java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * AST directive node: {@code #nested}.
+ */
+final class ASTDirNested extends _ASTElement {
+    
+    
+    private List bodyParameters;
+    
+    
+    ASTDirNested(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
+    _ASTElement[] 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(((ASTExpression) 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 {
+        ASTDirMacro.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++) {
+                    ASTExpression exp = (ASTExpression) 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);
+                    }
+                }
+            }
+        }
+        
+        @Override
+        public TemplateModel getLocalVariable(String name) throws TemplateModelException {
+            return bodyVars == null ? null : bodyVars.get(name);
+        }
+        
+        @Override
+        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/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirNoAutoEsc.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirNoAutoEsc.java b/src/main/java/org/apache/freemarker/core/ASTDirNoAutoEsc.java
new file mode 100644
index 0000000..72f6928
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirNoAutoEsc.java
@@ -0,0 +1,77 @@
+/*
+ * 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 java.io.IOException;
+
+/**
+ * AST directive node: {@code #noautoesc}.
+ */
+final class ASTDirNoAutoEsc extends _ASTElement {
+    
+    ASTDirNoAutoEsc(TemplateElements children) { 
+        setChildren(children);
+    }
+
+    @Override
+    _ASTElement[] 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 "#noautoesc";
+    }
+    
+    @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/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirNoEscape.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirNoEscape.java b/src/main/java/org/apache/freemarker/core/ASTDirNoEscape.java
new file mode 100644
index 0000000..c914280
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ASTDirNoEscape.java
@@ -0,0 +1,78 @@
+/*
+ * 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 java.io.IOException;
+
+/**
+ * AST directive node: {@code #noescape}.
+ */
+class ASTDirNoEscape extends _ASTElement {
+
+    ASTDirNoEscape(TemplateElements children) {
+        setChildren(children);
+    }
+    
+    @Override
+    _ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        return getChildBuffer();
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        if (canonical) {
+            return "<" + getNodeTypeSymbol() + '>' + getChildrenCanonicalForm()
+                    + "</" + getNodeTypeSymbol() + '>';
+        } else {
+            return getNodeTypeSymbol();
+        }
+    }
+
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#noescape";
+    }
+
+    @Override
+    boolean isOutputCacheable() {
+        return true;
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}



Mime
View raw message