xref: /btstack/tool/btstack_parser.py (revision db806f4fd8e8d1df92cc0c13d105eac7ed7a8d1e)
1#!/usr/bin/env python3
2# BlueKitchen GmbH (c) 2014
3
4import re
5import os
6import sys
7
8# paths
9bluetooth_h_path = 'src/bluetooth.h'
10btstack_defines_h_path = 'src/btstack_defines.h'
11daemon_cmds_c_path = 'platform/daemon/src/daemon_cmds.c'
12daemon_cmds_h_path = 'platform/daemon/src/daemon_cmds.h'
13hci_cmds_c_path = 'src/hci_cmd.c'
14hci_cmds_h_path = 'src/hci_cmd.h'
15hci_h_path = 'src/hci.h'
16
17open_bracket = '['
18closing_bracket = ']'
19
20btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..')
21print ("BTstack root %s" % btstack_root)
22
23def set_btstack_root(path):
24    global btstack_root
25    btstack_root = path
26
27def assert_dir(path):
28    if not os.access(path, os.R_OK):
29        os.makedirs(path)
30
31def cap(x):
32    if x.lower() == 'btstack':
33        return 'BTstack'
34    acronyms = ['ATT', 'GAP', 'GATT', 'HCI', 'L2CAP', 'LE', 'RFCOMM', 'SM', 'SDP', 'UUID16', 'UUID128', 'HSP', 'HFP', 'ANCS']
35    if x.upper() in acronyms:
36        return x.upper()
37    return x.capitalize()
38
39def camel_case(name):
40    return ''.join(map(cap, name.split('_')))
41
42def camel_case_var(name):
43    if name in ['uuid128', 'uuid16']:
44        return name
45    camel = camel_case(name)
46    return camel[0].lower() + camel[1:]
47
48def read_defines(infile):
49    defines = dict()
50    with open (infile, 'rt') as fin:
51        for line in fin:
52            parts = re.match(r'#define\s+(\w+)\s+(\w*)[u]',line)
53            if parts and len(parts.groups()) == 2:
54                (key, value) = parts.groups()
55                defines[key] = value.lower()
56    return defines
57
58def parse_defines():
59    global btstack_root
60    defines = dict()
61    defines.update(read_defines(btstack_root + '/' + hci_cmds_h_path))
62    defines.update(read_defines(btstack_root + '/' + hci_h_path))
63    defines.update(read_defines(btstack_root + '/' + bluetooth_h_path))
64    defines.update(read_defines(btstack_root + '/' + btstack_defines_h_path))
65    return defines
66
67def my_parse_events(path):
68    events = []
69    subevents = []
70    params = []
71    event_types = set()
72    format = None
73    with open (path, 'rt') as fin:
74        for line in fin:
75            parts = re.match(r'.*@format\s*([a-zA-Z0-9_' + re.escape(open_bracket) + re.escape(closing_bracket) + r']*)\s*', line)
76            if parts and len(parts.groups()) == 1:
77                format = parts.groups()[0]
78            parts = re.match(r'.*@param\s*(\w*)\s*', line)
79            if parts and len(parts.groups()) == 1:
80                param = parts.groups()[0]
81                params.append(param)
82            parts = re.match(r'\s*#define\s+(\w+)\s+(\w*)[u]',line)
83            if parts and len(parts.groups()) == 2:
84                (key, value) = parts.groups()
85                if format != None:
86                    # renaming needed by Java Binding (... subevents are just enumerated with others due to event factory)
87                    if "_subevent_" in key.lower():
88                        subevents.append((value, key, format, params))
89                    else:
90                        events.append((value, key, format, params))
91                    event_types.add(key)
92                    params = []
93                    format = None
94    return (events, subevents, event_types)
95
96def parse_events():
97    global btstack_root
98
99    # parse bluetooth.h to get used events
100    (bluetooth_events, bluetooth_subevents, bluetooth_event_types) = my_parse_events(btstack_root + '/' + bluetooth_h_path)
101
102    # parse btstack_defines to get events
103    (btstack_events, btstack_subevents, btstack_event_types) = my_parse_events(btstack_root + '/' + btstack_defines_h_path)
104
105    # concat lists
106    (events, subvents, event_types) = (bluetooth_events + btstack_events, bluetooth_subevents + btstack_subevents, bluetooth_event_types | btstack_event_types)
107
108    return (events, subvents, event_types)
109def my_parse_opcodes(infile, convert_to_camel_case):
110    opcodes = {}
111    with open (infile, 'rt') as fin:
112        for line in fin:
113            definition = re.match(r'\s*(DAEMON_OPCODE_\w+)\s*=\s*DAEMON_OPCODE\s*\(\s*(\w+)\s*\).*', line)
114            if definition:
115                (opcode, daemon_ocf) = definition.groups()
116                # opcodes.append((opcode, 'OGF_BTSTACK', daemon_ocf))
117                opcodes[opcode] = ('OGF_BTSTACK', daemon_ocf)
118            definition = re.match(r'\s*(HCI_OPCODE_\w+)\s*=\s*HCI_OPCODE\s*\(\s*(\w+)\s*,\s*(\w+)\s*\).*', line)
119            if definition:
120                (opcode, ogf, ocf) = definition.groups()
121                # opcodes.append((opcode, ogf, ocf))
122                opcodes[opcode] = (ogf, ocf)
123    return opcodes
124
125def parse_opcodes(camel_case=True):
126    global btstack_root
127    opcodes = {}
128    opcodes.update(my_parse_opcodes(btstack_root + '/' + hci_cmds_h_path, camel_case))
129    opcodes.update(my_parse_opcodes(btstack_root + '/' + daemon_cmds_h_path, camel_case))
130    return opcodes
131
132def my_parse_commands(infile, opcodes, convert_to_camel_case):
133    commands = []
134    with open (infile, 'rt') as fin:
135
136        params = []
137        for line in fin:
138
139            parts = re.match(r'.*@param\s*(\w*)\s*', line)
140            if parts and len(parts.groups()) == 1:
141                param = parts.groups()[0]
142                if convert_to_camel_case:
143                    param = camel_case_var(param)
144                else:
145                    param = param.lower()
146                params.append(param)
147                continue
148
149            declaration = re.match(r'const\s+hci_cmd_t\s+(\w+)[\s=]+', line)
150            if declaration:
151                command_name = declaration.groups()[0]
152                # drop _cmd suffix for daemon commands
153                if command_name.endswith('_cmd'):
154                    command_name = command_name[:-len('_cmd')]
155                if convert_to_camel_case:
156                    command_name = camel_case(command_name)
157                else:
158                    command_name = command_name.lower()
159                continue
160
161            # HCI_OPCODE or DAEMON_OPCODE definition
162            definition = re.match(r'\s*(HCI_OPCODE_\w+|DAEMON_OPCODE_\w+)\s*,\s\"(\w*)\".*', line)
163            if definition:
164                (opcode, format) = definition.groups()
165                if len(params) != len(format):
166                    params = []
167                    arg_counter = 1
168                    for f in format:
169                        arg_name = 'arg%u' % arg_counter
170                        params.append(arg_name)
171                        arg_counter += 1
172                (ogf, ocf) = opcodes[opcode]
173                commands.append((command_name, ogf, ocf, format, params))
174                params = []
175                continue
176
177    return commands
178
179def parse_commands(camel_case=True):
180    global btstack_root
181    opcodes = parse_opcodes(camel_case)
182
183    commands = []
184    commands += my_parse_commands(btstack_root + '/' + hci_cmds_c_path, opcodes, camel_case)
185    commands += my_parse_commands(btstack_root + '/' + daemon_cmds_c_path, opcodes, camel_case)
186    return commands
187
188def parse_daemon_commands(camel_case=True):
189    global btstack_root
190    opcodes = parse_opcodes(camel_case)
191    return my_parse_commands(btstack_root + '/' + daemon_cmds_c_path, opcodes, camel_case)
192
193def print_opcode_enum(commands):
194    print("typedef enum {")
195    for command in commands:
196        (name, ogf, ocf, format, params) = command
197        print("    DAEMON_OPCODE_%s = HCI_OPCODE (%s, %s)," % (name.upper(), ogf, ocf))
198    print("} daemon_opcode_t;")
199
200
201