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