Output
57 |
58 | ```c
59 | // Include required definitions first.
60 | #include "py/obj.h"
61 | #include "py/runtime.h"
62 | #include "py/builtin.h"
63 |
64 | //Adds two integers
65 | //:param a:
66 | //:param b:
67 | //:return:a + b
68 | STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
69 | mp_int_t a = mp_obj_get_int(a_obj);
70 | mp_int_t b = mp_obj_get_int(b_obj);
71 | mp_int_t ret_val;
72 |
73 | //Your code here
74 |
75 | return mp_obj_new_int(ret_val);
76 | }
77 | MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints);
78 |
79 | STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
80 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) },
81 | { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
82 | };
83 |
84 | STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);
85 | const mp_obj_module_t example_user_cmodule = {
86 | .base = {&mp_type_module},
87 | .globals = (mp_obj_dict_t*)&example_module_globals,
88 | };
89 |
90 | MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED);
91 | ```
92 |
93 |
94 | This will parse all the functions in the module and attach them to the same namespace in micropython.
95 | ##### Note: It will only generate the boilerplate code and not the actual code that does the work such as a + b
96 | After editing the code in the template at the place marked //Code goes here you can follow the instructions
97 | [here](http://docs.micropython.org/en/latest/develop/cmodules.html#basic-example) for modifying
98 | the Make File and building the module into your micro python deployment.
99 |
100 | You should then be able to use the module in micro python by typing
101 | ```python
102 | import example # from example.c compiled into micropython
103 | example.add_ints(1, 2)
104 | # prints 3
105 | ```
106 | ##### Note: This example.py is the one compiled into the micropython source and not the file we created earlier
107 |
108 | ### Advanced usage
109 | If you added two more functions to the original example.py
110 | ```python
111 | def lots_of_parameters(a: int, b: float, c: tuple, d: object, e: str) -> None:
112 | """
113 | :param a:
114 | :param b:
115 | :param c:
116 | :param d:
117 | :return:
118 | """
119 |
120 | def readfrom_mem(addr: int = 0, memaddr: int = 0, arg: object = None, *, addrsize: int = 8) -> str:
121 | """
122 | :param addr:
123 | :param memaddr:
124 | :param arg:
125 | :param addrsize: Keyword only arg
126 | :return:
127 | """
128 | ```
129 |
130 | logs_of_parameters shows the types of types you can parse in. You always need to annotate each parameter and the return.
131 | readfrom_mem shows that you can set default values for certain parameters and specify that addrsize is a keyword only
132 | argument.
133 |
134 | At the c level in micropython, there is only three ways of implementing a function.
135 | ##### Basic Case
136 | ```python
137 | def foo(a, b, c): # 0 to 3 args
138 | pass
139 | ```
140 | ```c
141 | MP_DEFINE_CONST_FUN_OBJ_X // Where x is 0 to 3 args
142 | ```
143 | ##### Greater than three positional args
144 | ```python
145 | def foo(*args):
146 | pass
147 | ```
148 | ```c
149 | MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN
150 | ```
151 | ##### Arbitary args
152 | ```python
153 | def foo(*args, **kwargs):
154 | pass
155 | ```
156 | ```c
157 | MP_DEFINE_CONST_FUN_OBJ_KW
158 | ```
159 | Each successively increasing the boiler plate to conveniently accessing the variables.
160 |
161 | Output
162 |
163 | ```c
164 | // Include required definitions first.
165 | #include "py/obj.h"
166 | #include "py/runtime.h"
167 | #include "py/builtin.h"
168 |
169 | //Adds two integers
170 | //:param a:
171 | //:param b:
172 | //:return:a + b
173 | STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
174 | mp_int_t a = mp_obj_get_int(a_obj);
175 | mp_int_t b = mp_obj_get_int(b_obj);
176 | mp_int_t ret_val;
177 |
178 | //Your code here
179 |
180 | return mp_obj_new_int(ret_val);
181 | }
182 | MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints);
183 | //
184 | //:param a:
185 | //:param b:
186 | //:param c:
187 | //:param d:
188 | //:return:
189 | //
190 | STATIC mp_obj_t example_lots_of_parameters(size_t n_args, const mp_obj_t *args) {
191 | mp_int_t a = mp_obj_get_int(a_obj);
192 | mp_float_t b = mp_obj_get_float(b_obj);
193 | mp_obj_t *c = NULL;
194 | size_t c_len = 0;
195 | mp_obj_get_array(c_arg, &c_len, &c);
196 | mp_obj_t d args[ARG_d].u_obj;
197 |
198 | //Your code here
199 |
200 | return mp_const_none;
201 | }
202 | MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(example_lots_of_parameters_obj, 4, 4, example_lots_of_parameters);
203 | //
204 | //:param addr:
205 | //:param memaddr:
206 | //:param arg:
207 | //:param addrsize: Keyword only arg
208 | //:return:
209 | //
210 | STATIC mp_obj_t example_readfrom_mem(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
211 | enum { ARG_addr, ARG_memaddr, ARG_arg, ARG_addrsize };
212 | STATIC const mp_arg_t example_readfrom_mem_allowed_args[] = {
213 | { MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } },
214 | { MP_QSTR_memaddr, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } },
215 | { MP_QSTR_arg, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
216 | { MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 8 } },
217 | };
218 |
219 | mp_arg_val_t args[MP_ARRAY_SIZE(example_readfrom_mem_allowed_args)];
220 | mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args,
221 | MP_ARRAY_SIZE(example_readfrom_mem_allowed_args), example_readfrom_mem_allowed_args, args);
222 |
223 | mp_int_t addr = args[ARG_addr].u_int;
224 | mp_int_t memaddr = args[ARG_memaddr].u_int;
225 | mp_obj_t arg = args[ARG_arg].u_obj;
226 | mp_int_t addrsize = args[ARG_addrsize].u_int;
227 |
228 | //Your code here
229 |
230 | return mp_obj_new_str(, );
231 | }
232 | MP_DEFINE_CONST_FUN_OBJ_KW(example_readfrom_mem_obj, 1, example_readfrom_mem);
233 |
234 | STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
235 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) },
236 | { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
237 | { MP_ROM_QSTR(MP_QSTR_lots_of_parameters), MP_ROM_PTR(&example_lots_of_parameters_obj) },
238 | { MP_ROM_QSTR(MP_QSTR_readfrom_mem), MP_ROM_PTR(&example_readfrom_mem_obj) },
239 | };
240 |
241 | STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);
242 | const mp_obj_module_t example_user_cmodule = {
243 | .base = {&mp_type_module},
244 | .globals = (mp_obj_dict_t*)&example_module_globals,
245 | };
246 |
247 | MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED);
248 | ```
249 |
250 |
251 | #### Adding fully implemented c functions
252 | Going one step further you can directly add c code to be substituted into the c generated code where the
253 | "//Your code here comment" is.
254 |
255 | For example, starting with a fresh example.py you could define it as.
256 |
257 | ```python
258 | def add_ints(a: int, b: int) -> int:
259 | """Adds two integers
260 | :param a:
261 | :param b:
262 | :return:a + b"""
263 | add_ints.code = " ret_val = a + b;"
264 | ```
265 | to get a fully defined function in c
266 |
267 | Output
268 |
269 | ```c
270 | // Include required definitions first.
271 | #include "py/obj.h"
272 | #include "py/runtime.h"
273 | #include "py/builtin.h"
274 |
275 | //Adds two integers
276 | //:param a:
277 | //:param b:
278 | //:return:a + b
279 | STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
280 | mp_int_t a = mp_obj_get_int(a_obj);
281 | mp_int_t b = mp_obj_get_int(b_obj);
282 | mp_int_t ret_val;
283 |
284 | ret_val = a + b;
285 |
286 | return mp_obj_new_int(ret_val);
287 | }
288 | MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints);
289 |
290 | STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
291 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) },
292 | { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) },
293 | };
294 |
295 | STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);
296 | const mp_obj_module_t example_user_cmodule = {
297 | .base = {&mp_type_module},
298 | .globals = (mp_obj_dict_t*)&example_module_globals,
299 | };
300 |
301 | MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED);
302 | ```
303 |
304 |
305 | #### Using functions without a module definition
306 | If you don't need the fully module boiler plate, you can generate individual functions with
307 | ```python
308 | import ustubby
309 | def add_ints(a: int, b: int) -> int:
310 | """add two ints"""
311 | add_ints.code = " ret_val = a + b;"
312 | add_ints.__module__ = "new_module"
313 |
314 | print(ustubby.stub_function(add_ints))
315 | ```
316 |
317 | ```c
318 | //add two ints
319 | STATIC mp_obj_t new_module_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) {
320 | mp_int_t a = mp_obj_get_int(a_obj);
321 | mp_int_t b = mp_obj_get_int(b_obj);
322 | mp_int_t ret_val;
323 |
324 | ret_val = a + b;
325 |
326 | return mp_obj_new_int(ret_val);
327 | }
328 | MP_DEFINE_CONST_FUN_OBJ_2(new_module_add_ints_obj, new_module_add_ints);
329 | ```
330 |
331 | #### Parsing Litex Files
332 | uStubby is also trying to support c code generation from Litex files such as
333 | ```csv
334 | #--------------------------------------------------------------------------------
335 | # Auto-generated by Migen (5585912) & LiteX (e637aa65) on 2019-08-04 03:04:29
336 | #--------------------------------------------------------------------------------
337 | csr_register,cas_leds_out,0x82000800,1,rw
338 | csr_register,cas_buttons_ev_status,0x82000804,1,rw
339 | csr_register,cas_buttons_ev_pending,0x82000808,1,rw
340 | csr_register,cas_buttons_ev_enable,0x8200080c,1,rw
341 | csr_register,ctrl_reset,0x82001000,1,rw
342 | csr_register,ctrl_scratch,0x82001004,4,rw
343 | csr_register,ctrl_bus_errors,0x82001014,4,ro
344 | ```
345 | Currently only csr_register is supported. Please raise issues if you need to expand this feature.
346 | ```python
347 | import ustubby
348 | mod = ustubby.parse_csv("csr.csv")
349 | print(ustubby.stub_module(mod))
350 | ```
351 |
352 | ## Running the tests
353 | Install the test requirements with
354 | ```bash
355 | pip install -r requirements-test.txt
356 | ```
357 | Install the package in editable mode
358 | ```bash
359 | pip install -e .
360 | ```
361 | Run the tests
362 | ```bash
363 | pytest
364 | ```
365 |
366 | ## Check out the docs
367 |
368 | TBD
369 |
370 | ## Contributing
371 |
372 | Contributions are welcome. Get in touch or create a new pull request.
373 |
374 | ## Credits
375 | Inspired by
376 | - [Extending MicroPython: Using C for Good](https://youtu.be/fUb3Urw4H-E)
377 | - [Online C Stub Generator](https://gitlab.com/oliver.robson/mpy-c-stub-gen)
378 | - [Micropython](https://micropython.org)
379 |
380 | PyCon AU 2019 Sprints
381 |
382 | ## Authors
383 |
384 | * **Ryan Parry-Jones** - *Original Developer* - [pazzarpj](https://github.com/pazzarpj)
385 |
386 | See also the list of [contributors](https://github.com/pazzarpj/micropython-ustubby/contributors) who participated in this project.
387 |
388 | ## License
389 |
390 | This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details
391 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | testpaths = test
3 |
--------------------------------------------------------------------------------
/requirements-test.txt:
--------------------------------------------------------------------------------
1 | pytest
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pazzarpj/micropython-ustubby/08890e47b71d01e8e97a741d3c2db6c0a9689882/requirements.txt
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from setuptools import setup, find_packages
3 |
4 | import re
5 | import sys
6 | import os
7 |
8 | BASE_LOCATION = os.path.abspath(os.path.dirname(__file__))
9 |
10 | VERSION_FILE = os.path.join(BASE_LOCATION, "src", "ustubby", "__init__.py")
11 | REQUIRES_FILE = 'requirements.txt'
12 | DEPENDENCIES_FILE = None
13 |
14 |
15 | def filter_comments(fd):
16 | no_comments = list(filter(lambda l: l.strip().startswith("#") is False, fd.readlines()))
17 | return list(filter(lambda l: l.strip().startswith("-") is False, no_comments))
18 |
19 |
20 | def readfile(filename, func):
21 | try:
22 | with open(os.path.join(BASE_LOCATION, filename)) as f:
23 | data = func(f)
24 | except (IOError, IndexError):
25 | sys.stderr.write(u"""
26 | Can't find '%s' file. This doesn't seem to be a valid release.
27 | """ % filename)
28 | sys.exit(1)
29 | return data
30 |
31 |
32 | def get_version():
33 | with open(VERSION_FILE, 'r') as f:
34 | data = f.read()
35 | m = re.search(r"__version__ ?= ?\"[\d.]+\"", data)
36 | res = m.group(0)
37 | if res:
38 | ret = re.search(r"(?<=\")[\d\.]+", res).group(0)
39 | if ret:
40 | return ret
41 | raise ValueError("No version for ustubby found")
42 |
43 |
44 | def get_requires():
45 | return readfile(REQUIRES_FILE, filter_comments)
46 |
47 |
48 | def get_dependencies():
49 | return readfile(DEPENDENCIES_FILE, filter_comments)
50 |
51 |
52 | setup(
53 | name="ustubby",
54 | author="Ryan Parry-Jones",
55 | author_email="ryanspj+github@gmail.com",
56 | description="Micropython c stub generator",
57 | long_description=open("README.md").read(),
58 | long_description_content_type="text/markdown",
59 | package_dir={'': 'src'},
60 | packages=find_packages('src'),
61 | entry_points={
62 | 'console_scripts': [
63 | 'ustubby = ustubby.__main__:main'
64 | ]
65 | },
66 | url="https://github.com/pazzarpj/micropython-ustubby",
67 | version=get_version(),
68 | python_requires='>=3.6',
69 | dependency_links=[],
70 | include_package_data=True,
71 | zip_safe=False,
72 | classifiers=[
73 | "Development Status :: 4 - Beta",
74 | "Intended Audience :: Manufacturing",
75 | "License :: OSI Approved :: MIT License",
76 | "Programming Language :: Python :: 3 :: Only",
77 | "Programming Language :: Python :: 3.6",
78 | "Programming Language :: Python :: 3.7",
79 | "Programming Language :: Python :: 3.8",
80 | ],
81 | )
82 |
--------------------------------------------------------------------------------
/src/ustubby/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 | import inspect
3 | import types
4 | import csv
5 | from typing import Dict
6 |
7 | __version__ = "0.1.1"
8 |
9 |
10 | def string_template(base_str):
11 | def string_handle(*args, **kwargs):
12 | return base_str.format(*args, **kwargs)
13 |
14 | return string_handle
15 |
16 |
17 | type_handler = {
18 | int: string_template("\tmp_int_t {0} = mp_obj_get_int({0}_obj);"),
19 | float: string_template("\tmp_float_t {0} = mp_obj_get_float({0}_obj);"),
20 | bool: string_template("\tbool {0} = mp_obj_is_true({0}_obj);"),
21 | str: string_template("\tconst char* {0} = mp_obj_str_get_str({0}_obj);"),
22 | tuple: string_template(
23 | "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
24 | list: string_template(
25 | "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
26 | set: string_template(
27 | "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
28 | object: string_template("\tmp_obj_t {0} args[ARG_{0}].u_obj;"),
29 | "self": string_template("\tSELF_t *self = MP_OBJ_TO_PTR(self_in);"),
30 | }
31 | type_handler_arr = {
32 | int: string_template("\tmp_int_t {0} = mp_obj_get_int(args[{1}]);"),
33 | float: string_template("\tmp_float_t {0} = mp_obj_get_float(args[{1}]);"),
34 | bool: string_template("\tbool {0} = mp_obj_is_true(args[{1}]);"),
35 | str: string_template("\tconst char* {0} = mp_obj_str_get_str(args[{1}]);"),
36 | tuple: string_template(
37 | "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array(args[{1}], &{0}_len, &{0});"),
38 | list: string_template(
39 | "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array(args[{1}], &{0}_len, &{0});"),
40 | set: string_template(
41 | "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array(args[{1}], &{0}_len, &{0});"),
42 | object: string_template("\tmp_obj_t {0} args[ARG_{0}].u_obj;")
43 | }
44 |
45 | return_type_handler = {
46 | int: "\tmp_int_t ret_val;",
47 | float: "\tmp_float_t ret_val;",
48 | bool: "\tbool ret_val;",
49 | str: "",
50 | tuple: "",
51 | # tuple: string_template(
52 | # "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
53 | # list: string_template(
54 | # "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
55 | # set: string_template(
56 | # "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
57 | None: ""
58 | }
59 |
60 | return_handler = {
61 | int: "\treturn mp_obj_new_int(ret_val);",
62 | float: "\treturn mp_obj_new_float(ret_val);",
63 | bool: "\treturn mp_obj_new_bool(ret_val);",
64 | str: "\treturn mp_obj_new_str(