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