-
Notifications
You must be signed in to change notification settings - Fork 192
Expand file tree
/
Copy pathscad_parser.py
More file actions
339 lines (254 loc) · 7.64 KB
/
Copy pathscad_parser.py
File metadata and controls
339 lines (254 loc) · 7.64 KB
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
from ply import lex, yacc
# workaround relative imports.... make this module runable as script
if __name__ == "__main__":
from scad_ast import (
ScadGlobalVar,
ScadFunction,
ScadModule,
ScadParameter,
ScadTypes,
)
# Note that the lexer depends on importing all elements in scad_tokens
from scad_tokens import * # noqa: F403
else:
from .scad_ast import (
ScadGlobalVar,
ScadFunction,
ScadModule,
ScadParameter,
ScadTypes,
)
# Note that the lexer depends on importing all elements in scad_tokens
from .scad_tokens import * # noqa: F403
precedence = (
("nonassoc", "ASSERT"),
("nonassoc", "ECHO"),
("nonassoc", "THEN"),
("nonassoc", "ELSE"),
("nonassoc", "?"),
("nonassoc", ":"),
("nonassoc", "(", ")", "{", "}"),
("nonassoc", "="),
("left", "AND", "OR"),
("left", "EQUAL", "NOT_EQUAL", "GREATER_OR_EQUAL", "LESS_OR_EQUAL", ">", "<"),
("left", "+", "-"),
("left", "%"),
("left", "*", "/"),
("right", "^"),
("right", "NEG", "POS", "BACKGROUND", "NOT"),
("left", "ACCESS"),
)
def p_statements(p):
"""statements : statements statement"""
p[0] = p[1]
if p[2] is not None:
p[0].append(p[2])
def p_statements_empty(p):
"""statements : empty"""
p[0] = []
def p_empty(p):
"empty :"
def p_statement(p):
"""statement : IF "(" expression ")" statement %prec THEN
| IF "(" expression ")" statement ELSE statement
| for_loop statement
| LET "(" assignment_list ")" statement %prec THEN
| ASSERT "(" opt_call_parameter_list ")" statement
| ECHO "(" opt_call_parameter_list ")" statement
| "{" statements "}"
| "%" statement %prec BACKGROUND
| "*" statement %prec BACKGROUND
| "!" statement %prec BACKGROUND
| "#" statement %prec BACKGROUND
| call statement
| USE FILENAME
| INCLUDE FILENAME
| ";"
"""
def p_for_loop(p):
'''for_loop : FOR "(" parameter_list ")"
| FOR "(" parameter_list ";" expression ";" parameter_list ")"'''
def p_statement_function(p):
"statement : function"
p[0] = p[1]
def p_statement_module(p):
"statement : module"
p[0] = p[1]
def p_statement_assignment(p):
'statement : ID "=" expression ";"'
p[0] = ScadGlobalVar(p[1])
def p_logic_expr(p):
"""logic_expr : "-" expression %prec NEG
| "+" expression %prec POS
| "!" expression %prec NOT
| expression "?" expression ":" expression
| expression "%" expression
| expression "+" expression
| expression "-" expression
| expression "/" expression
| expression "*" expression
| expression "^" expression
| expression "<" expression
| expression ">" expression
| expression EQUAL expression
| expression NOT_EQUAL expression
| expression GREATER_OR_EQUAL expression
| expression LESS_OR_EQUAL expression
| expression AND expression
| expression OR expression
"""
def p_access_expr(p):
"""access_expr : ID %prec ACCESS
| expression "." ID %prec ACCESS
| expression "(" call_parameter_list ")" %prec ACCESS
| expression "(" ")" %prec ACCESS
| expression "[" expression "]" %prec ACCESS
"""
def p_list_stuff(p):
"""list_stuff : FUNCTION "(" opt_parameter_list ")" expression
| LET "(" assignment_list ")" expression %prec THEN
| EACH expression %prec THEN
| "[" expression ":" expression "]"
| "[" expression ":" expression ":" expression "]"
| "[" for_loop expression "]"
| tuple
"""
def p_assert_or_echo(p):
"""assert_or_echo : ASSERT "(" opt_call_parameter_list ")"
| ECHO "(" opt_call_parameter_list ")"
"""
def p_constants(p):
"""constants : STRING
| TRUE
| FALSE
| NUMBER"""
def p_opt_else(p):
"""opt_else :
| ELSE expression %prec THEN
"""
# this causes some shift/reduce conflicts, but I don't know how to solve it
def p_for_or_if(p):
"""for_or_if : for_loop expression %prec THEN
| IF "(" expression ")" expression opt_else
"""
def p_expression(p):
"""expression : access_expr
| logic_expr
| list_stuff
| assert_or_echo
| assert_or_echo expression %prec ASSERT
| constants
| for_or_if
| "(" expression ")"
"""
# the assert_or_echo stuff causes some shift/reduce conflicts, but I don't know how to solve it
def p_assignment_list(p):
"""assignment_list : ID "=" expression
| assignment_list "," ID "=" expression
"""
def p_call(p):
'''call : ID "(" call_parameter_list ")"
| ID "(" ")"'''
def p_tuple(p):
"""tuple : "[" opt_expression_list "]" """
def p_commas(p):
"""commas : commas ","
| ","
"""
def p_opt_expression_list(p):
"""opt_expression_list : expression_list
| expression_list commas
| empty"""
def p_expression_list(p):
"""expression_list : expression_list commas expression
| expression
"""
def p_opt_call_parameter_list(p):
"""opt_call_parameter_list :
| call_parameter_list
"""
def p_call_parameter_list(p):
"""call_parameter_list : call_parameter_list commas call_parameter
| call_parameter"""
def p_call_parameter(p):
"""call_parameter : expression
| ID "=" expression"""
def p_opt_parameter_list(p):
"""opt_parameter_list : parameter_list
| parameter_list commas
| empty
"""
if p[1] is not None:
p[0] = p[1]
else:
p[0] = []
def p_parameter_list(p):
"""parameter_list : parameter_list commas parameter
| parameter"""
if len(p) > 2:
p[0] = p[1] + [p[3]]
else:
p[0] = [p[1]]
def p_parameter(p):
"""parameter : ID
| ID "=" expression"""
p[0] = ScadParameter(p[1], len(p) == 4)
def p_function(p):
"""function : FUNCTION ID "(" opt_parameter_list ")" "=" expression"""
params = None
if p[4] != ")":
params = p[4]
p[0] = ScadFunction(p[2], params)
def p_module(p):
"""module : MODULE ID "(" opt_parameter_list ")" statement"""
params = None
if p[4] != ")":
params = p[4]
p[0] = ScadModule(p[2], params)
def p_error(p):
print(
f"py_scadparser: Syntax error: {p.lexer.filename}({p.lineno}) {p.type} - {p.value}"
)
def parseFile(scadFile):
lexer = lex.lex(debug=False)
lexer.filename = scadFile
parser = yacc.yacc(debug=False)
modules = []
functions = []
globalVars = []
appendObject = {
ScadTypes.MODULE: lambda x: modules.append(x),
ScadTypes.FUNCTION: lambda x: functions.append(x),
ScadTypes.GLOBAL_VAR: lambda x: globalVars.append(x),
}
from pathlib import Path
with Path(scadFile).open() as f:
for i in parser.parse(f.read(), lexer=lexer):
appendObject[i.getType()](i)
return modules, functions, globalVars
def parseFileAndPrintGlobals(scadFile):
print(f"======{scadFile}======")
modules, functions, globalVars = parseFile(scadFile)
print("Modules:")
for m in modules:
print(f" {m}")
print("Functions:")
for m in functions:
print(f" {m}")
print("Global Variables:")
for m in globalVars:
print(f" {m.name}")
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print(
f"usage: {sys.argv[0]} [-q] <scad-file> [<scad-file> ...]\n -q : quiete"
)
quiete = sys.argv[1] == "-q"
files = sys.argv[2:] if quiete else sys.argv[1:]
for i in files:
if quiete:
print(i)
parseFile(i)
else:
parseFileAndPrintGlobals(i)