This is an automated email from the ASF dual-hosted git repository.

joshtynjala pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git

commit edd4b5743ce31e9ba78a0496cd049b3f129f64b6
Author: Josh Tynjala <[email protected]>
AuthorDate: Tue Dec 12 15:04:12 2023 -0800

    MXMLDataBindingParser: fix using backslash as an escape character for { and 
} to make the compiler treat them as regular characters instead of binding 
delimiters (closes #110)
    
    Behavior updated to better match the Flex SDK compiler.
    
    If either the { or the } is escaped, but the other one isn't, they're both 
treated as part of the string, same as if both were escaped.
    
    All of these, except for the first, should be treated as regular strings:
    
    <js:Label text="{not_binding}"/>
    <js:Label text="\{not_binding}"/>
    <js:Label text="\{not_binding\}"/>
    <js:Label text="{not_binding\}"/>
    <js:Label text="{not_binding"/>
---
 .../mxml/royale/TestRoyaleMXMLApplication.java     | 347 ++++++++++++++++++++-
 .../internal/tree/mxml/MXMLDataBindingParser.java  |   8 +-
 .../internal/tree/mxml/MXMLTreeBuilder.java        |   6 +
 3 files changed, 358 insertions(+), 3 deletions(-)

diff --git 
a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/mxml/royale/TestRoyaleMXMLApplication.java
 
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/mxml/royale/TestRoyaleMXMLApplication.java
index 13442c7be..8a3f1eec9 100644
--- 
a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/mxml/royale/TestRoyaleMXMLApplication.java
+++ 
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/mxml/royale/TestRoyaleMXMLApplication.java
@@ -235,8 +235,12 @@ public class TestRoyaleMXMLApplication extends 
RoyaleTestBase
         assertOutWithMetadata(outTemplate.replaceAll("AppName", appName));
     }
 
+       // we previously had a test that checked if a backslash could escape 
another
+       // backslash, but the Flex SDK compiler treats that as two backslashes!
+       // the only characters that the Flex SDK compiler escapes with backslash
+       // are { and } curly braces (which are used for binding, if unescaped)
     @Test
-    public void testAlreadyEscapedBackslashStringAttribute()
+    public void testDoubleBackslashStringAttribute()
     {
         String code = "<basic:Application 
xmlns:fx=\"http://ns.adobe.com/mxml/2009\"; 
xmlns:basic=\"library://ns.apache.org/royale/basic\">"
                 + "<basic:beads><basic:TextPromptBead 
prompt=\"0-9\\\\\"/></basic:beads></basic:Application>";
@@ -299,7 +303,346 @@ public class TestRoyaleMXMLApplication extends 
RoyaleTestBase
                        "      '$ID_8_0',\n" +
                        "      'prompt',\n" +
                        "      true,\n" +
-                       "      '0-9\\\\',\n" +
+                       "      '0-9\\\\\\\\',\n" +
+                       "      0,\n" +
+                       "      0,\n" +
+                       "      null\n" +
+                       "    ],\n" +
+                       "    0,\n" +
+                       "    0\n" +
+                       "  ]);\n" +
+                       "  \n" +
+                       "};\n" +
+                       "goog.inherits(AppName, 
org.apache.royale.core.Application);\n" +
+                       "\n" +
+                       "\n" +
+                       "\n" +
+                               "\n" +
+                       "/**\n" +
+                       " * Metadata\n" +
+                       " *\n" +
+                       " * @type {Object.<string, Array.<Object>>}\n" +
+                       " */\n" +
+                       "AppName.prototype.ROYALE_CLASS_INFO = { names: [{ 
name: 'AppName', qName: 'AppName', kind: 'class'  }] };\n" +
+                       "\n" +
+                       "\n" +
+                       "\n" +
+                       "/**\n" +
+                       " * Reflection\n" +
+                       " *\n" +
+                       " * @return {Object.<string, Function>}\n" +
+                       " */\n" +
+                       "AppName.prototype.ROYALE_REFLECTION_INFO = function () 
{\n" +
+                       "  return {\n" +
+                       "    methods: function () {\n" +
+                       "      return {\n" +
+                               "        'AppName': { type: '', declaredBy: 
'AppName'}\n"+
+                       "      };\n" +
+                       "    }\n" +
+                       "  };\n" +
+                       "};\n" +
+                       "/**\n" +
+                       " * @const\n" +
+                       " * @type {number}\n" +
+                       " */\n" +
+                       "AppName.prototype.ROYALE_COMPILE_FLAGS = 9;\n" +
+                       "\n" +
+                       "\n";
+
+        assertOutWithMetadata(outTemplate.replaceAll("AppName", appName));
+    }
+
+    @Test
+    public void testBackslashOpenCurlyBraceStringAttribute()
+    {
+        String code = "<basic:Application 
xmlns:fx=\"http://ns.adobe.com/mxml/2009\"; 
xmlns:basic=\"library://ns.apache.org/royale/basic\">"
+                + "<basic:beads><basic:TextPromptBead 
prompt=\"\\{0-9}\"/></basic:beads></basic:Application>";
+
+        IMXMLDocumentNode dnode = (IMXMLDocumentNode) getNode(code,
+                       IMXMLDocumentNode.class, 
RoyaleTestBase.WRAP_LEVEL_NONE);
+
+        
((JSRoyaleEmitter)(mxmlBlockWalker.getASEmitter())).getModel().setCurrentClass(dnode.getDefinition());
+        mxmlBlockWalker.visitDocument(dnode);
+        String appName = dnode.getQualifiedName();
+        String outTemplate = "/**\n" +
+                       " * AppName\n" +
+                       " *\n" +
+                       " * @fileoverview\n" +
+                       " *\n" +
+                       " * @suppress {checkTypes|accessControls}\n" +
+                       " */\n" +
+                       "\n" +
+                       "goog.provide('AppName');\n" +
+                       "\n" +
+                       "goog.require('org.apache.royale.core.Application');\n" 
+
+                       
"goog.require('org.apache.royale.html.accessories.TextPromptBead');\n" +
+                       "\n" +
+                       "\n" +
+                       "\n" +
+                       "/**\n" +
+                       " * @constructor\n" +
+                       " * @extends {org.apache.royale.core.Application}\n" +
+                       " */\n" +
+                       "AppName = function() {\n" +
+                       "  AppName.base(this, 'constructor');\n" +
+                       "  \n" +
+                       "  /**\n" +
+                       "   * @private\n" +
+                       "   * @type 
{org.apache.royale.html.accessories.TextPromptBead}\n" +
+                       "   */\n" +
+                       "  this.$ID_8_0;\n" +
+                       "  \n" +
+                       "  /**\n" +
+                       "   * @private\n" +
+                       "   * @type {Array}\n" +
+                       "   */\n" +
+                       "  this.mxmldd;\n" +
+                       "  \n" +
+                       "  /**\n" +
+                       "   * @private\n" +
+                       "   * @type {Array}\n" +
+                       "   */\n" +
+                       "  this.mxmldp;\n" +
+                       "\n" +
+                       "  this.generateMXMLAttributes([\n" +
+                       "    1,\n" +
+                       "    'beads',\n" +
+                       "    null,\n" +
+                       "    [\n" +
+                       "      
org.apache.royale.html.accessories.TextPromptBead,\n" +
+                       "      2,\n" +
+                       "      '_id',\n" +
+                       "      true,\n" +
+                       "      '$ID_8_0',\n" +
+                       "      'prompt',\n" +
+                       "      true,\n" +
+                       "      '{0-9}',\n" +
+                       "      0,\n" +
+                       "      0,\n" +
+                       "      null\n" +
+                       "    ],\n" +
+                       "    0,\n" +
+                       "    0\n" +
+                       "  ]);\n" +
+                       "  \n" +
+                       "};\n" +
+                       "goog.inherits(AppName, 
org.apache.royale.core.Application);\n" +
+                       "\n" +
+                       "\n" +
+                       "\n" +
+                               "\n" +
+                       "/**\n" +
+                       " * Metadata\n" +
+                       " *\n" +
+                       " * @type {Object.<string, Array.<Object>>}\n" +
+                       " */\n" +
+                       "AppName.prototype.ROYALE_CLASS_INFO = { names: [{ 
name: 'AppName', qName: 'AppName', kind: 'class'  }] };\n" +
+                       "\n" +
+                       "\n" +
+                       "\n" +
+                       "/**\n" +
+                       " * Reflection\n" +
+                       " *\n" +
+                       " * @return {Object.<string, Function>}\n" +
+                       " */\n" +
+                       "AppName.prototype.ROYALE_REFLECTION_INFO = function () 
{\n" +
+                       "  return {\n" +
+                       "    methods: function () {\n" +
+                       "      return {\n" +
+                               "        'AppName': { type: '', declaredBy: 
'AppName'}\n"+
+                       "      };\n" +
+                       "    }\n" +
+                       "  };\n" +
+                       "};\n" +
+                       "/**\n" +
+                       " * @const\n" +
+                       " * @type {number}\n" +
+                       " */\n" +
+                       "AppName.prototype.ROYALE_COMPILE_FLAGS = 9;\n" +
+                       "\n" +
+                       "\n";
+
+        assertOutWithMetadata(outTemplate.replaceAll("AppName", appName));
+    }
+
+    @Test
+    public void testBackslashCloseCurlyBraceStringAttribute()
+    {
+        String code = "<basic:Application 
xmlns:fx=\"http://ns.adobe.com/mxml/2009\"; 
xmlns:basic=\"library://ns.apache.org/royale/basic\">"
+                + "<basic:beads><basic:TextPromptBead 
prompt=\"{0-9\\}\"/></basic:beads></basic:Application>";
+
+        IMXMLDocumentNode dnode = (IMXMLDocumentNode) getNode(code,
+                       IMXMLDocumentNode.class, 
RoyaleTestBase.WRAP_LEVEL_NONE);
+
+        
((JSRoyaleEmitter)(mxmlBlockWalker.getASEmitter())).getModel().setCurrentClass(dnode.getDefinition());
+        mxmlBlockWalker.visitDocument(dnode);
+        String appName = dnode.getQualifiedName();
+        String outTemplate = "/**\n" +
+                       " * AppName\n" +
+                       " *\n" +
+                       " * @fileoverview\n" +
+                       " *\n" +
+                       " * @suppress {checkTypes|accessControls}\n" +
+                       " */\n" +
+                       "\n" +
+                       "goog.provide('AppName');\n" +
+                       "\n" +
+                       "goog.require('org.apache.royale.core.Application');\n" 
+
+                       
"goog.require('org.apache.royale.html.accessories.TextPromptBead');\n" +
+                       "\n" +
+                       "\n" +
+                       "\n" +
+                       "/**\n" +
+                       " * @constructor\n" +
+                       " * @extends {org.apache.royale.core.Application}\n" +
+                       " */\n" +
+                       "AppName = function() {\n" +
+                       "  AppName.base(this, 'constructor');\n" +
+                       "  \n" +
+                       "  /**\n" +
+                       "   * @private\n" +
+                       "   * @type 
{org.apache.royale.html.accessories.TextPromptBead}\n" +
+                       "   */\n" +
+                       "  this.$ID_8_0;\n" +
+                       "  \n" +
+                       "  /**\n" +
+                       "   * @private\n" +
+                       "   * @type {Array}\n" +
+                       "   */\n" +
+                       "  this.mxmldd;\n" +
+                       "  \n" +
+                       "  /**\n" +
+                       "   * @private\n" +
+                       "   * @type {Array}\n" +
+                       "   */\n" +
+                       "  this.mxmldp;\n" +
+                       "\n" +
+                       "  this.generateMXMLAttributes([\n" +
+                       "    1,\n" +
+                       "    'beads',\n" +
+                       "    null,\n" +
+                       "    [\n" +
+                       "      
org.apache.royale.html.accessories.TextPromptBead,\n" +
+                       "      2,\n" +
+                       "      '_id',\n" +
+                       "      true,\n" +
+                       "      '$ID_8_0',\n" +
+                       "      'prompt',\n" +
+                       "      true,\n" +
+                       "      '{0-9}',\n" +
+                       "      0,\n" +
+                       "      0,\n" +
+                       "      null\n" +
+                       "    ],\n" +
+                       "    0,\n" +
+                       "    0\n" +
+                       "  ]);\n" +
+                       "  \n" +
+                       "};\n" +
+                       "goog.inherits(AppName, 
org.apache.royale.core.Application);\n" +
+                       "\n" +
+                       "\n" +
+                       "\n" +
+                               "\n" +
+                       "/**\n" +
+                       " * Metadata\n" +
+                       " *\n" +
+                       " * @type {Object.<string, Array.<Object>>}\n" +
+                       " */\n" +
+                       "AppName.prototype.ROYALE_CLASS_INFO = { names: [{ 
name: 'AppName', qName: 'AppName', kind: 'class'  }] };\n" +
+                       "\n" +
+                       "\n" +
+                       "\n" +
+                       "/**\n" +
+                       " * Reflection\n" +
+                       " *\n" +
+                       " * @return {Object.<string, Function>}\n" +
+                       " */\n" +
+                       "AppName.prototype.ROYALE_REFLECTION_INFO = function () 
{\n" +
+                       "  return {\n" +
+                       "    methods: function () {\n" +
+                       "      return {\n" +
+                               "        'AppName': { type: '', declaredBy: 
'AppName'}\n"+
+                       "      };\n" +
+                       "    }\n" +
+                       "  };\n" +
+                       "};\n" +
+                       "/**\n" +
+                       " * @const\n" +
+                       " * @type {number}\n" +
+                       " */\n" +
+                       "AppName.prototype.ROYALE_COMPILE_FLAGS = 9;\n" +
+                       "\n" +
+                       "\n";
+
+        assertOutWithMetadata(outTemplate.replaceAll("AppName", appName));
+    }
+
+    @Test
+    public void testBackslashOpenAndCloseCurlyBraceStringAttribute()
+    {
+        String code = "<basic:Application 
xmlns:fx=\"http://ns.adobe.com/mxml/2009\"; 
xmlns:basic=\"library://ns.apache.org/royale/basic\">"
+                + "<basic:beads><basic:TextPromptBead 
prompt=\"\\{0-9\\}\"/></basic:beads></basic:Application>";
+
+        IMXMLDocumentNode dnode = (IMXMLDocumentNode) getNode(code,
+                       IMXMLDocumentNode.class, 
RoyaleTestBase.WRAP_LEVEL_NONE);
+
+        
((JSRoyaleEmitter)(mxmlBlockWalker.getASEmitter())).getModel().setCurrentClass(dnode.getDefinition());
+        mxmlBlockWalker.visitDocument(dnode);
+        String appName = dnode.getQualifiedName();
+        String outTemplate = "/**\n" +
+                       " * AppName\n" +
+                       " *\n" +
+                       " * @fileoverview\n" +
+                       " *\n" +
+                       " * @suppress {checkTypes|accessControls}\n" +
+                       " */\n" +
+                       "\n" +
+                       "goog.provide('AppName');\n" +
+                       "\n" +
+                       "goog.require('org.apache.royale.core.Application');\n" 
+
+                       
"goog.require('org.apache.royale.html.accessories.TextPromptBead');\n" +
+                       "\n" +
+                       "\n" +
+                       "\n" +
+                       "/**\n" +
+                       " * @constructor\n" +
+                       " * @extends {org.apache.royale.core.Application}\n" +
+                       " */\n" +
+                       "AppName = function() {\n" +
+                       "  AppName.base(this, 'constructor');\n" +
+                       "  \n" +
+                       "  /**\n" +
+                       "   * @private\n" +
+                       "   * @type 
{org.apache.royale.html.accessories.TextPromptBead}\n" +
+                       "   */\n" +
+                       "  this.$ID_8_0;\n" +
+                       "  \n" +
+                       "  /**\n" +
+                       "   * @private\n" +
+                       "   * @type {Array}\n" +
+                       "   */\n" +
+                       "  this.mxmldd;\n" +
+                       "  \n" +
+                       "  /**\n" +
+                       "   * @private\n" +
+                       "   * @type {Array}\n" +
+                       "   */\n" +
+                       "  this.mxmldp;\n" +
+                       "\n" +
+                       "  this.generateMXMLAttributes([\n" +
+                       "    1,\n" +
+                       "    'beads',\n" +
+                       "    null,\n" +
+                       "    [\n" +
+                       "      
org.apache.royale.html.accessories.TextPromptBead,\n" +
+                       "      2,\n" +
+                       "      '_id',\n" +
+                       "      true,\n" +
+                       "      '$ID_8_0',\n" +
+                       "      'prompt',\n" +
+                       "      true,\n" +
+                       "      '{0-9}',\n" +
                        "      0,\n" +
                        "      0,\n" +
                        "      null\n" +
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/mxml/MXMLDataBindingParser.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/mxml/MXMLDataBindingParser.java
index 329ba167d..6408d58b8 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/mxml/MXMLDataBindingParser.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/mxml/MXMLDataBindingParser.java
@@ -189,7 +189,7 @@ class MXMLDataBindingParser
                     case RIGHT_BRACE:
                     {
                         nesting--;
-                        if (nesting == 0)
+                        if (nesting == 0 && !escape && leftBraceCharIndex != 
-1)
                         {
                             // We've found the matching '}' for the '{'.
                             // Record where the '{' and '}' are.
@@ -197,6 +197,10 @@ class MXMLDataBindingParser
                                 result = LinkedListMultimap.<ISourceFragment, 
Integer> create();
                             result.put(leftBraceFragment, leftBraceCharIndex);
                             result.put(fragment, i);
+                            // clear the left brace, so that we can start
+                            // searching for a new one
+                            leftBraceFragment = null;
+                            leftBraceCharIndex = -1;
                         }
                         escape = false;
                         break;
@@ -351,6 +355,8 @@ class MXMLDataBindingParser
     {
         ISourceFragment[] fragments = fragmentList.toArray(new 
ISourceFragment[0]);
         String text = SourceFragmentsReader.concatLogicalText(fragments);
+        // the { and } characters may be escaped with backslash, so remove it
+        text = text.replaceAll("\\\\\\{", "{").replaceAll("\\\\\\}", "}");
 
         // LiteralNode automatically strips out quote characters at the
         // beginning and end of the string.
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/mxml/MXMLTreeBuilder.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/mxml/MXMLTreeBuilder.java
index 6916a3716..979ea2655 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/mxml/MXMLTreeBuilder.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/mxml/MXMLTreeBuilder.java
@@ -537,6 +537,12 @@ public class MXMLTreeBuilder
             {
                 text = SourceFragmentsReader.concatPhysicalText(fragments);
             }
+            if (flags.contains(TextParsingFlags.ALLOW_BINDING))
+            {
+                // if binding is allowed, then the { and } characters may be 
+                // escaped with backslash, so remove the backslash
+                text = text.replaceAll("\\\\\\{", "{").replaceAll("\\\\\\}", 
"}");
+            }
 
             value = parseValue(propertyNode, type, text, flags, defaultValue, 
isAttribute);
 

Reply via email to