424 lines
8.7 KiB
Python
424 lines
8.7 KiB
Python
# An implementation of Dartmouth BASIC (1964)
|
|
#
|
|
|
|
from ply import *
|
|
import basiclex
|
|
|
|
tokens = basiclex.tokens
|
|
|
|
precedence = (
|
|
('left', 'PLUS','MINUS'),
|
|
('left', 'TIMES','DIVIDE'),
|
|
('left', 'POWER'),
|
|
('right','UMINUS')
|
|
)
|
|
|
|
#### A BASIC program is a series of statements. We represent the program as a
|
|
#### dictionary of tuples indexed by line number.
|
|
|
|
def p_program(p):
|
|
'''program : program statement
|
|
| statement'''
|
|
|
|
if len(p) == 2 and p[1]:
|
|
p[0] = { }
|
|
line,stat = p[1]
|
|
p[0][line] = stat
|
|
elif len(p) ==3:
|
|
p[0] = p[1]
|
|
if not p[0]: p[0] = { }
|
|
if p[2]:
|
|
line,stat = p[2]
|
|
p[0][line] = stat
|
|
|
|
#### This catch-all rule is used for any catastrophic errors. In this case,
|
|
#### we simply return nothing
|
|
|
|
def p_program_error(p):
|
|
'''program : error'''
|
|
p[0] = None
|
|
p.parser.error = 1
|
|
|
|
#### Format of all BASIC statements.
|
|
|
|
def p_statement(p):
|
|
'''statement : INTEGER command NEWLINE'''
|
|
if isinstance(p[2],str):
|
|
print("%s %s %s" % (p[2],"AT LINE", p[1]))
|
|
p[0] = None
|
|
p.parser.error = 1
|
|
else:
|
|
lineno = int(p[1])
|
|
p[0] = (lineno,p[2])
|
|
|
|
#### Interactive statements.
|
|
|
|
def p_statement_interactive(p):
|
|
'''statement : RUN NEWLINE
|
|
| LIST NEWLINE
|
|
| NEW NEWLINE'''
|
|
p[0] = (0, (p[1],0))
|
|
|
|
#### Blank line number
|
|
def p_statement_blank(p):
|
|
'''statement : INTEGER NEWLINE'''
|
|
p[0] = (0,('BLANK',int(p[1])))
|
|
|
|
#### Error handling for malformed statements
|
|
|
|
def p_statement_bad(p):
|
|
'''statement : INTEGER error NEWLINE'''
|
|
print("MALFORMED STATEMENT AT LINE %s" % p[1])
|
|
p[0] = None
|
|
p.parser.error = 1
|
|
|
|
#### Blank line
|
|
|
|
def p_statement_newline(p):
|
|
'''statement : NEWLINE'''
|
|
p[0] = None
|
|
|
|
#### LET statement
|
|
|
|
def p_command_let(p):
|
|
'''command : LET variable EQUALS expr'''
|
|
p[0] = ('LET',p[2],p[4])
|
|
|
|
def p_command_let_bad(p):
|
|
'''command : LET variable EQUALS error'''
|
|
p[0] = "BAD EXPRESSION IN LET"
|
|
|
|
#### READ statement
|
|
|
|
def p_command_read(p):
|
|
'''command : READ varlist'''
|
|
p[0] = ('READ',p[2])
|
|
|
|
def p_command_read_bad(p):
|
|
'''command : READ error'''
|
|
p[0] = "MALFORMED VARIABLE LIST IN READ"
|
|
|
|
#### DATA statement
|
|
|
|
def p_command_data(p):
|
|
'''command : DATA numlist'''
|
|
p[0] = ('DATA',p[2])
|
|
|
|
def p_command_data_bad(p):
|
|
'''command : DATA error'''
|
|
p[0] = "MALFORMED NUMBER LIST IN DATA"
|
|
|
|
#### PRINT statement
|
|
|
|
def p_command_print(p):
|
|
'''command : PRINT plist optend'''
|
|
p[0] = ('PRINT',p[2],p[3])
|
|
|
|
def p_command_print_bad(p):
|
|
'''command : PRINT error'''
|
|
p[0] = "MALFORMED PRINT STATEMENT"
|
|
|
|
#### Optional ending on PRINT. Either a comma (,) or semicolon (;)
|
|
|
|
def p_optend(p):
|
|
'''optend : COMMA
|
|
| SEMI
|
|
|'''
|
|
if len(p) == 2:
|
|
p[0] = p[1]
|
|
else:
|
|
p[0] = None
|
|
|
|
#### PRINT statement with no arguments
|
|
|
|
def p_command_print_empty(p):
|
|
'''command : PRINT'''
|
|
p[0] = ('PRINT',[],None)
|
|
|
|
#### GOTO statement
|
|
|
|
def p_command_goto(p):
|
|
'''command : GOTO INTEGER'''
|
|
p[0] = ('GOTO',int(p[2]))
|
|
|
|
def p_command_goto_bad(p):
|
|
'''command : GOTO error'''
|
|
p[0] = "INVALID LINE NUMBER IN GOTO"
|
|
|
|
#### IF-THEN statement
|
|
|
|
def p_command_if(p):
|
|
'''command : IF relexpr THEN INTEGER'''
|
|
p[0] = ('IF',p[2],int(p[4]))
|
|
|
|
def p_command_if_bad(p):
|
|
'''command : IF error THEN INTEGER'''
|
|
p[0] = "BAD RELATIONAL EXPRESSION"
|
|
|
|
def p_command_if_bad2(p):
|
|
'''command : IF relexpr THEN error'''
|
|
p[0] = "INVALID LINE NUMBER IN THEN"
|
|
|
|
#### FOR statement
|
|
|
|
def p_command_for(p):
|
|
'''command : FOR ID EQUALS expr TO expr optstep'''
|
|
p[0] = ('FOR',p[2],p[4],p[6],p[7])
|
|
|
|
def p_command_for_bad_initial(p):
|
|
'''command : FOR ID EQUALS error TO expr optstep'''
|
|
p[0] = "BAD INITIAL VALUE IN FOR STATEMENT"
|
|
|
|
def p_command_for_bad_final(p):
|
|
'''command : FOR ID EQUALS expr TO error optstep'''
|
|
p[0] = "BAD FINAL VALUE IN FOR STATEMENT"
|
|
|
|
def p_command_for_bad_step(p):
|
|
'''command : FOR ID EQUALS expr TO expr STEP error'''
|
|
p[0] = "MALFORMED STEP IN FOR STATEMENT"
|
|
|
|
#### Optional STEP qualifier on FOR statement
|
|
|
|
def p_optstep(p):
|
|
'''optstep : STEP expr
|
|
| empty'''
|
|
if len(p) == 3:
|
|
p[0] = p[2]
|
|
else:
|
|
p[0] = None
|
|
|
|
#### NEXT statement
|
|
|
|
def p_command_next(p):
|
|
'''command : NEXT ID'''
|
|
|
|
p[0] = ('NEXT',p[2])
|
|
|
|
def p_command_next_bad(p):
|
|
'''command : NEXT error'''
|
|
p[0] = "MALFORMED NEXT"
|
|
|
|
#### END statement
|
|
|
|
def p_command_end(p):
|
|
'''command : END'''
|
|
p[0] = ('END',)
|
|
|
|
#### REM statement
|
|
|
|
def p_command_rem(p):
|
|
'''command : REM'''
|
|
p[0] = ('REM',p[1])
|
|
|
|
#### STOP statement
|
|
|
|
def p_command_stop(p):
|
|
'''command : STOP'''
|
|
p[0] = ('STOP',)
|
|
|
|
#### DEF statement
|
|
|
|
def p_command_def(p):
|
|
'''command : DEF ID LPAREN ID RPAREN EQUALS expr'''
|
|
p[0] = ('FUNC',p[2],p[4],p[7])
|
|
|
|
def p_command_def_bad_rhs(p):
|
|
'''command : DEF ID LPAREN ID RPAREN EQUALS error'''
|
|
p[0] = "BAD EXPRESSION IN DEF STATEMENT"
|
|
|
|
def p_command_def_bad_arg(p):
|
|
'''command : DEF ID LPAREN error RPAREN EQUALS expr'''
|
|
p[0] = "BAD ARGUMENT IN DEF STATEMENT"
|
|
|
|
#### GOSUB statement
|
|
|
|
def p_command_gosub(p):
|
|
'''command : GOSUB INTEGER'''
|
|
p[0] = ('GOSUB',int(p[2]))
|
|
|
|
def p_command_gosub_bad(p):
|
|
'''command : GOSUB error'''
|
|
p[0] = "INVALID LINE NUMBER IN GOSUB"
|
|
|
|
#### RETURN statement
|
|
|
|
def p_command_return(p):
|
|
'''command : RETURN'''
|
|
p[0] = ('RETURN',)
|
|
|
|
#### DIM statement
|
|
|
|
def p_command_dim(p):
|
|
'''command : DIM dimlist'''
|
|
p[0] = ('DIM',p[2])
|
|
|
|
def p_command_dim_bad(p):
|
|
'''command : DIM error'''
|
|
p[0] = "MALFORMED VARIABLE LIST IN DIM"
|
|
|
|
#### List of variables supplied to DIM statement
|
|
|
|
def p_dimlist(p):
|
|
'''dimlist : dimlist COMMA dimitem
|
|
| dimitem'''
|
|
if len(p) == 4:
|
|
p[0] = p[1]
|
|
p[0].append(p[3])
|
|
else:
|
|
p[0] = [p[1]]
|
|
|
|
#### DIM items
|
|
|
|
def p_dimitem_single(p):
|
|
'''dimitem : ID LPAREN INTEGER RPAREN'''
|
|
p[0] = (p[1],eval(p[3]),0)
|
|
|
|
def p_dimitem_double(p):
|
|
'''dimitem : ID LPAREN INTEGER COMMA INTEGER RPAREN'''
|
|
p[0] = (p[1],eval(p[3]),eval(p[5]))
|
|
|
|
#### Arithmetic expressions
|
|
|
|
def p_expr_binary(p):
|
|
'''expr : expr PLUS expr
|
|
| expr MINUS expr
|
|
| expr TIMES expr
|
|
| expr DIVIDE expr
|
|
| expr POWER expr'''
|
|
|
|
p[0] = ('BINOP',p[2],p[1],p[3])
|
|
|
|
def p_expr_number(p):
|
|
'''expr : INTEGER
|
|
| FLOAT'''
|
|
p[0] = ('NUM',eval(p[1]))
|
|
|
|
def p_expr_variable(p):
|
|
'''expr : variable'''
|
|
p[0] = ('VAR',p[1])
|
|
|
|
def p_expr_group(p):
|
|
'''expr : LPAREN expr RPAREN'''
|
|
p[0] = ('GROUP',p[2])
|
|
|
|
def p_expr_unary(p):
|
|
'''expr : MINUS expr %prec UMINUS'''
|
|
p[0] = ('UNARY','-',p[2])
|
|
|
|
#### Relational expressions
|
|
|
|
def p_relexpr(p):
|
|
'''relexpr : expr LT expr
|
|
| expr LE expr
|
|
| expr GT expr
|
|
| expr GE expr
|
|
| expr EQUALS expr
|
|
| expr NE expr'''
|
|
p[0] = ('RELOP',p[2],p[1],p[3])
|
|
|
|
#### Variables
|
|
|
|
def p_variable(p):
|
|
'''variable : ID
|
|
| ID LPAREN expr RPAREN
|
|
| ID LPAREN expr COMMA expr RPAREN'''
|
|
if len(p) == 2:
|
|
p[0] = (p[1],None,None)
|
|
elif len(p) == 5:
|
|
p[0] = (p[1],p[3],None)
|
|
else:
|
|
p[0] = (p[1],p[3],p[5])
|
|
|
|
#### Builds a list of variable targets as a Python list
|
|
|
|
def p_varlist(p):
|
|
'''varlist : varlist COMMA variable
|
|
| variable'''
|
|
if len(p) > 2:
|
|
p[0] = p[1]
|
|
p[0].append(p[3])
|
|
else:
|
|
p[0] = [p[1]]
|
|
|
|
|
|
#### Builds a list of numbers as a Python list
|
|
|
|
def p_numlist(p):
|
|
'''numlist : numlist COMMA number
|
|
| number'''
|
|
|
|
if len(p) > 2:
|
|
p[0] = p[1]
|
|
p[0].append(p[3])
|
|
else:
|
|
p[0] = [p[1]]
|
|
|
|
#### A number. May be an integer or a float
|
|
|
|
def p_number(p):
|
|
'''number : INTEGER
|
|
| FLOAT'''
|
|
p[0] = eval(p[1])
|
|
|
|
#### A signed number.
|
|
|
|
def p_number_signed(p):
|
|
'''number : MINUS INTEGER
|
|
| MINUS FLOAT'''
|
|
p[0] = eval("-"+p[2])
|
|
|
|
#### List of targets for a print statement
|
|
#### Returns a list of tuples (label,expr)
|
|
|
|
def p_plist(p):
|
|
'''plist : plist COMMA pitem
|
|
| pitem'''
|
|
if len(p) > 3:
|
|
p[0] = p[1]
|
|
p[0].append(p[3])
|
|
else:
|
|
p[0] = [p[1]]
|
|
|
|
def p_item_string(p):
|
|
'''pitem : STRING'''
|
|
p[0] = (p[1][1:-1],None)
|
|
|
|
def p_item_string_expr(p):
|
|
'''pitem : STRING expr'''
|
|
p[0] = (p[1][1:-1],p[2])
|
|
|
|
def p_item_expr(p):
|
|
'''pitem : expr'''
|
|
p[0] = ("",p[1])
|
|
|
|
#### Empty
|
|
|
|
def p_empty(p):
|
|
'''empty : '''
|
|
|
|
#### Catastrophic error handler
|
|
def p_error(p):
|
|
if not p:
|
|
print("SYNTAX ERROR AT EOF")
|
|
|
|
bparser = yacc.yacc()
|
|
|
|
def parse(data,debug=0):
|
|
bparser.error = 0
|
|
p = bparser.parse(data,debug=debug)
|
|
if bparser.error: return None
|
|
return p
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|