|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +import logging |
| 4 | + |
| 5 | +from lark import Lark |
| 6 | +from lark.visitors import Transformer, v_args |
| 7 | + |
| 8 | +try: |
| 9 | + from .matter_idl_types import * |
| 10 | +except: |
| 11 | + import os |
| 12 | + import sys |
| 13 | + sys.path.append(os.path.abspath(os.path.dirname(__file__))) |
| 14 | + |
| 15 | + from matter_idl_types import * |
| 16 | + |
| 17 | + |
| 18 | +class MatterIdlTransformer(Transformer): |
| 19 | + """A transformer capable to transform data |
| 20 | + parsed by Lark according to matter_grammar.lark |
| 21 | + """ |
| 22 | + |
| 23 | + def number(self, tokens): |
| 24 | + """Numbers in the grammar are integers or hex numbers. |
| 25 | + """ |
| 26 | + if len(tokens) != 1: |
| 27 | + raise Error("Unexpected argument counts") |
| 28 | + |
| 29 | + n = tokens[0].value |
| 30 | + if n.startswith('0x'): |
| 31 | + return int(n[2:], 16) |
| 32 | + else: |
| 33 | + return int(n) |
| 34 | + |
| 35 | + def id(self, tokens): |
| 36 | + """An id is a string containing an identifier |
| 37 | + """ |
| 38 | + if len(tokens) != 1: |
| 39 | + raise Error("Unexpected argument counts") |
| 40 | + return tokens[0].value |
| 41 | + |
| 42 | + def type(self, tokens): |
| 43 | + """A type is just a string for the type |
| 44 | + """ |
| 45 | + if len(tokens) != 1: |
| 46 | + raise Error("Unexpected argument counts") |
| 47 | + return tokens[0].value |
| 48 | + |
| 49 | + def data_type(self, tokens): |
| 50 | + if len(tokens) == 1: |
| 51 | + return DataType(name=tokens[0]) |
| 52 | + # Just a string for data type |
| 53 | + elif len(tokens) == 2: |
| 54 | + return DataType(name=tokens[0], max_length=tokens[1]) |
| 55 | + else: |
| 56 | + raise Error("Unexpected size for data type") |
| 57 | + |
| 58 | + @v_args(inline=True) |
| 59 | + def enum_entry(self, id, number): |
| 60 | + return EnumEntry(name=id, code=number) |
| 61 | + |
| 62 | + @v_args(inline=True) |
| 63 | + def enum(self, id, type, *entries): |
| 64 | + return Enum(name=id, base_type=type, entries=list(entries)) |
| 65 | + |
| 66 | + def field(self, args): |
| 67 | + data_type, name = args[0], args[1] |
| 68 | + is_list = (len(args) == 4) |
| 69 | + code = args[-1] |
| 70 | + |
| 71 | + return Field(data_type=data_type, name=name, code=code, is_list=is_list) |
| 72 | + |
| 73 | + def optional(self, _): |
| 74 | + return FieldAttribute.OPTIONAL |
| 75 | + |
| 76 | + def nullable(self, _): |
| 77 | + return FieldAttribute.NULLABLE |
| 78 | + |
| 79 | + def attr_readonly(self, _): |
| 80 | + return AttributeTag.READABLE |
| 81 | + |
| 82 | + def attr_global(self, _): |
| 83 | + return AttributeTag.GLOBAL |
| 84 | + |
| 85 | + def critical_priority(self, _): |
| 86 | + return EventPriority.CRITICAL |
| 87 | + |
| 88 | + def info_priority(self, _): |
| 89 | + return EventPriority.INFO |
| 90 | + |
| 91 | + def debug_priority(self, _): |
| 92 | + return EventPriority.DEBUG |
| 93 | + |
| 94 | + def endpoint_server_cluster(self, _): |
| 95 | + return EndpointContentType.SERVER_CLUSTER |
| 96 | + |
| 97 | + def endpoint_binding_to_cluster(self, _): |
| 98 | + return EndpointContentType.CLIENT_BINDING |
| 99 | + |
| 100 | + def struct_field(self, args): |
| 101 | + # Last argument is the named_member, the rest |
| 102 | + # are attributes |
| 103 | + field = args[-1] |
| 104 | + field.attributes = set(args[:-1]) |
| 105 | + return field |
| 106 | + |
| 107 | + def server_cluster(self, _): |
| 108 | + return ClusterSide.SERVER |
| 109 | + |
| 110 | + def client_cluster(self, _): |
| 111 | + return ClusterSide.CLIENT |
| 112 | + |
| 113 | + def command(self, args): |
| 114 | + # A command has 3 arguments if no input or |
| 115 | + # 4 arguments if input parameter is available |
| 116 | + param_in = None |
| 117 | + if len(args) > 3: |
| 118 | + param_in = args[1] |
| 119 | + return Command(name=args[0], input_param=param_in, output_param=args[-2], code=args[-1]) |
| 120 | + |
| 121 | + def event(self, args): |
| 122 | + return Event(priority=args[0], name=args[1], code=args[2], fields=args[3:], ) |
| 123 | + |
| 124 | + def attribute(self, args): |
| 125 | + tags = set(args[:-1]) |
| 126 | + # until we support write only (and need a bit of a reshuffle) |
| 127 | + # if the 'attr_readonly == READABLE' is not in the list, we make things |
| 128 | + # read/write |
| 129 | + if AttributeTag.READABLE not in tags: |
| 130 | + tags.add(AttributeTag.READABLE) |
| 131 | + tags.add(AttributeTag.WRITABLE) |
| 132 | + |
| 133 | + return Attribute(definition=args[-1], tags=tags) |
| 134 | + |
| 135 | + @v_args(inline=True) |
| 136 | + def struct(self, id, *fields): |
| 137 | + return Struct(name=id, fields=list(fields)) |
| 138 | + |
| 139 | + @v_args(inline=True) |
| 140 | + def request_struct(self, value): |
| 141 | + value.tag = StructTag.REQUEST |
| 142 | + return value |
| 143 | + |
| 144 | + @v_args(inline=True) |
| 145 | + def response_struct(self, value): |
| 146 | + value.tag = StructTag.RESPONSE |
| 147 | + return value |
| 148 | + |
| 149 | + @v_args(inline=True) |
| 150 | + def endpoint(self, number, *clusters): |
| 151 | + endpoint = Endpoint(number=number) |
| 152 | + |
| 153 | + for t, name in clusters: |
| 154 | + if t == EndpointContentType.CLIENT_BINDING: |
| 155 | + endpoint.client_bindings.append(name) |
| 156 | + elif t == EndpointContentType.SERVER_CLUSTER: |
| 157 | + endpoint.server_clusters.append(name) |
| 158 | + else: |
| 159 | + raise Error("Unknown endpoint content: %r" % t) |
| 160 | + |
| 161 | + return endpoint |
| 162 | + |
| 163 | + @v_args(inline=True) |
| 164 | + def endpoint_cluster(self, t, id): |
| 165 | + return (t, id) |
| 166 | + |
| 167 | + @v_args(inline=True) |
| 168 | + def cluster(self, side, name, code, *content): |
| 169 | + result = Cluster(side=side, name=name, code=code) |
| 170 | + |
| 171 | + for item in content: |
| 172 | + if type(item) == Enum: |
| 173 | + result.enums.append(item) |
| 174 | + elif type(item) == Event: |
| 175 | + result.events.append(item) |
| 176 | + elif type(item) == Attribute: |
| 177 | + result.attributes.append(item) |
| 178 | + elif type(item) == Struct: |
| 179 | + result.structs.append(item) |
| 180 | + elif type(item) == Command: |
| 181 | + result.commands.append(item) |
| 182 | + else: |
| 183 | + raise Error("UNKNOWN cluster content item: %r" % item) |
| 184 | + |
| 185 | + return result |
| 186 | + |
| 187 | + def idl(self, items): |
| 188 | + idl = Idl() |
| 189 | + |
| 190 | + for item in items: |
| 191 | + if type(item) == Enum: |
| 192 | + idl.enums.append(item) |
| 193 | + elif type(item) == Struct: |
| 194 | + idl.structs.append(item) |
| 195 | + elif type(item) == Cluster: |
| 196 | + idl.clusters.append(item) |
| 197 | + elif type(item) == Endpoint: |
| 198 | + idl.endpoints.append(item) |
| 199 | + else: |
| 200 | + raise Error("UNKNOWN idl content item: %r" % item) |
| 201 | + |
| 202 | + return idl |
| 203 | + |
| 204 | + |
| 205 | +def CreateParser(): |
| 206 | + return Lark.open('matter_grammar.lark', rel_to=__file__, start='idl', parser='lalr', transformer=MatterIdlTransformer()) |
| 207 | + |
| 208 | + |
| 209 | +if __name__ == '__main__': |
| 210 | + import click |
| 211 | + import coloredlogs |
| 212 | + |
| 213 | + # Supported log levels, mapping string values required for argument |
| 214 | + # parsing into logging constants |
| 215 | + __LOG_LEVELS__ = { |
| 216 | + 'debug': logging.DEBUG, |
| 217 | + 'info': logging.INFO, |
| 218 | + 'warn': logging.WARN, |
| 219 | + 'fatal': logging.FATAL, |
| 220 | + } |
| 221 | + |
| 222 | + @click.command() |
| 223 | + @click.option( |
| 224 | + '--log-level', |
| 225 | + default='INFO', |
| 226 | + type=click.Choice(__LOG_LEVELS__.keys(), case_sensitive=False), |
| 227 | + help='Determines the verbosity of script output.') |
| 228 | + @click.argument('filename') |
| 229 | + def main(log_level, filename=None): |
| 230 | + coloredlogs.install(level=__LOG_LEVELS__[ |
| 231 | + log_level], fmt='%(asctime)s %(levelname)-7s %(message)s') |
| 232 | + |
| 233 | + logging.info("Starting to parse ...") |
| 234 | + data = CreateParser().parse(open(filename).read()) |
| 235 | + logging.info("Parse completed") |
| 236 | + |
| 237 | + logging.info("Data:") |
| 238 | + print(data) |
| 239 | + |
| 240 | + main() |
0 commit comments