freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [01/21] incubator-freemarker git commit: FREEMARKER-63: Very early state. Until it's fully functional, the new interface is called TemplateDirectiveModel2, and is invoked with <~...> instead of <@...>. Later it will replace TemplateDirectiveModel and th
Date Mon, 07 Aug 2017 22:32:03 GMT
Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 bc91bce60 -> a3311d52e


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c28a78bd/freemarker-core/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/javacc/FTL.jj b/freemarker-core/src/main/javacc/FTL.jj
index 13655d6..97e2ade 100644
--- a/freemarker-core/src/main/javacc/FTL.jj
+++ b/freemarker-core/src/main/javacc/FTL.jj
@@ -35,6 +35,7 @@ import org.apache.freemarker.core.outputformat.impl.*;
 import org.apache.freemarker.core.model.*;
 import org.apache.freemarker.core.model.impl.*;
 import org.apache.freemarker.core.util.*;
+import org.apache.freemarker.core.ASTDirDynamicDirectiveCall.NamedArgument;
 import java.io.*;
 import java.util.*;
 import java.nio.charset.Charset;
@@ -95,6 +96,16 @@ public class FMParser {
     private LinkedList escapes = new LinkedList();
     private int mixedContentNesting; // for stripText
 
+    // Argument arrays reused for top-level invocations, purely for a bit of optimization.
+    private static final int INITAL_TOP_ARGS_BUFFER_SIZE = 8;
+    private ASTExpression[] topPositionalArgsBuffer;
+    private int topPositionalArgsLength;
+    private NamedArgument[] topNamedArgsBuffer;
+    private int topNamedArgsLength;
+    private static final int INITAL_TOP_LOOP_VAR_NAMES_BUFFER_SIZE = 4;
+    private String[] topLoopVarNamesBuffer;
+    private int topLoopVarNamesLength;
+
     FMParser(Template template, Reader reader,
             ParsingConfiguration pCfg, OutputFormat outputFormat, AutoEscapingPolicy autoEscapingPolicy,
             InputStream streamToUnmarkWhenEncEstabd) {
@@ -411,6 +422,45 @@ public class FMParser {
                 lhoExp);
     }
 
+    private void addToTopNamedArgs(NamedArgument namedArg) {
+        if (topNamedArgsBuffer == null) {
+            topNamedArgsBuffer = new NamedArgument[INITAL_TOP_ARGS_BUFFER_SIZE];
+        } else if (topNamedArgsBuffer.length == topNamedArgsLength) {
+            NamedArgument[] newNamedTopArgsBuffer =  new NamedArgument[topNamedArgsBuffer.length
* 2];
+            for (int i = 0; i < topNamedArgsBuffer.length; i++) {
+                newNamedTopArgsBuffer[i] = topNamedArgsBuffer[i];
+            }
+            topNamedArgsBuffer = newNamedTopArgsBuffer;
+        }
+        topNamedArgsBuffer[topNamedArgsLength++] = namedArg;
+    }
+
+    private void addToTopPositionalArgs(ASTExpression argValue) {
+        if (topPositionalArgsBuffer == null) {
+            topPositionalArgsBuffer = new ASTExpression[INITAL_TOP_ARGS_BUFFER_SIZE];
+        } else if (topPositionalArgsBuffer.length == topPositionalArgsLength) {
+            ASTExpression[] newPositionalArgsBuffer =  new ASTExpression[topPositionalArgsBuffer.length
* 2];
+            for (int i = 0; i < topPositionalArgsBuffer.length; i++) {
+                newPositionalArgsBuffer[i] = topPositionalArgsBuffer[i];
+            }
+            topPositionalArgsBuffer = newPositionalArgsBuffer;
+        }
+        topPositionalArgsBuffer[topPositionalArgsLength++] = argValue;
+    }
+
+    private void addToTopLoopVarNames(String loopVarName) {
+        if (topLoopVarNamesBuffer == null) {
+            topLoopVarNamesBuffer = new String[INITAL_TOP_LOOP_VAR_NAMES_BUFFER_SIZE];
+        } else if (topLoopVarNamesBuffer.length == topLoopVarNamesLength) {
+            String[] newLoopVarNamesBuffer =  new String[topLoopVarNamesBuffer.length * 2];
+            for (int i = 0; i < topLoopVarNamesBuffer.length; i++) {
+                newLoopVarNamesBuffer[i] = topLoopVarNamesBuffer[i];
+            }
+            topLoopVarNamesBuffer = newLoopVarNamesBuffer;
+        }
+        topLoopVarNamesBuffer[topLoopVarNamesLength++] = loopVarName;
+    }
+
 }
 
 PARSER_END(FMParser)
@@ -474,7 +524,7 @@ TOKEN_MGR_DECLS:
     boolean squBracTagSyntax,
             autodetectTagSyntax,
             directiveSyntaxEstablished,
-            inInvocation;
+            inNamedParameterExpression;
     int incompatibleImprovements;
 
     void setParser(FMParser parser) {
@@ -799,6 +849,10 @@ TOKEN:
     |
     <UNIFIED_CALL_END : ("<" | "[") "/@" ((<ID>) ("."<ID>)*)? <CLOSE_TAG1>>
{ unifiedCallEnd(matchedToken); }
     |
+    <DYNAMIC_DIRECTIVE_CALL : "<~" | "[~" > { unifiedCall(matchedToken); }
+    |
+    <DYNAMIC_DIRECTIVE_CALL_END : ("<" | "[") "/~" ((<ID>) ("."<ID>)*)?
<CLOSE_TAG1>> { unifiedCallEnd(matchedToken); }
+    |
     <FTL_HEADER : ("<#ftl" | "[#ftl") <BLANK>> { ftlHeader(matchedToken);
}
     |
     <TRIVIAL_FTL_HEADER : ("<#ftl" | "[#ftl") ("/")? (">" | "]")> { ftlHeader(matchedToken);
}
@@ -914,7 +968,7 @@ TOKEN:
     < "-->" | "--]">
     {
         if (parenthesisNesting > 0) SwitchTo(IN_PAREN);
-        else if (inInvocation) SwitchTo(NAMED_PARAMETER_EXPRESSION);
+        else if (inNamedParameterExpression) SwitchTo(NAMED_PARAMETER_EXPRESSION);
         else SwitchTo(FM_EXPRESSION);
     }
 }
@@ -1016,7 +1070,7 @@ TOKEN:
     |
     <COMMA : ",">
     |
-    <SEMICOLON : ";">
+    <SEMICOLON : ";"> : FM_EXPRESSION  // FM_EXPRESSION to exit WS-less expression
mode in <@foo; x>
     |
     <COLON : ":">
     |
@@ -1040,7 +1094,7 @@ TOKEN:
     {
         --parenthesisNesting;
         if (parenthesisNesting == 0) {
-            if (inInvocation) SwitchTo(NAMED_PARAMETER_EXPRESSION);
+            if (inNamedParameterExpression) SwitchTo(NAMED_PARAMETER_EXPRESSION);
             else SwitchTo(FM_EXPRESSION);
         }
     }
@@ -1816,10 +1870,14 @@ ASTExpression DefaultTo(ASTExpression exp) :
         |
         (
             t = <EXCLAM>
-            [
-                LOOKAHEAD(ASTExpression())
-                rhs = ASTExpression()
-            ]
+            (
+                LOOKAHEAD(<ID><ASSIGNMENT_EQUALS>) { /* Do not consume */ }
+                |
+                [
+                    LOOKAHEAD(ASTExpression())
+                    rhs = ASTExpression()
+                ]
+            )
         )
     )
     {
@@ -3005,10 +3063,10 @@ ASTElement UnifiedMacroTransform() :
     [
         <SEMICOLON>{bodyParameters = new ArrayList(4); }
         [
-            [<TERMINATING_WHITESPACE>] t = <ID> { bodyParameters.add(t.image);
}
+            t = <ID> { bodyParameters.add(t.image); }
             (
-                [<TERMINATING_WHITESPACE>] <COMMA>
-                [<TERMINATING_WHITESPACE>] t = <ID>{bodyParameters.add(t.image);
}
+                <COMMA>
+                t = <ID>{bodyParameters.add(t.image); }
             )*
         ]
     ]
@@ -3070,6 +3128,194 @@ ASTElement UnifiedMacroTransform() :
     }
 }
 
+ASTElement DynamicDirectiveCall() :
+{
+    Token t;
+    ASTExpression exp;
+
+    Token start = null, end;
+    ASTExpression callableValueExp;
+    ASTExpression endTagNameExp;
+    Token prevChoiceComma = null;
+    TemplateElements children;
+    int pushedCtxCount = 0;
+}
+{
+    start = <DYNAMIC_DIRECTIVE_CALL>
+    callableValueExp = ASTExpression()
+    [<TERMINATING_WHITESPACE>]
+    {
+        topPositionalArgsLength = 0;
+        topNamedArgsLength = 0;
+        topLoopVarNamesLength = 0;
+    }
+
+    (
+        LOOKAHEAD(<ID><ASSIGNMENT_EQUALS>)
+        (
+            t = <ID>
+            <ASSIGNMENT_EQUALS>
+            exp = ASTExpression()
+            {
+                if (prevChoiceComma != null) {
+                    throw new ParseException(
+                            "Remove comma (\",\"); it can only be used between arguments
passed by position.",
+                            template, prevChoiceComma);
+                }
+                prevChoiceComma = null;
+
+                addToTopNamedArgs(new NamedArgument(t.image, exp));
+            }
+        )
+        |
+        (
+            // This could be part of the positional paramter choice, but we can give better
error messages this way.
+            t = <COMMA>
+            {
+                if (prevChoiceComma != null) {
+                    throw new ParseException("Two commas (\",\") without argument between.",
+                            template, t);
+                }
+                prevChoiceComma = t;
+
+                if (topNamedArgsLength != 0 || topPositionalArgsLength == 0) {
+                    throw new ParseException(
+                            "Remove comma (\",\"); it can only be used between arguments
passed by position.",
+                            template, t);
+                }
+            }
+        )
+        |
+        (
+            exp = ASTExpression()
+            {
+                if (topNamedArgsLength != 0) {
+                    throw new ParseException(
+                            "Expression looks like an argument passed by position (no preceding
\"<argName>=\"), "
+                            + " but those must be earlier than arguments passed by name.",
+                            exp);
+                }
+
+                if (prevChoiceComma == null && topPositionalArgsLength != 0) {
+                        throw new ParseException("Missing comma (\",\") before expression.
"
+                                + "Arguments passed by position must be separated by comma.",
exp);
+                }
+                prevChoiceComma = null;
+
+                addToTopPositionalArgs(exp);
+            }
+        )
+    )*
+
+    [
+        <SEMICOLON>
+        [
+            t = <ID> { addToTopLoopVarNames(t.image); }
+            (
+                <COMMA>
+                t = <ID> { addToTopLoopVarNames(t.image); }
+            )*
+        ]
+    ]
+
+    {
+        // We must copy the top level arrays (as they will be reused). The copies will have
the minimum length. Also,
+        // they will be null instead of an empty array.
+
+        ASTExpression[] trimmedPositionalArgs;
+        if (topPositionalArgsLength == 0) {
+            trimmedPositionalArgs = null;
+        } else {
+            trimmedPositionalArgs = new ASTExpression[topPositionalArgsLength];
+            for (int i = 0; i < topPositionalArgsLength; i++) {
+                trimmedPositionalArgs[i] = topPositionalArgsBuffer[i];
+            }
+        }
+
+        NamedArgument[] trimmedNamedArgs;
+        if (topNamedArgsLength == 0) {
+            trimmedNamedArgs = null;
+        } else {
+            trimmedNamedArgs = new NamedArgument[topNamedArgsLength];
+            for (int i = 0; i < topNamedArgsLength; i++) {
+                trimmedNamedArgs[i] = topNamedArgsBuffer[i];
+            }
+        }
+
+        String[] trimmedLoopVarNames;
+        if (topLoopVarNamesLength == 0) {
+            trimmedLoopVarNames = null;
+        } else {
+            trimmedLoopVarNames = new String[topLoopVarNamesLength];
+            for (int i = 0; i < topLoopVarNamesLength; i++) {
+                trimmedLoopVarNames[i] = topLoopVarNamesBuffer[i];
+            }
+        }
+    }
+
+    (
+        end = <EMPTY_DIRECTIVE_END> { children = TemplateElements.EMPTY; }
+        |
+        (
+            <DIRECTIVE_END>
+            {
+                if (topLoopVarNamesLength != 0 && iteratorBlockContexts != null &&
!iteratorBlockContexts.isEmpty()) {
+                    // It's possible that we shadow a #list/#items loop variable, in which
case that must be noted.
+                    int ctxsLen = iteratorBlockContexts.size();
+	                for (int loopVarIdx = 0; loopVarIdx < topLoopVarNamesLength; loopVarIdx++)
{
+                        String loopVarName = (String) topLoopVarNamesBuffer[loopVarIdx];
+                        walkCtxStack: for (int ctxIdx = ctxsLen - 1; ctxIdx >= 0; ctxIdx--)
{
+                            ParserIteratorBlockContext ctx
+                                    = (ParserIteratorBlockContext) iteratorBlockContexts.get(ctxIdx);
+                            if (ctx.loopVarName != null && ctx.loopVarName.equals(loopVarName))
{
+                                // If it wasn't already shadowed, shadow it:
+                                if (ctx.kind != ITERATOR_BLOCK_KIND_USER_DIRECTIVE) {
+                                    ParserIteratorBlockContext shadowingCtx = pushIteratorBlockContext();
+                                    shadowingCtx.loopVarName = loopVarName;
+                                    shadowingCtx.kind = ITERATOR_BLOCK_KIND_USER_DIRECTIVE;
+                                    pushedCtxCount++;
+                                }
+                                break walkCtxStack;
+                            }
+                        }
+                   }
+                }
+            }
+
+            children = MixedContentElements()
+
+            end = <DYNAMIC_DIRECTIVE_CALL_END>
+            {
+                for (int i = 0; i < pushedCtxCount; i++) {
+                    popIteratorBlockContext();
+                }
+
+                String endTagName = end.image.substring(3, end.image.length() - 1).trim();
+                if (endTagName.length() > 0) {
+                    if (callableValueExp instanceof ASTExpVariable
+                            || (callableValueExp instanceof ASTExpDot
+                                    && ((ASTExpDot) callableValueExp).onlyHasIdentifiers()))
{
+                        String startTagName = callableValueExp.getCanonicalForm(); // TODO
[FM3] Why the canonical form?
+                        if (!endTagName.equals(startTagName)) {
+                            throw new ParseException("Expecting </@> or </@" + startTagName
+ ">", template, end);
+                        }
+                    } else {
+                        throw new ParseException("Expecting </@>", template, end);
+                    }
+                }
+            }
+        )
+    )
+    {
+        ASTElement result = new ASTDirDynamicDirectiveCall(
+                callableValueExp, false,
+                trimmedPositionalArgs, trimmedNamedArgs, trimmedLoopVarNames,
+                children);
+        result.setLocation(template, start, end);
+        return result;
+    }
+}
+
 HashMap NamedArgs() :
 {
     HashMap result = new HashMap();
@@ -3082,7 +3328,7 @@ HashMap NamedArgs() :
         <ASSIGNMENT_EQUALS>
         {
             token_source.SwitchTo(token_source.NAMED_PARAMETER_EXPRESSION);
-            token_source.inInvocation = true;
+            token_source.inNamedParameterExpression = true;
         }             
         exp = ASTExpression()
         {
@@ -3090,7 +3336,7 @@ HashMap NamedArgs() :
         }
     )+
     {
-        token_source.inInvocation = false;
+        token_source.inNamedParameterExpression = false;
         return result;
     }
 }
@@ -3166,25 +3412,26 @@ ASTDirSwitch Switch() :
         breakableDirectiveNesting++;
         switchBlock = new ASTDirSwitch(switchExp, ignoredSectionBeforeFirstCase);
     }
-    (
-        LOOKAHEAD(2)
-        caseOrDefault = ASTDirCase()
-        {
-            if (caseOrDefault.condition == null) {
-                if (defaultFound) {
-                    throw new ParseException(
-                            "You can only have one default case in a switch statement", template,
start);
+    [
+        (
+            caseOrDefault = ASTDirCase()
+            {
+                if (caseOrDefault.condition == null) {
+                    if (defaultFound) {
+                        throw new ParseException(
+                                "You can only have one default case in a switch statement",
template, start);
+                    }
+                    defaultFound = true;
+                } else if (defaultFound) {
+                        throw new ParseException(
+                                "You can't have a \"case\" directive after the \"default\"
directive",
+                                caseOrDefault);
                 }
-                defaultFound = true;
-            } else if (defaultFound) {
-                    throw new ParseException(
-                            "You can't have a \"case\" directive after the \"default\" directive",
-                            caseOrDefault);
+                switchBlock.addCase(caseOrDefault);
             }
-            switchBlock.addCase(caseOrDefault);
-        }
-    )*
-    [<STATIC_TEXT_WS>]
+        )+
+        [<STATIC_TEXT_WS>]
+    ]
     end = <END_SWITCH>
     {
         breakableDirectiveNesting--;
@@ -3476,6 +3723,8 @@ ASTElement FreemarkerDirective() :
         |
         tp = UnifiedMacroTransform()
         |
+        tp = DynamicDirectiveCall()
+        |
         tp = Items()
         |
         tp = Sep()


Mime
View raw message