Răsfoiți Sursa

Fabric-scoping support in IDL parsing (#20819)

* Add fabric scope support in IDL for commands

* Add support for fabric scoped attributes. Due to ambiguity of fabric_scoped, moved to earley parser instead of lalr. Potentially somewhat slower, but likely still fast enough

* Switch back to lalr parser because it is much faster

* Doc updates and one more test

* Fix command id in examples

* Use common keyword of fabric after updating grammar a bit

* Fix extra rule

* Restyle
Andrei Litvin 3 ani în urmă
părinte
comite
08bb91f42e

+ 11 - 1
scripts/idl/README.md

@@ -94,6 +94,10 @@ server cluster AccessControl = 31 {
   // These defaults can be modified to any of view/operate/manage/administer roles.
   attribute access(read: manage, write: administer) int32u customAcl = 3;
 
+  // Attributes may be fabric-scoped as well by tagging them as `fabric`.
+  fabric readonly attribute int16u myFabricAttr = 22;
+  fabric attribute(read: view, write: administer) int16u someFabricRWAttribute = 33;
+
   // attributes may be read-only as well
   readonly attribute int16u clusterRevision = 65533;
 
@@ -118,7 +122,13 @@ server cluster AccessControl = 31 {
   command access(invoke: administer) Off(): DefaultSuccess = 4;
 
   // command invocation can require timed invoke usage
-  timed command RequiresTimedInvok(): DefaultSuccess = 4;
+  timed command RequiresTimedInvok(): DefaultSuccess = 5;
+
+  // commands may be fabric scoped
+  fabric command RequiresTimedInvok(): DefaultSuccess = 6;
+
+  // commands may have multiple attributes
+  fabric timed command RequiresTimedInvok(): DefaultSuccess = 7;
 }
 
 // A client cluster represents something that is used by an app

+ 6 - 3
scripts/idl/matter_grammar.lark

@@ -26,9 +26,13 @@ attribute_access: "access"i "(" (attribute_access_entry ("," attribute_access_en
 
 attribute_with_access: attribute_access? struct_field
 
-attribute: attribute_tag* "attribute"i attribute_with_access ";"
+shared_tag: "fabric"i -> shared_tag_fabric
+shared_tags: shared_tag* -> shared_tags
+
+attribute: shared_tags attribute_tags "attribute"i attribute_with_access ";"
 attribute_tag: "readonly"i -> attr_readonly
              | "nosubscribe"i -> attr_nosubscribe
+attribute_tags: attribute_tag* -> attribute_tags
 
 request_struct: "request"i struct
 
@@ -38,12 +42,11 @@ response_struct: "response"i "struct"i id "=" positive_integer "{" (struct_field
 command_attribute: "timed"i -> timed_command
 command_attributes: command_attribute*
 
-
 command_access: "access"i "(" ("invoke"i ":" access_privilege)? ")"
 
 command_with_access: "command"i command_access? id
 
-command: command_attributes command_with_access "(" id? ")" ":" id "=" positive_integer ";"
+command: shared_tags command_attributes command_with_access "(" id? ")" ":" id "=" positive_integer ";"
 
 cluster: cluster_side "cluster"i id "=" positive_integer "{" (enum|bitmap|event|attribute|struct|request_struct|response_struct|command)* "}"
 ?cluster_side: "server"i -> server_cluster

+ 46 - 9
scripts/idl/matter_idl_parser.py

@@ -1,5 +1,6 @@
 #!/usr/bin/env python
 
+import enum
 import logging
 
 from lark import Lark
@@ -15,6 +16,10 @@ except:
     from matter_idl_types import *
 
 
+class SharedTag(enum.Enum):
+    FABRIC_SCOPED = enum.auto()
+
+
 class AddServerClusterToEndpointTransform:
     """Provides an 'apply' method that can be run on endpoints
        to add a server cluster to the given endpoint.
@@ -128,6 +133,12 @@ class MatterIdlTransformer(Transformer):
         else:
             raise Error("Unexpected size for data type")
 
+    def shared_tag_fabric(self, _):
+        return SharedTag.FABRIC_SCOPED
+
+    def shared_tags(self, entries):
+        return entries
+
     @v_args(inline=True)
     def constant_entry(self, id, number):
         return ConstantEntry(name=id, code=number)
@@ -159,6 +170,9 @@ class MatterIdlTransformer(Transformer):
     def attr_nosubscribe(self, _):
         return AttributeTag.NOSUBSCRIBE
 
+    def attribute_tags(self, tags):
+        return tags
+
     def critical_priority(self, _):
         return EventPriority.CRITICAL
 
@@ -204,14 +218,23 @@ class MatterIdlTransformer(Transformer):
         return init_args
 
     def command(self, args):
-        # A command has 4 arguments if no input or
-        # 5 arguments if input parameter is available
-        param_in = None
-        if len(args) > 4:
-            param_in = args[2]
+        # The command takes 5 arguments if no input argument, 6 if input
+        # argument is provided
+        if len(args) != 6:
+            args.insert(3, None)
+
+        attr = args[1]  # direct command attributes
+        for shared_attr in args[0]:
+            if shared_attr == SharedTag.FABRIC_SCOPED:
+                attr.add(CommandAttribute.FABRIC_SCOPED)
+            else:
+                raise Exception("Unknown shared tag: %r" % shared_attr)
 
         return Command(
-            attributes=args[0], input_param=param_in, output_param=args[-2], code=args[-1], **args[1])
+            attributes=attr,
+            input_param=args[3], output_param=args[4], code=args[5],
+            **args[2]
+        )
 
     def event_access(self, privilege):
         return privilege[0]
@@ -291,9 +314,17 @@ class MatterIdlTransformer(Transformer):
         # handle escapes, skip the start and end quotes
         return s.value[1:-1].encode('utf-8').decode('unicode-escape')
 
-    def attribute(self, args):
-        tags = set(args[:-1])
-        (definition, acl) = args[-1]
+    @v_args(inline=True)
+    def attribute(self, shared_tags, tags, definition_tuple):
+
+        tags = set(tags)
+        (definition, acl) = definition_tuple
+
+        for shared_attr in shared_tags:
+            if shared_attr == SharedTag.FABRIC_SCOPED:
+                tags.add(AttributeTag.FABRIC_SCOPED)
+            else:
+                raise Exception("Unknown shared tag: %r" % shared_attr)
 
         # until we support write only (and need a bit of a reshuffle)
         # if the 'attr_readonly == READABLE' is not in the list, we make things
@@ -396,6 +427,12 @@ def CreateParser(skip_meta: bool = False):
     """
     Generates a parser that will process a ".matter" file into a IDL
     """
+
+    # NOTE: LALR parser is fast. While Earley could parse more ambigous grammars,
+    #       earley is much slower:
+    #    - 0.39s LALR parsing of all-clusters-app.matter
+    #    - 2.26s Earley parsing of the same thing.
+    # For this reason, every attempt should be made to make the grammar context free
     return ParserWithLines(Lark.open('matter_grammar.lark', rel_to=__file__, start='idl', parser='lalr', propagate_positions=True), skip_meta)
 
 

+ 2 - 0
scripts/idl/matter_idl_types.py

@@ -28,12 +28,14 @@ class FieldAttribute(enum.Enum):
 
 class CommandAttribute(enum.Enum):
     TIMED_INVOKE = enum.auto()
+    FABRIC_SCOPED = enum.auto()
 
 
 class AttributeTag(enum.Enum):
     READABLE = enum.auto()
     WRITABLE = enum.auto()
     NOSUBSCRIBE = enum.auto()
+    FABRIC_SCOPED = enum.auto()
 
 
 class AttributeStorage(enum.Enum):

+ 17 - 0
scripts/idl/test_matter_idl_parser.py

@@ -96,6 +96,7 @@ class TestParser(unittest.TestCase):
                 attribute int32u rwAttr[] = 123;
                 readonly nosubscribe attribute int8s nosub[] = 0xaa;
                 readonly attribute nullable int8s isNullable = 0xab;
+                fabric readonly attribute int8s fabric_attr = 0x1234;
             }
         """)
 
@@ -112,6 +113,8 @@ class TestParser(unittest.TestCase):
                             data_type=DataType(name="int8s"), code=0xAA, name="nosub", is_list=True)),
                         Attribute(tags=set([AttributeTag.READABLE]), definition=Field(
                             data_type=DataType(name="int8s"), code=0xAB, name="isNullable", attributes=set([FieldAttribute.NULLABLE]))),
+                        Attribute(tags=set([AttributeTag.READABLE, AttributeTag.FABRIC_SCOPED]), definition=Field(
+                            data_type=DataType(name="int8s"), code=0x1234, name="fabric_attr"))
                     ]
                     )])
         self.assertEqual(actual, expected)
@@ -145,6 +148,7 @@ class TestParser(unittest.TestCase):
                 attribute access(read: manage)                 int8s attr3 = 3;
                 attribute access(write: administer)            int8s attr4 = 4;
                 attribute access(read: operate, write: manage) int8s attr5 = 5;
+                fabric attribute access(read: view, write: administer) int16u attr6 = 6;
             }
         """)
 
@@ -176,6 +180,11 @@ class TestParser(unittest.TestCase):
                             readacl=AccessPrivilege.OPERATE,
                             writeacl=AccessPrivilege.MANAGE
                         ),
+                        Attribute(tags=set([AttributeTag.READABLE, AttributeTag.WRITABLE, AttributeTag.FABRIC_SCOPED]), definition=Field(
+                            data_type=DataType(name="int16u"), code=6, name="attr6"),
+                            readacl=AccessPrivilege.VIEW,
+                            writeacl=AccessPrivilege.ADMINISTER
+                        ),
                     ]
                     )])
         self.assertEqual(actual, expected)
@@ -190,6 +199,8 @@ class TestParser(unittest.TestCase):
                 command WithoutArg(): DefaultSuccess = 123;
                 command InOutStuff(InParam): OutParam = 222;
                 timed command TimedCommand(InParam): DefaultSuccess = 0xab;
+                fabric command FabricScopedCommand(InParam): DefaultSuccess = 0xac;
+                fabric Timed command FabricScopedTimedCommand(InParam): DefaultSuccess = 0xad;
             }
         """)
         expected = Idl(clusters=[
@@ -210,6 +221,12 @@ class TestParser(unittest.TestCase):
                         Command(name="TimedCommand", code=0xab,
                                 input_param="InParam", output_param="DefaultSuccess",
                                 attributes=set([CommandAttribute.TIMED_INVOKE])),
+                        Command(name="FabricScopedCommand", code=0xac,
+                                input_param="InParam", output_param="DefaultSuccess",
+                                attributes=set([CommandAttribute.FABRIC_SCOPED])),
+                        Command(name="FabricScopedTimedCommand", code=0xad,
+                                input_param="InParam", output_param="DefaultSuccess",
+                                attributes=set([CommandAttribute.TIMED_INVOKE, CommandAttribute.FABRIC_SCOPED])),
                     ],
                     )])
         self.assertEqual(actual, expected)