Skip to content

Commit

Permalink
Fabric-scoping support in IDL parsing (#20819)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
andy31415 authored and pull[bot] committed Jun 23, 2023
1 parent 6a17e34 commit 1363024
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 13 deletions.
12 changes: 11 additions & 1 deletion scripts/idl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
9 changes: 6 additions & 3 deletions scripts/idl/matter_grammar.lark
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
55 changes: 46 additions & 9 deletions scripts/idl/matter_idl_parser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python

import enum
import logging

from lark import Lark
Expand All @@ -15,6 +16,10 @@
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.
Expand Down Expand Up @@ -128,6 +133,12 @@ def data_type(self, tokens):
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)
Expand Down Expand Up @@ -159,6 +170,9 @@ def attr_readonly(self, _):
def attr_nosubscribe(self, _):
return AttributeTag.NOSUBSCRIBE

def attribute_tags(self, tags):
return tags

def critical_priority(self, _):
return EventPriority.CRITICAL

Expand Down Expand Up @@ -204,14 +218,23 @@ def command_with_access(self, args):
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]
Expand Down Expand Up @@ -291,9 +314,17 @@ def ESCAPED_STRING(self, s):
# 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
Expand Down Expand Up @@ -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)


Expand Down
2 changes: 2 additions & 0 deletions scripts/idl/matter_idl_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
17 changes: 17 additions & 0 deletions scripts/idl/test_matter_idl_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def test_cluster_attribute(self):
attribute int32u rwAttr[] = 123;
readonly nosubscribe attribute int8s nosub[] = 0xaa;
readonly attribute nullable int8s isNullable = 0xab;
fabric readonly attribute int8s fabric_attr = 0x1234;
}
""")

Expand All @@ -112,6 +113,8 @@ def test_cluster_attribute(self):
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)
Expand Down Expand Up @@ -145,6 +148,7 @@ def test_attribute_access(self):
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;
}
""")

Expand Down Expand Up @@ -176,6 +180,11 @@ def test_attribute_access(self):
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)
Expand All @@ -190,6 +199,8 @@ def test_cluster_commands(self):
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=[
Expand All @@ -210,6 +221,12 @@ def test_cluster_commands(self):
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)
Expand Down

0 comments on commit 1363024

Please sign in to comment.