1#!/usr/bin/env python
2
3#
4# Copyright 2016, Data61
5# Commonwealth Scientific and Industrial Research Organisation (CSIRO)
6# ABN 41 687 119 230.
7#
8# This software may be distributed and modified according to the terms of
9# the BSD 2-Clause license. Note that NO WARRANTY is provided.
10# See "LICENSE_BSD2.txt" for details.
11#
12# @TAG(D61_BSD)
13#
14
15import sys, tempita, re, argparse
16from lxml import etree
17
18# ------------------------------------------- Configuration ----------------------------------------
19DESCRIPTION = """\
20CIDL - Simple C IDL Compiler is made to save the trouble of manually implementing
21RPC interface stubs to marshal / unmarshal C style arguments and return value.
22CIDL uses a simple XML-based IDL language.
23"""
24TITLE_MESSAGE = """\
25/* DO NOT EDIT MANUALLY!!!
26   This file was generated by CIDL.
27
28   Copyright 2016, Data61
29   Commonwealth Scientific and Industrial Research Organisation (CSIRO)
30   ABN 41 687 119 230.
31
32   This software may be distributed and modified according to the terms of
33   the BSD 2-Clause license. Note that NO WARRANTY is provided.
34   See "LICENSE_BSD2.txt" for details.
35
36   @TAG(D61_BSD)
37*/
38"""
39IMPL_WEAK_ATTRIB = '__attribute__((weak))'
40HEADER_PFX = 'refos-rpc/'
41CPTR_TYPE = 'seL4_CPtr'
42CSLOT_TYPE = 'seL4_CSlot'
43TEMPLATE_ROOT = open('cidl_templates/root.py', 'r').read()
44TEMPLATE_CLIENT_HEADER = open('cidl_templates/client_header.py', 'r').read()
45TEMPLATE_CLIENT = open('cidl_templates/client.py', 'r').read()
46TEMPLATE_SERVER_HEADER = open('cidl_templates/server_header.py', 'r').read()
47TEMPLATE_SERVER = open('cidl_templates/server.py', 'r').read()
48TEMPLATE_DISPATCHER = open('cidl_templates/dispatcher.py', 'r').read()
49TEMPLATE_DISPATCHER_CONTAINER = open('cidl_templates/dispatcher_container.py', 'r').read()
50TEMPLATE_ENUM = open('cidl_templates/enum.py', 'r').read()
51
52# ------------------------------------------ Helper Functions --------------------------------------
53
54def get_if_name(if_name):
55    if_name = re.sub(r'.xml$', '', if_name.strip())
56    if_name = re.search('([^\/]*)$', if_name).group(0)
57    if_name = re.sub(r'_interface$', '', if_name)
58    return if_name
59
60def get_headerfile_name(if_name, client_mode):
61    return (HEADER_PFX + get_if_name(if_name) + '_%s.h')%('client' if client_mode else 'server')
62
63def preprocess(filestr, pre_process = True):
64    if not pre_process:
65        return filestr
66    filestr_ = ""
67    for fileline in filestr.splitlines():
68        if fileline.startswith("#") == False:
69            filestr_ += fileline
70    return filestr_.replace("\n",'').replace(r'    ','').replace(r'____', '    ').\
71           replace("\\n", "\n").replace("\\\\", "\\")
72
73# ---------------------------------------- Arguments Processing ------------------------------------
74
75CONNECT_EP = ''
76def process_arg(name_idl, type_idl, dr = '', mode_idl = None, lenvar_idl = ''):
77    dir_idl = 'in';
78    if dr == 'out':
79        dir_idl = 'out'
80
81    apfx = ''
82    aref = ''
83    apsfx = ''
84
85    if type_idl in ['int', 'int32_t', 'uint32_t', 'char', 'uintptr_t',
86                    'size_t', 'void*', 'cslot', CSLOT_TYPE]:
87        type_internal = 'uint'
88    elif type_idl in ['cptr', CPTR_TYPE]:
89        type_internal = 'cptr'
90    elif type_idl == 'char*':
91        type_internal = 'str'
92    elif type_idl.endswith('*'):
93        type_internal = 'buf'
94        if mode_idl == 'array': apfx = '_array'; apsfx = ', ' + lenvar_idl
95    else:
96        type_internal = 'buf'
97        aref = '&'
98
99    if mode_idl is None: mode_idl = 'normal'
100    if mode_idl == 'length': dir_idl = 'length'
101    if mode_idl == 'connect_ep':
102        global CONNECT_EP;
103        CONNECT_EP = name_idl
104        dir_idl = 'neither'
105
106    return (dir_idl, (type_idl, type_internal, name_idl, mode_idl, dir_idl, apfx, aref, apsfx))
107
108def process_arg_idl(arg_idl):
109    return process_arg(arg_idl.get("name"), arg_idl.get("type"), arg_idl.get("dir"),\
110                       arg_idl.get("mode"), arg_idl.get("lenvar"))
111
112def process_arg_last(alist, return_type = '', ralist = []):
113    if return_type != '' and return_type != 'void':
114        (_, arg_obj) = process_arg('__ret__', return_type, 'out')
115        alist.append(arg_obj)
116        ralist.append(arg_obj)
117    if len(alist) <= 0:
118        return
119# ---------------------------------------- Generator functions -------------------------------------
120def process_function(func_idl, template_str, dct_func = {}, dbg = True):
121    global CONNECT_EP
122
123    alist = []
124    oalist = []
125    calist = []
126    ralist = []
127
128    for arg_idl in func_idl:
129        (dr, arg_obj) = process_arg_idl(arg_idl)
130        if arg_obj is None:
131            continue
132        calist.append(arg_obj)
133        if dr == 'in':
134            alist.append(arg_obj)
135        elif dr == 'out':
136            oalist.append(arg_obj)
137
138    process_arg_last(alist)
139    process_arg_last(oalist, func_idl.get('return'), ralist)
140    process_arg_last(calist)
141
142    dct_func['alist'] = alist
143    dct_func['oalist'] = oalist
144    dct_func['calist'] = calist
145    dct_func['ralist'] = ralist
146    dct_func['fname'] = func_idl.get('name')
147    dct_func['return_type'] = func_idl.get('return')
148    dct_func['comment_text'] = re.sub(r'^ +', '   ', func_idl.text.strip(), flags = re.MULTILINE)
149    dct_func['connect_ep'] = str(CONNECT_EP)
150    dct_func['weak_attrib'] = IMPL_WEAK_ATTRIB if dbg else ''
151
152    return tempita.Template(template_str).substitute(dct_func)
153
154def process(filename, client, header, disp, pre_process, dbg):
155    xml_idl = etree.parse(filename).getroot()
156
157    dct_root = {}
158    dct_root['includes'] = []
159    dct_root['includes'].append('#include <%srpc.h>' % HEADER_PFX)
160    dct_root['ifname'] = get_if_name(filename)
161    dct_root['label_min'] = xml_idl.get('label_min');
162    dct_root['default_connect_ep'] = xml_idl.get('connect_ep')
163    dct_root['header_mode'] = header
164    dct_root['client_mode'] = client
165    if not header:
166        dct_root['includes'].append('#include <%s>' % get_headerfile_name(filename, client))
167
168    if disp:
169        template_str = TEMPLATE_DISPATCHER
170    elif header:
171        template_str = TEMPLATE_CLIENT_HEADER if client else TEMPLATE_SERVER_HEADER
172    else:
173        template_str = TEMPLATE_CLIENT if client else TEMPLATE_SERVER
174
175    template_str = preprocess(template_str, pre_process)
176    template_enum = preprocess(TEMPLATE_ENUM, pre_process)
177    template_root = preprocess(TEMPLATE_ROOT, pre_process)
178    if disp:
179        template_root = preprocess(TEMPLATE_DISPATCHER_CONTAINER, pre_process)
180
181    dct_root['func_list'] = []; dct_root['enum_list'] = []
182    for x in xml_idl:
183        if x.tag == 'include':
184            dct_root['includes'].append(re.sub(r'^\s*', '#include <', x.text.strip(),\
185                                               flags = re.MULTILINE) + '>\n')
186            continue
187        dct_root['func_list'].append(process_function(x, template_str, dct_root, dbg))
188        dct_root['enum_list'].append(process_function(x, template_enum, dct_root, dbg))
189    print(TITLE_MESSAGE)
190    print(tempita.Template(template_root).substitute(dct_root))
191
192parser = argparse.ArgumentParser(description = DESCRIPTION)
193parser.add_argument('-r', '--header', action='store_true',\
194    help='generate header declarations.')
195parser.add_argument('-c', '--client', action='store_true',\
196    help='generate header/src files for the RPC client.')
197parser.add_argument('-s', '--server', action='store_true',\
198    help='generate header/src files for the RPC server.')
199parser.add_argument('-d', '--dispatcher', action='store_true',\
200    help='generate dispatcher source file for RPC server.')
201parser.add_argument('-n', '--no_preprocess', action='store_false',\
202    help='skip pre-process template files.')
203parser.add_argument('-g', '--debug', action='store_true',\
204    help='debug mode, adds weak symbols to handler implementation functions.')
205parser.add_argument('-v', '--version', action='version',\
206    version='%(prog)s 1.2 Wed 14 Aug 2013 13:57:15 EST ')
207parser.add_argument('filename', metavar='IDL_FILE', action='store', \
208    help='the XML-based IDL file to compile.')
209args = parser.parse_args()
210
211process(args.filename, args.client, args.header, args.dispatcher, args.no_preprocess, args.debug)
212