Skip to content

Commit 0424f28

Browse files
authored
Merge pull request #8 from pazzarpj/feature/object-based-parsing
Feature/object based parsing
2 parents fbd4c51 + 80dce09 commit 0424f28

File tree

2 files changed

+333
-40
lines changed

2 files changed

+333
-40
lines changed

src/ustubby/__init__.py

Lines changed: 263 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
from __future__ import annotations
12
import inspect
23
import types
34
import csv
5+
from typing import Dict
46

57
__version__ = "0.1.1"
68

@@ -12,13 +14,6 @@ def string_handle(*args, **kwargs):
1214
return string_handle
1315

1416

15-
def expand_newlines(lst_in):
16-
new_list = []
17-
for line in lst_in:
18-
new_list.extend(line.replace('\t', ' ').split('\n'))
19-
return new_list
20-
21-
2217
type_handler = {
2318
int: string_template("\tmp_int_t {0} = mp_obj_get_int({0}_obj);"),
2419
float: string_template("\tmp_float_t {0} = mp_obj_get_float({0}_obj);"),
@@ -33,6 +28,267 @@ def expand_newlines(lst_in):
3328
object: string_template("\tmp_obj_t {0} args[ARG_{0}].u_obj;")
3429
}
3530

31+
return_type_handler = {
32+
int: "\tmp_int_t ret_val;",
33+
float: "\tmp_float_t ret_val;",
34+
bool: "\tbool ret_val;",
35+
str: "",
36+
# tuple: string_template(
37+
# "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
38+
# list: string_template(
39+
# "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
40+
# set: string_template(
41+
# "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
42+
None: ""
43+
}
44+
45+
return_handler = {
46+
int: "\treturn mp_obj_new_int(ret_val);",
47+
float: "\treturn mp_obj_new_float(ret_val);",
48+
bool: "\treturn mp_obj_new_bool(ret_val);",
49+
str: "\treturn mp_obj_new_str(<ret_val_ptr>, <ret_val_len>);",
50+
None: "\treturn mp_const_none;"
51+
}
52+
53+
shortened_types = {
54+
int: "int",
55+
object: "obj",
56+
None: "null",
57+
bool: "bool",
58+
}
59+
60+
61+
def expand_newlines(lst_in):
62+
new_list = []
63+
for line in lst_in:
64+
new_list.extend(line.replace('\t', ' ').split('\n'))
65+
return new_list
66+
67+
68+
class BaseContainer:
69+
def load_c(self, input: str):
70+
"""
71+
:param input: String of c source
72+
:return: self
73+
"""
74+
return self
75+
76+
def load_python(self, input) -> BaseContainer:
77+
"""
78+
:param input: Python Object
79+
:return: self
80+
"""
81+
return self
82+
83+
def to_c(self):
84+
"""
85+
Parse the container into c source code
86+
:return:
87+
"""
88+
89+
def to_python(self):
90+
"""
91+
Parse the container into python objects
92+
:return:
93+
"""
94+
95+
96+
class ModuleContainer:
97+
def __init__(self):
98+
self.headers = ['#include "py/obj.h"', '#include "py/runtime.h"', '#include "py/builtin.h"']
99+
self.functions = []
100+
101+
102+
class FunctionContainer(BaseContainer):
103+
return_type_handler = {
104+
int: "mp_int_t ret_val;",
105+
float: "mp_float_t ret_val;",
106+
bool: "bool ret_val;",
107+
str: None,
108+
# tuple: string_template(
109+
# "mp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
110+
# list: string_template(
111+
# "mp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
112+
# set: string_template(
113+
# "mp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
114+
None: None
115+
}
116+
return_handler = {
117+
int: "return mp_obj_new_int(ret_val);",
118+
float: "return mp_obj_new_float(ret_val);",
119+
bool: "return mp_obj_new_bool(ret_val);",
120+
str: "return mp_obj_new_str(<ret_val_ptr>, <ret_val_len>);",
121+
None: "return mp_const_none;"
122+
}
123+
124+
def __init__(self):
125+
self.comments = None
126+
self.name = None
127+
self.module = None
128+
self.parameters: ParametersContainer = ParametersContainer()
129+
self.code = None
130+
self.return_type = None
131+
self.return_value = None
132+
self.signature = None
133+
134+
def load_python(self, input) -> FunctionContainer:
135+
"""
136+
:param input: Function to parse
137+
:return:
138+
"""
139+
self.comments = input.__doc__
140+
self.name = input.__name__
141+
self.module = input.__module__
142+
self.signature = inspect.signature(input)
143+
self.parameters.load_python(self.signature.parameters)
144+
self.return_type = inspect.signature(input).return_annotation
145+
return self
146+
147+
def to_c_comments(self):
148+
"""
149+
Uses single line comments as we can't know if there are string escapes such as /* in the code
150+
:param f:
151+
:return:
152+
"""
153+
if self.comments:
154+
return '\n'.join(["//" + line.strip() for line in self.comments.splitlines() if line.strip()])
155+
156+
def to_c_func_def(self):
157+
return f"STATIC mp_obj_t {self.module}_{self.name}"
158+
159+
def to_c_return_val_init(self):
160+
return self.return_type_handler[self.return_type]
161+
162+
def to_c_code_body(self):
163+
if self.code:
164+
return self.code
165+
else:
166+
return "//Your code here"
167+
168+
def to_c_return_value(self):
169+
if self.return_value is not None:
170+
return self.return_value
171+
else:
172+
return self.return_handler.get(self.return_type)
173+
174+
def to_c_define(self):
175+
if self.parameters.type == "positional":
176+
return f"MP_DEFINE_CONST_FUN_OBJ_{self.parameters.count}(" \
177+
f"{self.module}_{self.name}_obj, {self.module}_{self.name});"
178+
elif self.parameters.type == "between":
179+
return f"MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(" \
180+
f"{self.module}_{self.name}_obj, " \
181+
f"{self.parameters.count}, {self.parameters.count}, {self.module}_{self.name});"
182+
elif self.parameters.type == "keyword":
183+
return f"MP_DEFINE_CONST_FUN_OBJ_KW({self.module}_{self.name}_obj, 1, {self.module}_{self.name});"
184+
185+
def to_c_arg_array_def(self):
186+
if self.parameters.type == "keyword":
187+
return f"STATIC const mp_arg_t {self.module}_{self.name}_allowed_args[]"
188+
189+
def to_c(self):
190+
# TODO work on formatter
191+
resp = self.to_c_comments()
192+
resp += "\n"
193+
resp += f"{self.to_c_func_def()}({self.parameters.to_c_input()}) {{\n"
194+
resp += " " + "\n ".join(self.parameters.to_c_init().splitlines()) + "\n"
195+
if self.to_c_return_val_init():
196+
resp += " " + self.to_c_return_val_init()
197+
resp += f"\n\n {self.to_c_code_body()}\n\n"
198+
resp += f" {self.to_c_return_value()}\n"
199+
resp += "}\n"
200+
resp += self.to_c_define()
201+
return resp
202+
203+
204+
class ParametersContainer(BaseContainer):
205+
type_handler = {
206+
int: string_template("mp_int_t {0} = mp_obj_get_int({0}_obj);"),
207+
float: string_template("mp_float_t {0} = mp_obj_get_float({0}_obj);"),
208+
bool: string_template("bool {0} = mp_obj_is_true({0}_obj);"),
209+
str: string_template("const char* {0} = mp_obj_str_get_str({0}_obj);"),
210+
tuple: string_template(
211+
"mp_obj_t *{0} = NULL;\nsize_t {0}_len = 0;\nmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
212+
list: string_template(
213+
"mp_obj_t *{0} = NULL;\nsize_t {0}_len = 0;\nmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
214+
set: string_template(
215+
"mp_obj_t *{0} = NULL;\nsize_t {0}_len = 0;\nmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
216+
object: string_template("\tmp_obj_t {0} args[ARG_{0}].u_obj;")
217+
}
218+
219+
def __init__(self):
220+
self.type = ""
221+
self.count = 0
222+
self.parameters = None
223+
224+
def load_python(self, input: Dict[str, inspect.Parameter]) -> ParametersContainer:
225+
self.parameters = input
226+
self.count = len(self.parameters)
227+
simple = all([param.kind == param.POSITIONAL_OR_KEYWORD for param in self.parameters.values()])
228+
if simple and self.count < 4:
229+
self.type = "positional"
230+
elif simple:
231+
self.type = "between"
232+
else:
233+
self.type = "keyword"
234+
return self
235+
236+
def to_c_enums(self):
237+
if self.type != "keyword":
238+
return None
239+
return f"enum {{ {', '.join(['ARG_' + k for k in self.parameters])} }};"
240+
241+
def to_c_kw_allowed_args(self):
242+
if self.type == "keyword":
243+
args = []
244+
for name, param in self.parameters.items():
245+
if param.kind in [param.POSITIONAL_OR_KEYWORD, param.POSITIONAL_ONLY]:
246+
arg_type = "MP_ARG_REQUIRED"
247+
else:
248+
arg_type = "MP_ARG_KW_ONLY"
249+
type_txt = shortened_types[param.annotation]
250+
if param.default is inspect._empty:
251+
default = ""
252+
elif param.default is None:
253+
default = f"{{ .u_{type_txt} = MP_OBJ_NULL }}"
254+
else:
255+
default = f"{{ .u_{type_txt} = {param.default} }}"
256+
args.append(f"{{ MP_QSTR_{name}, {arg_type} | MP_ARG_{type_txt.upper()}, {default} }},")
257+
return args
258+
# return f"\tSTATIC const mp_arg_t {f.__module__}_{f.__name__}_allowed_args[] = {{\n\t\t{args}\n\t}};"
259+
260+
def to_c_arg_array(self):
261+
if self.type != "keyword":
262+
return None
263+
264+
def to_c_kw_arg_unpack(self):
265+
if self.type != "keyword":
266+
return None
267+
268+
def to_c_input(self):
269+
if self.type == "positional":
270+
return ", ".join([f"mp_obj_t {x}_obj" for x in self.parameters])
271+
elif self.type == "between":
272+
return "size_t n_args, const mp_obj_t *args"
273+
elif self.type == "keyword":
274+
# Complex case
275+
return "size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {"
276+
277+
def to_c_init(self):
278+
if self.type == "keyword":
279+
return "\n".join([self.to_c_enums(),
280+
self.to_c_kw_allowed_args(),
281+
"",
282+
self.to_c_arg_array(),
283+
"",
284+
self.to_c_kw_arg_unpack()])
285+
else:
286+
return "\n".join([self.type_handler[value.annotation](param) for param, value in self.parameters.items()])
287+
288+
289+
class ReturnContainer(BaseContainer):
290+
pass
291+
36292

37293
def stub_function(f):
38294
# Function implementation
@@ -87,29 +343,6 @@ def function_init(func_name):
87343
return f"STATIC mp_obj_t {func_name}("
88344

89345

90-
return_type_handler = {
91-
int: "\tmp_int_t ret_val;",
92-
float: "\tmp_float_t ret_val;",
93-
bool: "\tbool ret_val;",
94-
str: "",
95-
# tuple: string_template(
96-
# "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
97-
# list: string_template(
98-
# "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
99-
# set: string_template(
100-
# "\tmp_obj_t *{0} = NULL;\n\tsize_t {0}_len = 0;\n\tmp_obj_get_array({0}_arg, &{0}_len, &{0});"),
101-
None: ""
102-
}
103-
104-
return_handler = {
105-
int: "\treturn mp_obj_new_int(ret_val);",
106-
float: "\treturn mp_obj_new_float(ret_val);",
107-
bool: "\treturn mp_obj_new_bool(ret_val);",
108-
str: "\treturn mp_obj_new_str(<ret_val_ptr>, <ret_val_len>);",
109-
None: "\treturn mp_const_none;"
110-
}
111-
112-
113346
def ret_val_init(ret_type):
114347
return return_type_handler[ret_type]
115348

@@ -136,14 +369,6 @@ def kw_enum(params):
136369
return f"\tenum {{ {', '.join(['ARG_' + k for k in params])} }};"
137370

138371

139-
shortened_types = {
140-
int: "int",
141-
object: "obj",
142-
None: "null",
143-
bool: "bool",
144-
}
145-
146-
147372
def kw_allowed_args(f, params):
148373
args = []
149374
for name, param in params.items():

0 commit comments

Comments
 (0)