forked from micropython/micropython-esp32-ulp
- Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpreprocess.py
353 lines (245 loc) · 8.87 KB
/
preprocess.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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
importos
fromesp32_ulp.preprocessimportPreprocessor
fromesp32_ulp.definesdbimportDefinesDB, DBNAME
fromesp32_ulp.utilimportfile_exists
tests= []
deftest(param):
tests.append(param)
defresolve_relative_path(filename):
"""
Returns the full path to the filename provided, taken relative to the current file
e.g.
if this file was file.py at /path/to/file.py
and the provided relative filename was tests/unit.py
then the resulting path would be /path/to/tests/unit.py
"""
r=__file__.rsplit("/", 1) # poor man's os.path.dirname(__file__)
head=r[0]
iflen(r) ==1ornothead:
returnfilename
return"%s/%s"% (head, filename)
@test
deftest_replace_defines_should_return_empty_line_given_empty_string():
p=Preprocessor()
assertp.preprocess("") ==""
@test
defreplace_defines_should_return_remove_comments():
p=Preprocessor()
line="// some comment"
expected=""
assertp.preprocess(line) ==expected
@test
deftest_parse_defines():
p=Preprocessor()
assertp.parse_define_line("") == {}
assertp.parse_define_line("// comment") == {}
assertp.parse_define_line(" // comment") == {}
assertp.parse_define_line(" /* comment */") == {}
assertp.parse_define_line(" /* comment */ #define A 42") == {} # #define must be the first thing on a line
assertp.parse_define_line("#define a 1") == {"a": "1"}
assertp.parse_define_line(" #define a 1") == {"a": "1"}
assertp.parse_define_line("#define a 1 2") == {"a": "1 2"}
assertp.parse_define_line("#define f(a,b) 1") == {} # macros not supported
assertp.parse_define_line("#define f(a, b) 1") == {} # macros not supported
assertp.parse_define_line("#define f (a,b) 1") == {"f": "(a,b) 1"} # f is not a macro
assertp.parse_define_line("#define f (a, b) 1") == {"f": "(a, b) 1"} # f is not a macro
assertp.parse_define_line("#define RTC_ADDR 0x12345 // start of range") == {"RTC_ADDR": "0x12345"}
@test
deftest_parse_defines_handles_multiple_input_lines():
p=Preprocessor()
multi_line_1="""\
#define ID_WITH_UNDERSCORE something
#define ID2 somethingelse
"""
assertp.parse_defines(multi_line_1) == {"ID_WITH_UNDERSCORE": "something", "ID2": "somethingelse"}
@test
deftest_parse_defines_does_not_understand_comments_by_current_design():
# comments are not understood. lines are expected to already have comments removed!
p=Preprocessor()
multi_line_2="""\
#define ID_WITH_UNDERSCORE something
/*
#define ID2 somethingelse
*/
"""
assert"ID2"inp.parse_defines(multi_line_2)
@test
deftest_parse_defines_does_not_understand_line_continuations_with_backslash_by_current_design():
p=Preprocessor()
multi_line_3=r"""
#define ID_WITH_UNDERSCORE something \
line2
"""
assertp.parse_defines(multi_line_3) == {"ID_WITH_UNDERSCORE": "something \\"}
@test
defpreprocess_should_remove_comments_and_defines_but_keep_the_lines_as_empty_lines():
p=Preprocessor()
lines="""\
// copyright
#define A 1
move r1, r2"""
assertp.preprocess(lines) =="\n\n\n\tmove r1, r2"
@test
defpreprocess_should_replace_words_defined():
p=Preprocessor()
lines="""\
#define DR_REG_RTCIO_BASE 0x3ff48400
move r1, DR_REG_RTCIO_BASE"""
assert"move r1, 0x3ff48400"inp.preprocess(lines)
@test
defpreprocess_should_replace_words_defined_multiple_times():
p=Preprocessor()
lines="""\
#define DR_REG_RTCIO_BASE 0x3ff48400
move r1, DR_REG_RTCIO_BASE #once
move r2, DR_REG_RTCIO_BASE #second time"""
assert"move r1, 0x3ff48400"inp.preprocess(lines)
assert"move r2, 0x3ff48400"inp.preprocess(lines)
@test
defpreprocess_should_replace_all_defined_words():
p=Preprocessor()
lines="""\
#define DR_REG_RTCIO_BASE 0x3ff48400
#define SOME_OFFSET 4
move r1, DR_REG_RTCIO_BASE
add r2, r1, SOME_OFFSET"""
assert"move r1, 0x3ff48400"inp.preprocess(lines)
assert"add r2, r1, 4"inp.preprocess(lines)
@test
defpreprocess_should_not_replace_substrings_within_identifiers():
p=Preprocessor()
# ie. if AAA is defined don't touch PREFIX_AAA_SUFFIX
lines="""\
#define RTCIO 4
move r1, DR_REG_RTCIO_BASE"""
assert"DR_REG_4_BASE"notinp.preprocess(lines)
# ie. if A and AA are defined, don't replace AA as two A's but with AA
lines="""\
#define A 4
#define AA 8
move r1, A
move r2, AA"""
assert"move r1, 4"inp.preprocess(lines)
assert"move r2, 8"inp.preprocess(lines)
@test
defpreprocess_should_replace_defines_used_in_defines():
p=Preprocessor()
lines="""\
#define BITS (BASE << 4)
#define BASE 0x1234
move r1, BITS
move r2, BASE"""
assert"move r1, (0x1234 << 4)"inp.preprocess(lines)
@test
deftest_expand_rtc_macros():
p=Preprocessor()
assertp.expand_rtc_macros("") ==""
assertp.expand_rtc_macros("abc") =="abc"
assertp.expand_rtc_macros("WRITE_RTC_REG(1, 2, 3, 4)") =="\treg_wr 1, 2 + 3 - 1, 2, 4"
assertp.expand_rtc_macros("READ_RTC_REG(1, 2, 3)") =="\treg_rd 1, 2 + 3 - 1, 2"
assertp.expand_rtc_macros("WRITE_RTC_FIELD(1, 2, 3)") =="\treg_wr 1, 2 + 1 - 1, 2, 3 & 1"
assertp.expand_rtc_macros("READ_RTC_FIELD(1, 2)") =="\treg_rd 1, 2 + 1 - 1, 2"
@test
defpreprocess_should_replace_BIT_with_empty_string_unless_defined():
# by default replace BIT with empty string (see description for why in the code)
src=" move r1, 0x123 << BIT(24)"
assert"move r1, 0x123 << (24)"inPreprocessor().preprocess(src)
# but if BIT is defined, use that
src="""\
#define BIT 12
move r1, BIT"""
assert"move r1, 12"inPreprocessor().preprocess(src)
@test
deftest_process_include_file():
p=Preprocessor()
defines=p.process_include_file(resolve_relative_path('fixtures/incl.h'))
assertdefines['CONST1'] =='42'
assertdefines['CONST2'] =='99'
assertdefines.get('MULTI_LINE', None) =='abc \\'# correct. line continuations not supported
assert'MACRO'notindefines
@test
deftest_process_include_file_with_multiple_files():
p=Preprocessor()
defines=p.process_include_file(resolve_relative_path('fixtures/incl.h'))
defines=p.process_include_file(resolve_relative_path('fixtures/incl2.h'))
assertdefines['CONST1'] =='42', "constant from incl.h"
assertdefines['CONST2'] =='123', "constant overridden by incl2.h"
assertdefines['CONST3'] =='777', "constant from incl2.h"
@test
deftest_process_include_file_using_database():
db=DefinesDB()
db.clear()
p=Preprocessor()
p.use_db(db)
p.process_include_file(resolve_relative_path('fixtures/incl.h'))
p.process_include_file(resolve_relative_path('fixtures/incl2.h'))
assertdb['CONST1'] =='42', "constant from incl.h"
assertdb['CONST2'] =='123', "constant overridden by incl2.h"
assertdb['CONST3'] =='777', "constant from incl2.h"
db.close()
@test
deftest_process_include_file_should_not_load_database_keys_into_instance_defines_dictionary():
db=DefinesDB()
db.clear()
p=Preprocessor()
p.use_db(db)
p.process_include_file(resolve_relative_path('fixtures/incl.h'))
# a bit hackish to reference instance-internal state
# but it's important to verify this, as we otherwise run out of memory on device
assert'CONST2'notinp._defines
@test
deftest_preprocess_should_use_definesdb_when_provided():
p=Preprocessor()
content="""\
#define LOCALCONST 42
entry:
move r1, LOCALCONST
move r2, DBKEY
"""
# first try without db
result=p.preprocess(content)
assert"move r1, 42"inresult
assert"move r2, DBKEY"inresult
assert"move r2, 99"notinresult
# now try with db
db=DefinesDB()
db.clear()
db.update({'DBKEY': '99'})
p.use_db(db)
result=p.preprocess(content)
assert"move r1, 42"inresult
assert"move r2, 99"inresult
assert"move r2, DBKEY"notinresult
@test
deftest_preprocess_should_ensure_no_definesdb_is_created_when_only_reading_from_it():
content="""\
#define CONST 42
move r1, CONST"""
# remove any existing db
db=DefinesDB()
db.clear()
assertnotfile_exists(DBNAME)
# now preprocess using db
p=Preprocessor()
p.use_db(db)
result=p.preprocess(content)
assert"move r1, 42"inresult
assertnotfile_exists(DBNAME)
@test
deftest_preprocess_should_ensure_the_definesdb_is_properly_closed_after_use():
content="""\
#define CONST 42
move r1, CONST"""
# remove any existing db
db=DefinesDB()
db.open()
assertdb.is_open()
# now preprocess using db
p=Preprocessor()
p.use_db(db)
p.preprocess(content)
assertnotdb.is_open()
if__name__=='__main__':
# run all methods marked with @test
fortintests:
t()