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