-
Notifications
You must be signed in to change notification settings - Fork 0
/
examples.py
336 lines (249 loc) · 10.2 KB
/
examples.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
from dataclasses import dataclass
from re import match
from typing import *
from reference_parser import CType, CParameter, UnsupportedTypeError, FunctionReference
from helper_types import *
from typing import Dict, List, Tuple, Set
base_str = "({inputs}) {value} ({outputs})"
TypeMapping = List[Tuple[Name, CType]]
ParserMapping = List[Tuple[Name, Parser]]
@dataclass
class ExampleInstance:
"""
Contains the values for one example
"""
inputs: ParameterMapping
value: AnyValue
outputs: ParameterMapping
def form(self, inputs: TypeMapping, value: CType, outputs: TypeMapping) -> str:
"""
Creates a formatted string representation of the example.
:param inputs: the types of all of the input values
:param value: the return type
:param outputs: the types of the output values
:return: the formatted string
"""
inp_vals = [form_value(self.inputs[name], c_type)
for name, c_type in inputs]
outp_vals = [form_value(self.outputs[name], c_type)
for name, c_type in outputs]
return base_str.format(inputs=", ".join(inp_vals),
value=form_value(self.value, value),
outputs=", ".join(outp_vals))
@staticmethod
def parse(inputs: ParserMapping, value: Parser, outputs: ParserMapping, s: str):
"""
Parses an example
Details of the example format can be found in the :code:`parse` function.
:param s: the string to parse
:param inputs: parsers for the input values
:param value: a parser for the return value
:param outputs: parsers for the output values
:return: the example that has been parsed. Returns :code:`None` if this example could not be parsed
"""
def parse_group(s: str, grp: ParserMapping) -> Optional[Tuple[ParameterMapping, str]]:
"""
Helper function to parse something of the form:
(<values>)
where <values> is a comma-separated list of values that can be parsed by the parsers in :code:`grp`.
:param s: the string to parse
:param grp: the parsers to use to parse this group
:return: a standard parse result; the values and the new string position if successful
or :code:`None` if not
"""
s = s[s.index("(") + 1:]
grp_vals = {}
for name, parser in grp:
if (parsed := parser(s)) is None:
return None
val, s = parsed
grp_vals[name] = val
if (m := match(r"\s*,", s)) is not None:
s = s[m.end():]
if (m := match(r"\s*\)", s)) is not None:
s = s[m.end():]
else:
return None
return grp_vals, s
if (parsed := parse_group(s, inputs)) is None:
return None
input_vals, s = parsed
if (parsed := value(s)) is None:
return None
ret_val, s = parsed
if (parsed := parse_group(s, outputs)) is None:
return None
output_vals, s = parsed
return ExampleInstance(input_vals, ret_val, output_vals)
def form(reference: FunctionReference, examples: List[ExampleInstance]) -> List[str]:
'''
if isinstance(reference, FunctionReference):
inputs = [(param.name, param.type) for param in reference.parameters]
value = reference.type
outputs = [(param.name, param.type) for param in reference.outputs()]
else:
types = {param.name: param.contents.primitive.value.with_ptr_level(1 if param.is_array else 0)
for param in reference.parameters}
inputs = [(param.name, types[param.name]) for param in reference.parameters]
value = reference.type
outputs = [(param.name, types[param.name]) for param in reference.parameters if param.is_output]
'''
inputs = [(param.name, param.type) for param in reference.parameters]
value = reference.type
outputs = [(param.name, param.type) for param in reference.outputs()]
return form_examples(inputs, value, outputs, examples)
def form_examples(inputs: TypeMapping, value: CType, outputs: TypeMapping,
examples: List[ExampleInstance]) -> List[str]:
sig_str = [form_ref(inputs, value, outputs)]
example_strs = [ex.form(inputs, value, outputs) for ex in examples]
return sig_str + example_strs
def form_ref(inputs: TypeMapping, value: CType, outputs: TypeMapping) -> str:
inps = [str(CParameter(name, c_type)) for name, c_type in inputs]
outps = [str(CParameter(name, c_type)) for name, c_type in outputs]
return base_str.format(inputs=", ".join(inps),
value=str(value),
outputs=", ".join(outps))
def form_value(val: AnyValue, c_type: CType) -> str:
if c_type == CType("char", 1):
return f'"{val}"'
elif c_type == CType("void", 0):
return "_"
return str(val)
def parse(sig: str, examples: List[str]) -> List[ExampleInstance]:
"""
Uses a signature string and a list of examples to build the collection
The signature string is composed of three sections:
(<inputs>) <return> (<outputs>)
where <inputs> and <outputs> are a comma-separated list of parameters, and <ret> is a type.
The examples are of a similar form, except instead of parameters the fields contain
the values of the corresponding parameter.
For a void return type the special value '_' is used.
Example file:
(int a, float b, char *s) void (char *s)
(1, 1.5, "a string") _ (" a new string")
(-4, 10.001, "a string with \" escaped characters") _ ("less chars")
...
Another example file:
(int *a, int *b, int n) int ()
([1, 2, 3], [4, 5, 6], 3) -1 ()
([10. 15, 20, 25, 30], [1, -2, 3, -4, 5], 5) 10 ()
...
Any examples that can not be parsed correctly are ignored.
:param sig:
:param examples:
:return:
"""
inputs, value, outputs = parse_sig(sig)
return parse_examples(inputs, value, outputs, examples)
def parse_sig(s: str):
"""
Parses a signature string into the corresponding parameters/types
:param s: the string to parse
:return: a tuple containing the parsers for (inputs, return value, outputs)
"""
inp_end = s.index(")") + 1
outp_start = s.rindex("(")
assert inp_end < outp_start
inps = s[:inp_end].strip()
ret = s[inp_end:outp_start].strip()
outps = s[outp_start:].strip()
assert inps[0] == outps[0] == "("
assert inps[-1] == outps[-1] == ")"
input_params = [CParameter.parse(param.strip()) for param in inps[1:-1].split(',') if param.strip()]
ret_type = CType.parse(ret)
output_params = [CParameter.parse(param.strip()) for param in outps[1:-1].split(',') if param.strip()]
inputs = [(param.name, parser_for(param.type)) for param in input_params]
value = parser_for(ret_type)
outputs = [(param.name, parser_for(param.type)) for param in output_params]
return inputs, value, outputs
def parse_examples(inputs: ParserMapping, value: Parser, outputs: ParserMapping,
examples: List[str]) -> List[ExampleInstance]:
"""
Parses a collection of examples using the given parsers.
:param inputs:
:param value:
:param outputs:
:param examples:
:return: the ExampleInstance built from this selection
"""
vals = []
for example in examples:
val = ExampleInstance.parse(inputs, value, outputs, example)
if val is not None:
vals.append(val)
return vals
# Parsers for supported types
def parse_int(s: str) -> (int, str):
if (m := match(r"\s*(-?\d+)", s)) is not None:
return int(m[1]), s[m.end():]
else:
return None
def parse_real(s: str) -> (float, str):
if (m := match(r"\s*(-?\d+(?:\.\d+)?)", s)) is not None:
return float(m[1]), s[m.end():]
else:
raise None
def parse_char(s: str) -> (str, str):
if (m := match(r"\s*'([^\\']|\\.)'", s)) is not None:
return m[1], s[m.end():]
else:
return None
def parse_bool(s: str) -> (bool, str):
if (m := match(r"\s*(True|False)", s)) is not None:
return m[1] == "True", s[m.end():]
else:
return None
def parse_string(s: str) -> (str, str):
if (m := match(r'\s*"((?:[^\\"]|\\.)*)"', s)) is not None:
return m[1], s[m.end():]
else:
return None
def parse_list(s: str, elem: Parser) -> (list, str):
if (m := match(r"\s*\[", s)) is None:
return None
res = []
rem = s[m.end():]
while (inner_m := elem(rem)) is not None:
v, rem = inner_m
res.append(v)
if (sep := match(r"\s*,", rem)) is None:
break
rem = rem[sep.end():]
if (m := match(r"\s*]", rem)) is not None:
return res, rem[m.end():]
else:
return None
def parse_missing(s: str) -> (None, str):
"""
A special parser meant to parse the void value '_'
:param s: the string to parse
:return: a tuple, shifting the input string correctly, if parsing occurred otherwise :code:`None`
"""
if (m := match(r"\s*_", s)) is not None:
return None, s[m.end():]
else:
return None
def parser_for(c_type: CType) -> Parser:
"""
Fetch the correct parser for a given type
Recursively wraps pointers in lists if necessary.
:param c_type: the type to parse
:return: a function taking a string as input and returning a the parse of the string for the given type
"""
if c_type.contents == "void":
return parse_missing
if c_type == CType("char", 1):
return parse_string
if c_type.pointer_level >= 1:
inner_parser = parser_for(CType(c_type.contents, c_type.pointer_level - 1))
return lambda s: parse_list(s, inner_parser)
if c_type.contents == "int":
return parse_int
elif c_type.contents == "float" or c_type.contents == "double":
return parse_real
elif c_type.contents == "char":
return parse_char
elif c_type.contents == "bool":
return parse_bool
else:
raise UnsupportedTypeError(c_type)