7267
|
1 # An implementation of Dartmouth BASIC (1964)
|
|
2 #
|
|
3
|
|
4 from ply import *
|
|
5 import basiclex
|
|
6
|
|
7 tokens = basiclex.tokens
|
|
8
|
|
9 precedence = (
|
|
10 ('left', 'PLUS','MINUS'),
|
|
11 ('left', 'TIMES','DIVIDE'),
|
|
12 ('left', 'POWER'),
|
|
13 ('right','UMINUS')
|
|
14 )
|
|
15
|
|
16 #### A BASIC program is a series of statements. We represent the program as a
|
|
17 #### dictionary of tuples indexed by line number.
|
|
18
|
|
19 def p_program(p):
|
|
20 '''program : program statement
|
|
21 | statement'''
|
|
22
|
|
23 if len(p) == 2 and p[1]:
|
|
24 p[0] = { }
|
|
25 line,stat = p[1]
|
|
26 p[0][line] = stat
|
|
27 elif len(p) ==3:
|
|
28 p[0] = p[1]
|
|
29 if not p[0]: p[0] = { }
|
|
30 if p[2]:
|
|
31 line,stat = p[2]
|
|
32 p[0][line] = stat
|
|
33
|
|
34 #### This catch-all rule is used for any catastrophic errors. In this case,
|
|
35 #### we simply return nothing
|
|
36
|
|
37 def p_program_error(p):
|
|
38 '''program : error'''
|
|
39 p[0] = None
|
|
40 p.parser.error = 1
|
|
41
|
|
42 #### Format of all BASIC statements.
|
|
43
|
|
44 def p_statement(p):
|
|
45 '''statement : INTEGER command NEWLINE'''
|
|
46 if isinstance(p[2],str):
|
|
47 print("%s %s %s" % (p[2],"AT LINE", p[1]))
|
|
48 p[0] = None
|
|
49 p.parser.error = 1
|
|
50 else:
|
|
51 lineno = int(p[1])
|
|
52 p[0] = (lineno,p[2])
|
|
53
|
|
54 #### Interactive statements.
|
|
55
|
|
56 def p_statement_interactive(p):
|
|
57 '''statement : RUN NEWLINE
|
|
58 | LIST NEWLINE
|
|
59 | NEW NEWLINE'''
|
|
60 p[0] = (0, (p[1],0))
|
|
61
|
|
62 #### Blank line number
|
|
63 def p_statement_blank(p):
|
|
64 '''statement : INTEGER NEWLINE'''
|
|
65 p[0] = (0,('BLANK',int(p[1])))
|
|
66
|
|
67 #### Error handling for malformed statements
|
|
68
|
|
69 def p_statement_bad(p):
|
|
70 '''statement : INTEGER error NEWLINE'''
|
|
71 print("MALFORMED STATEMENT AT LINE %s" % p[1])
|
|
72 p[0] = None
|
|
73 p.parser.error = 1
|
|
74
|
|
75 #### Blank line
|
|
76
|
|
77 def p_statement_newline(p):
|
|
78 '''statement : NEWLINE'''
|
|
79 p[0] = None
|
|
80
|
|
81 #### LET statement
|
|
82
|
|
83 def p_command_let(p):
|
|
84 '''command : LET variable EQUALS expr'''
|
|
85 p[0] = ('LET',p[2],p[4])
|
|
86
|
|
87 def p_command_let_bad(p):
|
|
88 '''command : LET variable EQUALS error'''
|
|
89 p[0] = "BAD EXPRESSION IN LET"
|
|
90
|
|
91 #### READ statement
|
|
92
|
|
93 def p_command_read(p):
|
|
94 '''command : READ varlist'''
|
|
95 p[0] = ('READ',p[2])
|
|
96
|
|
97 def p_command_read_bad(p):
|
|
98 '''command : READ error'''
|
|
99 p[0] = "MALFORMED VARIABLE LIST IN READ"
|
|
100
|
|
101 #### DATA statement
|
|
102
|
|
103 def p_command_data(p):
|
|
104 '''command : DATA numlist'''
|
|
105 p[0] = ('DATA',p[2])
|
|
106
|
|
107 def p_command_data_bad(p):
|
|
108 '''command : DATA error'''
|
|
109 p[0] = "MALFORMED NUMBER LIST IN DATA"
|
|
110
|
|
111 #### PRINT statement
|
|
112
|
|
113 def p_command_print(p):
|
|
114 '''command : PRINT plist optend'''
|
|
115 p[0] = ('PRINT',p[2],p[3])
|
|
116
|
|
117 def p_command_print_bad(p):
|
|
118 '''command : PRINT error'''
|
|
119 p[0] = "MALFORMED PRINT STATEMENT"
|
|
120
|
|
121 #### Optional ending on PRINT. Either a comma (,) or semicolon (;)
|
|
122
|
|
123 def p_optend(p):
|
|
124 '''optend : COMMA
|
|
125 | SEMI
|
|
126 |'''
|
|
127 if len(p) == 2:
|
|
128 p[0] = p[1]
|
|
129 else:
|
|
130 p[0] = None
|
|
131
|
|
132 #### PRINT statement with no arguments
|
|
133
|
|
134 def p_command_print_empty(p):
|
|
135 '''command : PRINT'''
|
|
136 p[0] = ('PRINT',[],None)
|
|
137
|
|
138 #### GOTO statement
|
|
139
|
|
140 def p_command_goto(p):
|
|
141 '''command : GOTO INTEGER'''
|
|
142 p[0] = ('GOTO',int(p[2]))
|
|
143
|
|
144 def p_command_goto_bad(p):
|
|
145 '''command : GOTO error'''
|
|
146 p[0] = "INVALID LINE NUMBER IN GOTO"
|
|
147
|
|
148 #### IF-THEN statement
|
|
149
|
|
150 def p_command_if(p):
|
|
151 '''command : IF relexpr THEN INTEGER'''
|
|
152 p[0] = ('IF',p[2],int(p[4]))
|
|
153
|
|
154 def p_command_if_bad(p):
|
|
155 '''command : IF error THEN INTEGER'''
|
|
156 p[0] = "BAD RELATIONAL EXPRESSION"
|
|
157
|
|
158 def p_command_if_bad2(p):
|
|
159 '''command : IF relexpr THEN error'''
|
|
160 p[0] = "INVALID LINE NUMBER IN THEN"
|
|
161
|
|
162 #### FOR statement
|
|
163
|
|
164 def p_command_for(p):
|
|
165 '''command : FOR ID EQUALS expr TO expr optstep'''
|
|
166 p[0] = ('FOR',p[2],p[4],p[6],p[7])
|
|
167
|
|
168 def p_command_for_bad_initial(p):
|
|
169 '''command : FOR ID EQUALS error TO expr optstep'''
|
|
170 p[0] = "BAD INITIAL VALUE IN FOR STATEMENT"
|
|
171
|
|
172 def p_command_for_bad_final(p):
|
|
173 '''command : FOR ID EQUALS expr TO error optstep'''
|
|
174 p[0] = "BAD FINAL VALUE IN FOR STATEMENT"
|
|
175
|
|
176 def p_command_for_bad_step(p):
|
|
177 '''command : FOR ID EQUALS expr TO expr STEP error'''
|
|
178 p[0] = "MALFORMED STEP IN FOR STATEMENT"
|
|
179
|
|
180 #### Optional STEP qualifier on FOR statement
|
|
181
|
|
182 def p_optstep(p):
|
|
183 '''optstep : STEP expr
|
|
184 | empty'''
|
|
185 if len(p) == 3:
|
|
186 p[0] = p[2]
|
|
187 else:
|
|
188 p[0] = None
|
|
189
|
|
190 #### NEXT statement
|
|
191
|
|
192 def p_command_next(p):
|
|
193 '''command : NEXT ID'''
|
|
194
|
|
195 p[0] = ('NEXT',p[2])
|
|
196
|
|
197 def p_command_next_bad(p):
|
|
198 '''command : NEXT error'''
|
|
199 p[0] = "MALFORMED NEXT"
|
|
200
|
|
201 #### END statement
|
|
202
|
|
203 def p_command_end(p):
|
|
204 '''command : END'''
|
|
205 p[0] = ('END',)
|
|
206
|
|
207 #### REM statement
|
|
208
|
|
209 def p_command_rem(p):
|
|
210 '''command : REM'''
|
|
211 p[0] = ('REM',p[1])
|
|
212
|
|
213 #### STOP statement
|
|
214
|
|
215 def p_command_stop(p):
|
|
216 '''command : STOP'''
|
|
217 p[0] = ('STOP',)
|
|
218
|
|
219 #### DEF statement
|
|
220
|
|
221 def p_command_def(p):
|
|
222 '''command : DEF ID LPAREN ID RPAREN EQUALS expr'''
|
|
223 p[0] = ('FUNC',p[2],p[4],p[7])
|
|
224
|
|
225 def p_command_def_bad_rhs(p):
|
|
226 '''command : DEF ID LPAREN ID RPAREN EQUALS error'''
|
|
227 p[0] = "BAD EXPRESSION IN DEF STATEMENT"
|
|
228
|
|
229 def p_command_def_bad_arg(p):
|
|
230 '''command : DEF ID LPAREN error RPAREN EQUALS expr'''
|
|
231 p[0] = "BAD ARGUMENT IN DEF STATEMENT"
|
|
232
|
|
233 #### GOSUB statement
|
|
234
|
|
235 def p_command_gosub(p):
|
|
236 '''command : GOSUB INTEGER'''
|
|
237 p[0] = ('GOSUB',int(p[2]))
|
|
238
|
|
239 def p_command_gosub_bad(p):
|
|
240 '''command : GOSUB error'''
|
|
241 p[0] = "INVALID LINE NUMBER IN GOSUB"
|
|
242
|
|
243 #### RETURN statement
|
|
244
|
|
245 def p_command_return(p):
|
|
246 '''command : RETURN'''
|
|
247 p[0] = ('RETURN',)
|
|
248
|
|
249 #### DIM statement
|
|
250
|
|
251 def p_command_dim(p):
|
|
252 '''command : DIM dimlist'''
|
|
253 p[0] = ('DIM',p[2])
|
|
254
|
|
255 def p_command_dim_bad(p):
|
|
256 '''command : DIM error'''
|
|
257 p[0] = "MALFORMED VARIABLE LIST IN DIM"
|
|
258
|
|
259 #### List of variables supplied to DIM statement
|
|
260
|
|
261 def p_dimlist(p):
|
|
262 '''dimlist : dimlist COMMA dimitem
|
|
263 | dimitem'''
|
|
264 if len(p) == 4:
|
|
265 p[0] = p[1]
|
|
266 p[0].append(p[3])
|
|
267 else:
|
|
268 p[0] = [p[1]]
|
|
269
|
|
270 #### DIM items
|
|
271
|
|
272 def p_dimitem_single(p):
|
|
273 '''dimitem : ID LPAREN INTEGER RPAREN'''
|
|
274 p[0] = (p[1],eval(p[3]),0)
|
|
275
|
|
276 def p_dimitem_double(p):
|
|
277 '''dimitem : ID LPAREN INTEGER COMMA INTEGER RPAREN'''
|
|
278 p[0] = (p[1],eval(p[3]),eval(p[5]))
|
|
279
|
|
280 #### Arithmetic expressions
|
|
281
|
|
282 def p_expr_binary(p):
|
|
283 '''expr : expr PLUS expr
|
|
284 | expr MINUS expr
|
|
285 | expr TIMES expr
|
|
286 | expr DIVIDE expr
|
|
287 | expr POWER expr'''
|
|
288
|
|
289 p[0] = ('BINOP',p[2],p[1],p[3])
|
|
290
|
|
291 def p_expr_number(p):
|
|
292 '''expr : INTEGER
|
|
293 | FLOAT'''
|
|
294 p[0] = ('NUM',eval(p[1]))
|
|
295
|
|
296 def p_expr_variable(p):
|
|
297 '''expr : variable'''
|
|
298 p[0] = ('VAR',p[1])
|
|
299
|
|
300 def p_expr_group(p):
|
|
301 '''expr : LPAREN expr RPAREN'''
|
|
302 p[0] = ('GROUP',p[2])
|
|
303
|
|
304 def p_expr_unary(p):
|
|
305 '''expr : MINUS expr %prec UMINUS'''
|
|
306 p[0] = ('UNARY','-',p[2])
|
|
307
|
|
308 #### Relational expressions
|
|
309
|
|
310 def p_relexpr(p):
|
|
311 '''relexpr : expr LT expr
|
|
312 | expr LE expr
|
|
313 | expr GT expr
|
|
314 | expr GE expr
|
|
315 | expr EQUALS expr
|
|
316 | expr NE expr'''
|
|
317 p[0] = ('RELOP',p[2],p[1],p[3])
|
|
318
|
|
319 #### Variables
|
|
320
|
|
321 def p_variable(p):
|
|
322 '''variable : ID
|
|
323 | ID LPAREN expr RPAREN
|
|
324 | ID LPAREN expr COMMA expr RPAREN'''
|
|
325 if len(p) == 2:
|
|
326 p[0] = (p[1],None,None)
|
|
327 elif len(p) == 5:
|
|
328 p[0] = (p[1],p[3],None)
|
|
329 else:
|
|
330 p[0] = (p[1],p[3],p[5])
|
|
331
|
|
332 #### Builds a list of variable targets as a Python list
|
|
333
|
|
334 def p_varlist(p):
|
|
335 '''varlist : varlist COMMA variable
|
|
336 | variable'''
|
|
337 if len(p) > 2:
|
|
338 p[0] = p[1]
|
|
339 p[0].append(p[3])
|
|
340 else:
|
|
341 p[0] = [p[1]]
|
|
342
|
|
343
|
|
344 #### Builds a list of numbers as a Python list
|
|
345
|
|
346 def p_numlist(p):
|
|
347 '''numlist : numlist COMMA number
|
|
348 | number'''
|
|
349
|
|
350 if len(p) > 2:
|
|
351 p[0] = p[1]
|
|
352 p[0].append(p[3])
|
|
353 else:
|
|
354 p[0] = [p[1]]
|
|
355
|
|
356 #### A number. May be an integer or a float
|
|
357
|
|
358 def p_number(p):
|
|
359 '''number : INTEGER
|
|
360 | FLOAT'''
|
|
361 p[0] = eval(p[1])
|
|
362
|
|
363 #### A signed number.
|
|
364
|
|
365 def p_number_signed(p):
|
|
366 '''number : MINUS INTEGER
|
|
367 | MINUS FLOAT'''
|
|
368 p[0] = eval("-"+p[2])
|
|
369
|
|
370 #### List of targets for a print statement
|
|
371 #### Returns a list of tuples (label,expr)
|
|
372
|
|
373 def p_plist(p):
|
|
374 '''plist : plist COMMA pitem
|
|
375 | pitem'''
|
|
376 if len(p) > 3:
|
|
377 p[0] = p[1]
|
|
378 p[0].append(p[3])
|
|
379 else:
|
|
380 p[0] = [p[1]]
|
|
381
|
|
382 def p_item_string(p):
|
|
383 '''pitem : STRING'''
|
|
384 p[0] = (p[1][1:-1],None)
|
|
385
|
|
386 def p_item_string_expr(p):
|
|
387 '''pitem : STRING expr'''
|
|
388 p[0] = (p[1][1:-1],p[2])
|
|
389
|
|
390 def p_item_expr(p):
|
|
391 '''pitem : expr'''
|
|
392 p[0] = ("",p[1])
|
|
393
|
|
394 #### Empty
|
|
395
|
|
396 def p_empty(p):
|
|
397 '''empty : '''
|
|
398
|
|
399 #### Catastrophic error handler
|
|
400 def p_error(p):
|
|
401 if not p:
|
|
402 print("SYNTAX ERROR AT EOF")
|
|
403
|
|
404 bparser = yacc.yacc()
|
|
405
|
|
406 def parse(data,debug=0):
|
|
407 bparser.error = 0
|
|
408 p = bparser.parse(data,debug=debug)
|
|
409 if bparser.error: return None
|
|
410 return p
|
|
411
|
|
412
|
|
413
|
|
414
|
|
415
|
|
416
|
|
417
|
|
418
|
|
419
|
|
420
|
|
421
|
|
422
|
|
423
|
|
424
|