xref: /btstack/tool/btstack_parser.py (revision 80e33422a96c028b3a9c308fc4b9b874712dafb4)
1#!/usr/bin/env python
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'
12hci_cmds_c_path = 'src/hci_cmd.c'
13hci_cmds_h_path = 'src/hci_cmd.h'
14hci_h_path = 'src/hci.h'
15
16btstack_root = os.path.abspath(os.path.dirname(sys.argv[0]) + '/..')
17print ("BTstack root %s" % btstack_root)
18
19def set_btstack_root(path):
20    global btstack_root
21    btstack_root = path
22
23def assert_dir(path):
24    if not os.access(path, os.R_OK):
25        os.makedirs(path)
26
27def cap(x):
28    if x.lower() == 'btstack':
29        return 'BTstack'
30    acronyms = ['ATT', 'GAP', 'GATT', 'HCI', 'L2CAP', 'LE', 'RFCOMM', 'SM', 'SDP', 'UUID16', 'UUID128', 'HSP', 'HFP', 'ANCS']
31    if x.upper() in acronyms:
32        return x.upper()
33    return x.capitalize()
34
35def camel_case(name):
36    return ''.join(map(cap, name.split('_')))
37
38def camel_case_var(name):
39    if name in ['uuid128', 'uuid16']:
40        return name
41    camel = camel_case(name)
42    return camel[0].lower() + camel[1:]
43
44def read_defines(infile):
45    defines = dict()
46    with open (infile, 'rt') as fin:
47        for line in fin:
48            parts = re.match('#define\s+(\w+)\s+(\w*)',line)
49            if parts and len(parts.groups()) == 2:
50                (key, value) = parts.groups()
51                defines[key] = value
52    return defines
53
54def parse_defines():
55    global btstack_root
56    defines = dict()
57    defines.update(read_defines(btstack_root + '/' + hci_cmds_h_path))
58    defines.update(read_defines(btstack_root + '/' + hci_h_path))
59    defines.update(read_defines(btstack_root + '/' + bluetooth_h_path))
60    defines.update(read_defines(btstack_root + '/' + btstack_defines_h_path))
61    return defines
62
63def my_parse_events(path):
64    events = []
65    subevents = []
66    params = []
67    event_types = set()
68    format = None
69    with open (path, 'rt') as fin:
70        for line in fin:
71            parts = re.match('.*@format\s*(\w*)\s*', line)
72            if parts and len(parts.groups()) == 1:
73                format = parts.groups()[0]
74            parts = re.match('.*@param\s*(\w*)\s*', line)
75            if parts and len(parts.groups()) == 1:
76                param = parts.groups()[0]
77                params.append(param)
78            parts = re.match('\s*#define\s+(\w+)\s+(\w*)',line)
79            if parts and len(parts.groups()) == 2:
80                (key, value) = parts.groups()
81                if format != None:
82                    # renaming needed by Java Binding (... subevents are just enumerated with others due to event factory)
83                    if "_subevent_" in key.lower():
84                        subevents.append((value, key, format, params))
85                    else:
86                        events.append((value, key, format, params))
87                    event_types.add(key)
88                    params = []
89                    format = None
90    return (events, subevents, event_types)
91
92def parse_events():
93    global btstack_root
94
95    # parse bluetooth.h to get used events
96    (bluetooth_events, bluetooth_subevents, bluetooth_event_types) = my_parse_events(btstack_root + '/' + bluetooth_h_path)
97
98    # parse btstack_defines to get events
99    (btstack_events, btstack_subevents, btstack_event_types) = my_parse_events(btstack_root + '/' + btstack_defines_h_path)
100
101    # concat lists
102    (events, subvents, event_types) = (bluetooth_events + btstack_events, bluetooth_subevents + btstack_subevents, bluetooth_event_types | btstack_event_types)
103
104    return (events, subvents, event_types)
105
106def my_parse_commands(infile, convert_to_camel_case):
107    commands = []
108    with open (infile, 'rt') as fin:
109
110        params = []
111        for line in fin:
112
113            parts = re.match('.*@param\s*(\w*)\s*', line)
114            if parts and len(parts.groups()) == 1:
115                param = parts.groups()[0]
116                if convert_to_camel_case:
117                    param = camel_case_var(param)
118                else:
119                    param = param.lower()
120                params.append(param)
121                continue
122
123            declaration = re.match('const\s+hci_cmd_t\s+(\w+)[\s=]+', line)
124            if declaration:
125                command_name = declaration.groups()[0]
126                # drop _cmd suffix for daemon commands
127                if command_name.endswith('_cmd'):
128                    command_name = command_name[:-len('_cmd')]
129                if convert_to_camel_case:
130                    command_name = camel_case(command_name)
131                else:
132                    command_name = command_name.lower()
133                continue
134
135            definition = re.match('\s*OPCODE\\(\s*(\w+)\s*,\s+(\w+)\s*\\)\s*,\s\\"(\w*)\\".*', line)
136            if definition:
137                (ogf, ocf, format) = definition.groups()
138                if len(params) != len(format):
139                    params = []
140                    arg_counter = 1
141                    for f in format:
142                        arg_name = 'arg%u' % arg_counter
143                        params.append(arg_name)
144                        arg_counter += 1
145                commands.append((command_name, ogf, ocf, format, params))
146                params = []
147                continue
148    return commands
149
150def parse_commands(camel_case=True):
151    global btstack_root
152    commands = []
153    commands = commands = my_parse_commands(btstack_root + '/' + hci_cmds_c_path, camel_case)
154    commands = commands = my_parse_commands(btstack_root + '/' + daemon_cmds_c_path, camel_case)
155    return commands
156