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
19def 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
37def p_program_error(p):
38    '''program : error'''
39    p[0] = None
40    p.parser.error = 1
41
42#### Format of all BASIC statements.
43
44def 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
56def 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
63def p_statement_blank(p):
64    '''statement : INTEGER NEWLINE'''
65    p[0] = (0,('BLANK',int(p[1])))
66
67#### Error handling for malformed statements
68
69def 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
77def p_statement_newline(p):
78    '''statement : NEWLINE'''
79    p[0] = None
80
81#### LET statement
82
83def p_command_let(p):
84    '''command : LET variable EQUALS expr'''
85    p[0] = ('LET',p[2],p[4])
86
87def p_command_let_bad(p):
88    '''command : LET variable EQUALS error'''
89    p[0] = "BAD EXPRESSION IN LET"
90
91#### READ statement
92
93def p_command_read(p):
94    '''command : READ varlist'''
95    p[0] = ('READ',p[2])
96
97def p_command_read_bad(p):
98    '''command : READ error'''
99    p[0] = "MALFORMED VARIABLE LIST IN READ"
100
101#### DATA statement
102
103def p_command_data(p):
104    '''command : DATA numlist'''
105    p[0] = ('DATA',p[2])
106
107def p_command_data_bad(p):
108    '''command : DATA error'''
109    p[0] = "MALFORMED NUMBER LIST IN DATA"
110
111#### PRINT statement
112
113def p_command_print(p):
114    '''command : PRINT plist optend'''
115    p[0] = ('PRINT',p[2],p[3])
116
117def 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
123def 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
134def p_command_print_empty(p):
135    '''command : PRINT'''
136    p[0] = ('PRINT',[],None)
137
138#### GOTO statement
139
140def p_command_goto(p):
141    '''command : GOTO INTEGER'''
142    p[0] = ('GOTO',int(p[2]))
143
144def p_command_goto_bad(p):
145    '''command : GOTO error'''
146    p[0] = "INVALID LINE NUMBER IN GOTO"
147
148#### IF-THEN statement
149
150def p_command_if(p):
151    '''command : IF relexpr THEN INTEGER'''
152    p[0] = ('IF',p[2],int(p[4]))
153
154def p_command_if_bad(p):
155    '''command : IF error THEN INTEGER'''
156    p[0] = "BAD RELATIONAL EXPRESSION"
157
158def p_command_if_bad2(p):
159    '''command : IF relexpr THEN error'''
160    p[0] = "INVALID LINE NUMBER IN THEN"
161
162#### FOR statement
163
164def 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
168def 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
172def 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
176def 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
182def 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
192def p_command_next(p):
193    '''command : NEXT ID'''
194
195    p[0] = ('NEXT',p[2])
196
197def p_command_next_bad(p):
198    '''command : NEXT error'''
199    p[0] = "MALFORMED NEXT"
200
201#### END statement
202
203def p_command_end(p):
204    '''command : END'''
205    p[0] = ('END',)
206
207#### REM statement
208
209def p_command_rem(p):
210    '''command : REM'''
211    p[0] = ('REM',p[1])
212
213#### STOP statement
214
215def p_command_stop(p):
216    '''command : STOP'''
217    p[0] = ('STOP',)
218
219#### DEF statement
220
221def p_command_def(p):
222    '''command : DEF ID LPAREN ID RPAREN EQUALS expr'''
223    p[0] = ('FUNC',p[2],p[4],p[7])
224
225def p_command_def_bad_rhs(p):
226    '''command : DEF ID LPAREN ID RPAREN EQUALS error'''
227    p[0] = "BAD EXPRESSION IN DEF STATEMENT"
228
229def 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
235def p_command_gosub(p):
236    '''command : GOSUB INTEGER'''
237    p[0] = ('GOSUB',int(p[2]))
238
239def p_command_gosub_bad(p):
240    '''command : GOSUB error'''
241    p[0] = "INVALID LINE NUMBER IN GOSUB"
242
243#### RETURN statement
244
245def p_command_return(p):
246    '''command : RETURN'''
247    p[0] = ('RETURN',)
248
249#### DIM statement
250
251def p_command_dim(p):
252    '''command : DIM dimlist'''
253    p[0] = ('DIM',p[2])
254
255def 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
261def 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
272def p_dimitem_single(p):
273    '''dimitem : ID LPAREN INTEGER RPAREN'''
274    p[0] = (p[1],eval(p[3]),0)
275
276def 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
282def 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
291def p_expr_number(p):
292    '''expr : INTEGER
293            | FLOAT'''
294    p[0] = ('NUM',eval(p[1]))
295
296def p_expr_variable(p):
297    '''expr : variable'''
298    p[0] = ('VAR',p[1])
299
300def p_expr_group(p):
301    '''expr : LPAREN expr RPAREN'''
302    p[0] = ('GROUP',p[2])
303
304def p_expr_unary(p):
305    '''expr : MINUS expr %prec UMINUS'''
306    p[0] = ('UNARY','-',p[2])
307
308#### Relational expressions
309
310def 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
321def 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
334def 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
346def 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
358def p_number(p):
359    '''number  : INTEGER
360               | FLOAT'''
361    p[0] = eval(p[1])
362
363#### A signed number.
364
365def 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
373def 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
382def p_item_string(p):
383    '''pitem : STRING'''
384    p[0] = (p[1][1:-1],None)
385
386def p_item_string_expr(p):
387    '''pitem : STRING expr'''
388    p[0] = (p[1][1:-1],p[2])
389
390def p_item_expr(p):
391    '''pitem : expr'''
392    p[0] = ("",p[1])
393
394#### Empty
395
396def p_empty(p):
397    '''empty : '''
398
399#### Catastrophic error handler
400def p_error(p):
401    if not p:
402        print("SYNTAX ERROR AT EOF")
403
404bparser = yacc.yacc()
405
406def 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
425