Viewing file: basparse.py (8.69 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# 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
|