Skip to content

Instantly share code, notes, and snippets.

@Pinacolada64
Last active April 3, 2022 06:30
Show Gist options
  • Save Pinacolada64/57edf11bdf847f89c803a0364f81a281 to your computer and use it in GitHub Desktop.
Save Pinacolada64/57edf11bdf847f89c803a0364f81a281 to your computer and use it in GitHub Desktop.
Claude BASIC
import math
import random
import re
import time
import sys
line = ""
matched = None
cursor = 0
variables = {}
functions = {}
commands = {}
immediate_commands = {}
stack = []
program = {}
program_counter = -1
jumped = False
stopped = False
class ParsingError(Exception):
pass
################################################################################
# Lexical analysis (breaking the text down into fundamental forms, or
# 'tokens')
#
def skip_whitespace():
global cursor
while cursor < len(line) and line[cursor].isspace():
cursor += 1
def expect_kw():
global cursor
skip_whitespace()
if cursor >= len(line):
raise ParsingError("Unexpected end of line (expecting a keyword.)")
mark = cursor
while cursor < len(line) and line[cursor].isalpha():
cursor += 1
if cursor > mark:
return line[mark:cursor]
else:
raise ParsingError("Expected a keyword")
def expect_kwlist():
varnames = [expect_kw()]
while match(","):
varnames.append(expect_kw())
return varnames
def match_kw():
global matched
try:
matched = expect_kw()
return True
except ParsingError:
return False
def expect_string():
global cursor
skip_whitespace()
if cursor >= len(line):
raise ParsingError("Unexpected end of line (expected string literal.)")
if line[cursor] != '"':
raise ParsingError("Expected string literal")
else:
cursor += 1
mark = cursor
while line[cursor] != '"':
# Detect backslash escape sequences and skip over them (if they're
# valid) -- they're replaced below
if line[cursor] == '\\':
if (cursor + 1) < len(line) and line[cursor + 1] == '"':
cursor += 1
elif (cursor + 1) < len(line) and line[cursor + 1] == '\\':
cursor += 1
else:
raise ParsingError("Unknown backslash escape in string")
cursor += 1
if not cursor < len(line):
raise ParsingError("Unterminated string literal")
# Skip the closing double quote, so we won't find it when we look
# for the next thing:
cursor += 1
return line[mark:cursor - 1].replace("\\\\", "\\").replace("\\\"", '"')
def match_string():
global matched
# This is bad -- it won't actually tell the user about errors in
# their strings, like erroneous backslash escapes...
try:
matched = expect_string()
return True
except ParsingError:
return False
def expect_num():
global cursor
skip_whitespace()
if cursor >= len(line):
raise ParsingError("Unexpected end of line (expected number.)")
if not line[cursor].isdigit():
raise ParsingError("Expected a number")
mark = cursor
while cursor < len(line) and line[cursor].isdigit():
cursor += 1
if cursor < len(line) and line[cursor] == ".":
cursor += 1 # Skip any `.' encountered
return float(line[mark:cursor])
def match_num():
global matched
try:
matched = expect_num()
return True
except ParsingError:
return False
def match(string):
global cursor
skip_whitespace()
if len(line) < (cursor + len(string)):
return False
if line[cursor:cursor + len(string)] == string:
cursor += len(string)
return True
else:
return False
# TODO: make sure there aren't any other places we should be using this?
def match_nocase(string):
global cursor
skip_whitespace()
mark = cursor
try:
kw = expect_kw().lower()
if kw == string:
return True
else:
cursor = mark
return False
except ParsingError:
return False
def expect_nocase(string):
if not match_nocase(string):
raise ParsingError("Expected `"+string.upper()+"'")
def expect_varname():
global cursor
skip_whitespace()
if cursor >= len(line):
raise ParsingError("Expected a variable, not end of line")
if not line[cursor].isalpha():
raise ParsingError("Expected a variable")
mark = cursor
while cursor < len(line) and (line[cursor].isalpha() or line[cursor].isdigit() or line[cursor] == "_"):
cursor += 1
return line[mark:cursor].lower()
def expect_varlist():
varnames = [expect_varname()]
while match(","):
varnames.append(expect_varname())
return varnames
def match_identifier():
global matched
try:
matched = expect_varname()
return True
except ParsingError:
return False
def expect_defvar():
global cursor
vn = expect_varname()
if vn in variables:
return vn
else:
raise ParsingError("Undefined variable '" + vn + ".")
def match_defvar():
global matched, cursor
# Might turn out not to be defined, but still consume part of the line
mark = cursor
try:
matched = expect_defvar()
return True
except ParsingError:
cursor = mark
return False
def expect(str):
"""Generically expect `str' to be the next non-whitespace portion of
input; raise error if not. As with all expect(), advances the cursor,
"consuming" part of the line."""
if not match(str):
raise ParsingError("Expected `" + str + "'.")
################################################################################
# Parsing/interpreting arithmetic.
#
operators = []
# In order of _decreasing_ precedence:
# (I'm not sure if / should be higher than * ... I thought maybe it should,
# but ...)
# (It probably shouldn't. Meh.)
operators.append({
"+": (lambda x,y: x + y),
"-": (lambda x,y: x - y)})
operators.append({
"/": (lambda x,y: x / y)})
operators.append({
"*": (lambda x,y: x * y)})
def expect_value():
"""Expect a value: either an atomic number, a variable (will in that case return
the contents of the variable), a function call, or a subexpression wrapped in
parentheses ('( ... )'), which will be evaluated like expect_arithmetic()."""
global cursor, matched
# Experimental, not sure if it will break things by eating -s where it shouldn't,
# but it doesn't seem to.
signum = 1
if match("-"):
signum = -1
if match_num():
return float(matched) * signum
elif match_identifier():
# Extra complicated, because for now we're hooking functions into the parser here too.
if matched in functions:
return call_fn(matched, expect_fn_args()) * signum
elif matched in variables:
return variables[matched] * signum
else:
raise ParsingError("Expected a variable or a function")
elif match("("):
res = expect_arithmetic()
if not match(")"):
raise ParsingError("Expected a closing parenthesis")
return res * signum
else:
raise ParsingError("Expected a value")
def match_operator(rank):
global cursor, matched
skip_whitespace()
if cursor < len(line) and line[cursor] in operators[rank]: # !!!!! FIXME -- won't work on two-character operators
matched = line[cursor]
cursor += 1
return True
else:
return False
def expect_arithmetic(rank = 0):
# This function is the heart of parsing expressions... Do note : you might
# be able to make it more abstract by allowing caller to specify the
# operator table to use, and what function to use to acquire generic "atoms"
# (expect_value() here.)
global cursor, matched
accumulator = None
if rank < len(operators) - 1:
accumulator = expect_arithmetic(rank + 1)
else:
# We're the lowest level; we need to match fundamental/atomic values instead.
accumulator = expect_value()
while match_operator(rank):
op = matched
if rank < len(operators) - 1:
# There are 'levels' of operator below us.
accumulator = operators[rank][op](accumulator, expect_arithmetic())
else:
# We're the lowest level of operators; only fundamental values are
# below us.
accumulator = operators[rank][op](accumulator, expect_value())
return accumulator
################################################################################
# Parsing/interpreting logic (expressions.)
#
def match_relation():
global matched
relations = ["==", "<=", ">=", "!=", ">", "<"]
for r in relations:
if match(r):
matched = r
return True
return False
def expect_comparison():
left = expect_arithmetic()
if not match_relation(): # i.e., no < or == or (etc.) found
return left
else:
op = matched
right = expect_arithmetic()
if op == "==":
return -(left == right)
elif op == "<=":
return -(left <= right)
elif op == ">=":
return -(left >= right)
elif op == "!=":
return -(left != right)
elif op == ">":
return -(left > right)
elif op == "<":
return -(left < right)
else:
raise ParsingError("Somehow found an invalid relation (this should never happen)")
# Basically does the same thing as the arithmetic parsing above, but less cleverly.
# I could maybe write an even more general function that would handle all cases
# like this -- somehow... I'm not sure if it would be that much better though.
def expect_negation_or_comparison():
if match_nocase("not"):
return -(expect_comparison() == 0) # 'is equal to false' -- produces True (-1) when it's 'false' (0)
else:
return expect_comparison()
def expect_also_and():
left = expect_negation_or_comparison()
while match_nocase("and"):
right = expect_negation_or_comparison()
left = -(left != 0 and right != 0)
return left
def expect_also_or():
left = expect_also_and()
while match_nocase("or"):
right = expect_also_and()
left = -(left != 0 or right != 0)
return left
def expect_expression():
# This is super recursive.
return expect_also_or()
################################################################################
# Functions.
#
# They're hooked into the parser further above, in expect_value().
# However, these routines do most of the work, and we also define our
# builtin functions here.
def expect_fn_args():
if match("("):
if match(")"):
return []
args = [expect_expression()]
while match(","):
args.append(expect_expression())
expect(")")
return args
else:
return []
def call_fn(fn, args):
if fn not in functions:
raise ParsingError("No such function `"+fn+"'.")
if len(args) != functions[fn]["args"]:
raise ParsingError("Arity mismatch (want "+str(functions[fn]["args"])+", got "+str(len(args))+")")
# Bother, we need `apply'. -- okay, write foo(*args, **kwargs) to do `apply'-like things
# (kwargs would be a dictionary, presumably; args is a list.)
if "builtin" in functions[fn]:
return functions[fn]["builtin"](*args)
elif "body" in functions[fn]:
return call_ufn(fn, args)
else:
raise ParsingError("Function exists but has no definition (this should never happen)")
# return 0
# In BASIC, "function" is more like algebra's notion of a mathematical function
# -- they're essentially an expression packaged up into a reusable atom, not a
# "function" in the C or Python sense (there, you'd expect them to be more like
# subroutines.) BASIC uses GOSUB for subroutines.
functions["cos"] = {"args":1, "builtin":(lambda x:math.cos(x))}
functions["sin"] = {"args":1, "builtin":(lambda x:math.sin(x))}
functions["tan"] = {"args":1, "builtin":(lambda x:math.tan(x))}
functions["atan"] = {"args":1, "builtin":(lambda x:math.atan(x))}
functions["atan2"] = {"args":1, "builtin":(lambda x:math.atan2(x))}
functions["acos"] = {"args":1, "builtin":(lambda x:math.acos(x))}
functions["sqrt"] = {"args":1, "builtin":(lambda x:math.sqrt(x))}
functions["int"] = {"args":1, "builtin":(lambda x:math.trunc(x))}
functions["abs"] = {"args":1, "builtin":(lambda x:abs(x))}
functions["rnd"] = {"args":0, "builtin":(lambda:random.random())}
functions["timer"] = {"args":0, "builtin":(lambda:time.clock())}
# Let the user define their own functions.
def do_defn():
global cursor, functions
expect_nocase("fn")
name = expect_varname()
expect("(")
if match(")"):
args = []
else:
args = expect_varlist()
expect(")")
expect("=")
if name in functions and "builtin" in functions[name]:
raise ParsingError("Attempted to redefine a builtin function")
functions[name] = {
"args": len(args),
"body": line[cursor:], # Better not think you can do multiple statements
"arglist": args
}
cursor = len(line) # Consume the rest of the line.
commands["def"] = do_defn
# Figure out how to call the user's functions.
def call_ufn(fn, args):
# Mostly just a straight duplicate.
global variables, line, cursor
# The given strategy is to save a copy of the globals, then clobber them
# such that we can just use our existing expression parser to parse the
# stored expression (i.e., putting the `args' into ordinary BASIC
# variables.) I'm sure there's a better, more computer science-y way to do
# this, but I don't know what it is.
tvars = variables
tline = line
tcursor = cursor
this = functions[fn]
for argname in this["arglist"]:
variables[argname] = args[0]
del args[0]
line = this["body"]
cursor = 0
# Basically copying the given method. Try ... finally because parsing can throw
# ParsingErrors.
try:
result = expect_expression()
finally:
line = tline
variables = tvars
cursor = tcursor
return result
################################################################################
# Parsing/interpreting single statements.
#
def parse_statement():
global cursor
s = expect_kw().lower()
if s == "rem":
cursor = len(line)
return
elif s in commands:
commands[s]()
else:
raise ParsingError("Unrecognised statement (" + s + ").")
def do_print():
global cursor, matched, variables
skip_whitespace()
if cursor <= len(line):
msg = ""
while cursor < len(line):
if match_string():
msg += matched
else:
msg += str(expect_expression())
if not match(","):
break
print msg
else:
print() # a new line
commands["print"] = do_print
commands["p"] = do_print # shorthand for testing
def do_input():
global variables
prompt = ""
if match_string():
prompt = matched
if not match(","):
raise ParsingError("Expected ','")
varnames = expect_kwlist()
data = raw_input(prompt).split(",")
for n in varnames:
if len(data) >= 1:
variables[n] = float(data[0])
data = data[1:] # Trim off first element each time
else:
variables[n] = 0 # Hmm
commands["input"] = do_input
def do_let():
global variables
skip_whitespace()
if cursor >= len(line):
raise ParsingError("Unexpected end of line")
name = expect_varname()
if not match("="):
raise ParsingError("Expected =")
val = expect_expression()
variables[name] = val
commands["let"] = do_let
def do_if():
global cursor
skip_whitespace()
if cursor >= len(line):
raise ParsingError("Unexpected end of line")
if expect_expression() != 0: # "not false"
if not match_nocase("then"):
raise ParsingError("Expected THEN")
parse_statement()
else:
cursor = len(line)
commands["if"] = do_if
def do_stop():
global stopped
stopped = True
print "Program execution paused. DO NOT add or remove lines and attempt to `continue' \
from this point. If you do, use `run' instead."
commands["stop"] = do_stop
# A statement to support the rnd() function: help make the RNG repeatable (or
# set it back to pseudo-unpredictable.)
def do_randomize():
random.seed(expect_value())
commands["randomize"] = do_randomize
################################################################################
# Parsing lines (which might be lines to be stored into the program store) and
# blocks of statements.
#
def parse_line():
global cursor
skip_whitespace()
mark = cursor
while cursor < len(line) and line[cursor].isdigit():
cursor += 1
if cursor > mark:
# We found a leading (line) number; store the line
if cursor >= len(line):
raise ParsingError("Expected code")
skip_whitespace()
program[int(line[mark:cursor])] = line[cursor:len(line)]
else:
# It's immediate mode. Just parse it.
parse_block()
def parse_block():
parse_statement()
while match(":") and not jumped and not stopped:
parse_statement()
if not (cursor >= len(line)) and not jumped and not stopped:
raise ParsingError("Expected end of line")
################################################################################
# GOTO, GOSUB, and friends
#
def do_goto():
global program_counter, cursor, jumped
nx = expect_num()
if nx in program:
program_counter = sorted(program.keys()).index(nx)
cursor = 0
jumped = True
else:
raise ParsingError("No such line (" + str(nx) + ").")
commands["goto"] = do_goto
def do_gosub():
global program_counter, cursor, stack, jumped
nx = expect_num()
# This could be expect_expression(), which I guess would let you
# have dynamic GOSUBs assigned by the code earlier...
# `renumber' won't know about anything more complicated than a simple
# line number, however.
if nx in program:
stack.append({"ln":program_counter, "cursor":cursor})
program_counter = sorted(program.keys()).index(nx)
cursor = 0
# run() will go directly to this program line and cursor value if we set
# the `jumped' flag.
jumped = True
else:
raise ParsingError("No such line (" + str(nx) + ").")
commands["gosub"] = do_gosub
def do_return():
global program_counter, cursor, stack, jumped
try:
program_counter = stack[len(stack)-1]["ln"]
cursor = stack[len(stack)-1]["cursor"]
stack.pop()
print "Return to " + str(sorted(program.keys())[program_counter]) + "," + str(cursor)
jumped = True
except IndexError:
raise ParsingError("Stack overflow, or invalid stack frame.")
commands["return"] = do_return
def do_end():
global program_counter, stack
# Works by just 'jumping' to the end of the program. Apparently still works
# after the 'jumped' change -- probably because continue() just goes on to the
# next line, but still finds it's not there.
program_counter = len(program.keys())
# Let's reset the stack. This will keep anything that might be left from,
# well, stacking up between RUNs or, worse, causing odd behaviour from
# program run to program run in some kind of odd case I can't be bothered to
# think of a specific example for.
stack = []
# We could also reset the variables here. I'm not sure if we should.
# Maybe?
commands["end"] = do_end
################################################################################
# Looping control structures
#
def do_loop_do():
global stack
if program_counter+1 < len(program):
stack.append({"ln":program_counter+1, "cursor":0})
else:
raise ParsingError("Unexpected end of program")
commands["do"] = do_loop_do
def do_loop_loop():
global program_counter, stack, cursor, jumped
if len(stack) <= 0:
raise ParsingError("Stack underflow")
c = expect_kw().lower()
if c == "while":
if expect_expression() == 0:
# It's no longer true, so we don't jump back to loop start. Also
# take away our stack pointer.
del stack[len(stack)-1]
return
elif c == "until":
if expect_expression() != 0:
# It's true -- 'until' is satisfied, don't jump back.
del stack[len(stack)-1]
return
else:
raise ParsingError("Expected WHILE or UNTIL")
# Conditions passed.... let's jump back.
c = stack[len(stack)-1]
program_counter = c["ln"]
cursor = c["cursor"]
jumped = True
commands["loop"] = do_loop_loop
def do_loop_for():
global stack, variables
vn = expect_varname()
expect("=") # Tempted to write expect("from")
fff = expect_value() # To allow for negative numbers.
expect("to")
ttt = expect_value()
sss = 1
if ttt < fff:
sss = -1 # Target is LOWER than starting value, so we need to go down.
if match_nocase("step"):
sss = expect_value()
if sss == 0:
raise ParsingError("Infinite loop") # Just use GOTO (why would you want to?)
if program_counter >= len(program):
raise ParsingError("Expected more program")
stack.append({"ln": program_counter+1, "cursor":0})
variables[vn] = fff
stack.append({"loop-to": ttt, "loop-step": sss})
commands["for"] = do_loop_for
def do_loop_next():
global stack, variables, program_counter, cursor, jumped
vn = expect_defvar()
try:
loopstuff = stack[len(stack)-1]
target = stack[len(stack)-2]
except IndexError:
raise ParsingError("Stack underflow")
try:
variables[vn] += loopstuff["loop-step"]
if (loopstuff["loop-step"] > 0 and variables[vn] <= loopstuff["loop-to"]) or \
(loopstuff["loop-step"] < 0 and variables[vn] >= loopstuff["loop-to"]):
# We still want to loop, let's loop.
program_counter = target["ln"]
cursor = target["cursor"]
jumped = True
else:
# We're done looping. Let's clean up.
del stack[len(stack)-1]
del stack[len(stack)-2]
except KeyError:
raise ParsingError("Type mismatch with top of stack (mixed loop constructs?)")
commands["next"] = do_loop_next
################################################################################
# do_run() and do_continue()
#
# You could probably describe these as the "heart of the interpreter" -- it's
# the main loop, without which there would be no meaningful computation because
# we wouldn't know how to run more than one line at a time, without which we
# won't be much more than a pocket calculator.
def do_run():
global line, cursor, program_counter, jumped, stack
program_counter = 0
stack = [] # We may as well clear it every now and then. Start in a predictable state.
do_continue()
def do_continue():
global line, cursor, program_counter, jumped, stopped
lns = sorted(program.keys())
cursor = 0
stopped = False
if program_counter >= len(lns) or len(lns) == 0:
print "No more program to run."
return
while program_counter < len(lns) and not stopped:
# Retrieve line.
ln = lns[program_counter]
line = program[ln]
if jumped:
match(":")
# Kind of lousy, probably. In case we jumped back JUST in front of a new statement,
# skip any : to keep parse_line() from getting confused.
jumped = Falses
try:
parse_line()
# I think you can write '10 20 print "This overrides line 20! But
# it won't run till next RUN."' :)
# ... it works in practice, sort of, but causes an error for some
# reason as well...
except ParsingError as e:
# Report the error in a nice way.
print line
print " "*cursor + "^ error on line " + str(ln) + ": " + str(e)
cursor = len(line)
break
# The command above may have decided it wanted to jump to a different
# position -- line and cursor -- in the program. If it did, we should
# leave the new position in place. But if it didn't, we need to go
# onto the next line.
if not jumped:
program_counter += 1
cursor = 0
if stopped:
print "... on line " + str(lns[program_counter - 1])
# -1 because we've already technically moved onto the next line
print "cursor=" + str(cursor)
immediate_commands["run"] = do_run
immediate_commands["continue"] = do_continue
################################################################################
# Other immediate-mode commands
#
# These are stored separately from the language's commands. Because
# parse_block() doesn't handle them, they don't have to worry about leaving the
# interpreter in an odd state and messing up parse_block() when it tries to see
# if there's another statement to parse. They can ONLY be called interactively,
# from the interpreter itself line.
def do_save():
fn = expect_string()
with open(fn, "w") as fx:
for ln in sorted(program.keys()):
fx.write(" " + str(ln) + "\t" + program[ln])
fx.write("\n")
immediate_commands["save"] = do_save
immediate_commands["s"] = do_save
def do_load(filename = None):
global line, cursor
if filename:
fn = filename
else:
fn = expect_string()
with open(fn, "r") as fx:
for ln in fx:
line = re.sub("\n+$", "", ln) # Otherwise we end up with \ns read in on the ends of the lines.
cursor = 0
try:
# Allow there to be blank lines (i.e. ones consisting of nothing but space) in the file.
if re.match(".*[^\s]", line):
parse_line()
except ParsingError as e:
print "Error while parsing file:"
print line
print " "*cursor + "^ " + str(e)
break
immediate_commands["load"] = do_load
immediate_commands["l"] = do_load
def do_help():
# Elissa's addition
print "Immediate mode commands:"
# make a copy of the unsorted list so as not to disturb the original list.
# Sorts sort in place.
mydict = immediate_commands
for key in sorted(mydict.iterkeys()):
print ("%s\t" % key),
print("\n")
print "Program mode commands:"
mydict = commands
for key in sorted(mydict.iterkeys()):
print ("%s\t" % key),
print("\n")
# raw_input("Press Enter to continue: ")
immediate_commands["help"] = do_help
def do_new():
global program
program.clear()
immediate_commands["new"] = do_new
def do_clear():
global variables
variables.clear()
immediate_commands["clear"] = do_clear
################################################################################
# Program manipulation (and viewing) -- all the commands that operate on sets of
# lines.
#
class LineRange:
"""Represents a range of line numbers, probably selected by the user."""
def __init__(self, start, end):
if start < end or (start >= 0 and end == -1) or end is None:
self.start = start
self.end = end
else:
self.start = 0
self.end = 0
def lns(self):
"""Return a list of every line number in program[] that is within the
range."""
lns = sorted(program.keys())
if self.end is None:
# We only point to one, specific, line.
if not (self.start in lns):
raise ParsingError("No such line `" +str(int(self.start))+ "'.")
return [self.start]
else:
ret = []
# We point to a range of lines.
for ln in lns:
if ln >= self.start and (ln <= self.end or self.end == -1):
ret.append(ln)
return ret
def expect_linerange():
"""Read in a range of lines that the user wants to operate on."""
firstl = expect_num()
if match("-"):
if match_num():
secondl = matched
else:
secondl = -1 # i.e., "infinity"
else:
secondl = None
return LineRange(firstl, secondl)
def do_list():
"""List the program, or part of the program."""
try:
range = expect_linerange()
except ParsingError:
range = LineRange(0, -1) # start at 0, go to infinity
for line_number in range.lns():
print str(int(line_number)) + "\t" + program[line_number]
immediate_commands["list"] = do_list
def do_renum():
"""Renumber a range of lines -- makes it easier to manipulate programs when inserting
many lines, for instance. CAUTION!! Uncertain about this, it has to use regexps and
fix every command that refers to a line number!"""
global program
try:
r = expect_linerange()
except ParsingError:
r = LineRange(0, -1) # We'll remap the whole program by default.
target = 10
if match_nocase("to"):
target = expect_num()
step = 10
if match_nocase("step"):
step = expect_num()
oldp = program.copy() # Just in case.
counter = target
new = {}
# Move lines out of the program, renumbering as we go. This approach turns
# out to be much simpler than trying to renumber them in-place. Note that
# we also need to search through and renumber GOTO or GOSUB calls.
for ln in r.lns():
renum_change_calls(ln, counter)
new[counter] = program[ln]
del program[ln]
counter += step
# Merge it back into the program.
for kx in sorted(new.keys()):
if kx in program:
print "Whoops, line `" +str(int(kx))+ "' already exists!"
print "Rolling back changes and aborting."
program = oldp
return
program[kx] = new[kx]
def renum_change_calls(ln, new):
global program
lns = sorted(program.keys())
for line in lns:
program[line] = re.sub("(goto|gosub)\\s*"+str(int(ln)), "goto "+str(int(new)), program[line], flags=re.I)
immediate_commands["renumber"] = do_renum
immediate_commands["renum"] = do_renum
immediate_commands["rn"] = do_renum
def do_del():
global program
nx = expect_num()
if match("-"):
nx2 = expect_num()
for ln in sorted(program.keys()):
# if ln > nx and ln < nx2:
if ln in range(nx, nx2+1):
del program[ln]
else:
if nx in program:
del program[nx]
immediate_commands["delete"] = do_del
immediate_commands["del"] = do_del
################################################################################
# The interactive interpreter itself.
#
print("Crude BASIC interpreter")
do_help()
if len(sys.argv) > 1:
do_load(sys.argv[len(sys.argv)-1])
print "... in " + sys.argv[len(sys.argv)-1]
while True:
try:
line = raw_input("* ")
if line.lower() == "bye":
break
except EOFError:
break
cursor = 0
# Try matching immediate-mode commands.
mark = cursor
if match_kw() and matched.lower() in immediate_commands:
try:
immediate_commands[matched]()
except ParsingError as e:
print " "*(cursor+1) + "^"
print str(e)
except KeyboardInterrupt:
# Supposing the user gets into an infinite loop after typing `run', for example.
print "User interrupt."
else:
# If none was found, try running the line normally.
cursor = mark
try:
parse_line()
except ParsingError as e:
print (" " * (cursor+1)) + "^" # (Prompt is two chars wide, but cursor is 0-indexed.)
print str(e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment