"""This module implements the primitives of the Scheme language.""" import math import operator import sys from scheme_reader import Pair, nil try: import turtle except: print("warning: could not import the turtle module.", file=sys.stderr) class SchemeError(Exception): """Exception indicating an error in a Scheme program.""" class okay(object): """Signifies an undefined value.""" def __repr__(self): return "okay" okay = okay() # Assignment hides the okay class; there is only one instance ######################## # Primitive Operations # ######################## class PrimitiveProcedure: """A Scheme procedure defined as a Python function.""" def __init__(self, fn, use_env=False): self.fn = fn self.use_env = use_env def __str__(self): return '#[primitive]' _PRIMITIVES = [] def primitive(*names): """An annotation to convert a Python function into a PrimitiveProcedure.""" def add(fn): proc = PrimitiveProcedure(fn) for name in names: _PRIMITIVES.append((name,proc)) return fn return add def add_primitives(frame): """Enter bindings in _PRIMITIVES into FRAME, an environment frame.""" for name, proc in _PRIMITIVES: frame.define(name, proc) def check_type(val, predicate, k, name): """Returns VAL. Raises a SchemeError if not PREDICATE(VAL) using "argument K of NAME" to describe the offending value.""" if not predicate(val): msg = "argument {0} of {1} has wrong type ({2})" raise SchemeError(msg.format(k, name, type(val).__name__)) return val @primitive("boolean?") def scheme_booleanp(x): return x is True or x is False def scheme_true(val): """All values in Scheme are true except False.""" return val is not False def scheme_false(val): """Only False is false in Scheme.""" return val is False @primitive("not") def scheme_not(x): return not scheme_true(x) @primitive("eq?", "equal?") def scheme_eqp(x, y): return x == y @primitive("pair?") def scheme_pairp(x): return isinstance(x, Pair) @primitive("null?") def scheme_nullp(x): return x is nil @primitive("list?") def scheme_listp(x): """Return whether x is a well-formed list. Assumes no cycles.""" while x is not nil: if not isinstance(x, Pair): return False x = x.second return True @primitive("length") def scheme_length(x): if x is nil: return 0 check_type(x, scheme_listp, 0, 'length') return len(x) @primitive("cons") def scheme_cons(x, y): return Pair(x, y) @primitive("car") def scheme_car(x): check_type(x, scheme_pairp, 0, 'car') return x.first @primitive("cdr") def scheme_cdr(x): check_type(x, scheme_pairp, 0, 'cdr') return x.second @primitive("list") def scheme_list(*vals): result = nil for i in range(len(vals)-1, -1, -1): result = Pair(vals[i], result) return result @primitive("append") def scheme_append(*vals): if len(vals) == 0: return nil result = vals[-1] for i in range(len(vals)-2, -1, -1): v = vals[i] if v is not nil: check_type(v, scheme_pairp, i, "append") r = p = Pair(v.first, result) v = v.second while scheme_pairp(v): p.second = Pair(v.first, result) p = p.second v = v.second result = r return result @primitive("string?") def scheme_stringp(x): return isinstance(x, str) and x.startswith('"') @primitive("symbol?") def scheme_symbolp(x): return isinstance(x, str) and not scheme_stringp(x) @primitive("number?") def scheme_numberp(x): return isinstance(x, int) or isinstance(x, float) @primitive("integer?") def scheme_integerp(x): return isinstance(x, int) or (scheme_numberp(x) and round(x) == x) def _check_nums(*vals): """Check that all arguments in VALS are numbers.""" for i, v in enumerate(vals): if not scheme_numberp(v): msg = "operand {0} ({1}) is not a number" raise SchemeError(msg.format(i, v)) def _arith(fn, init, vals): """Perform the fn fneration on the number values of VALS, with INIT as the value when VALS is empty. Returns the result as a Scheme value.""" _check_nums(*vals) s = init for val in vals: s = fn(s, val) if round(s) == s: s = round(s) return s @primitive("+") def scheme_add(*vals): return _arith(operator.add, 0, vals) @primitive("-") def scheme_sub(val0, *vals): if len(vals) == 0: return -val0 return _arith(operator.sub, val0, vals) @primitive("*") def scheme_mul(*vals): return _arith(operator.mul, 1, vals) @primitive("/") def scheme_div(val0, val1): try: return _arith(operator.truediv, val0, [val1]) except ZeroDivisionError as err: raise SchemeError(err) @primitive("quotient") def scheme_quo(val0, val1): try: return _arith(operator.floordiv, val0, [val1]) except ZeroDivisionError as err: raise SchemeError(err) @primitive("modulo", "remainder") def scheme_modulo(val0, val1): try: return _arith(operator.mod, val0, [val1]) except ZeroDivisionError as err: raise SchemeError(err) @primitive("floor") def scheme_floor(val): _check_nums(val) return math.floor(val) @primitive("ceil") def scheme_ceil(val): _check_nums(val) return math.ceil(val) def _numcomp(op, x, y): _check_nums(x, y) return op(x, y) @primitive("=") def scheme_eq(x, y): return _numcomp(operator.eq, x, y) @primitive("<") def scheme_lt(x, y): return _numcomp(operator.lt, x, y) @primitive(">") def scheme_gt(x, y): return _numcomp(operator.gt, x, y) @primitive("<=") def scheme_le(x, y): return _numcomp(operator.le, x, y) @primitive(">=") def scheme_ge(x, y): return _numcomp(operator.ge, x, y) @primitive("even?") def scheme_evenp(x): _check_nums(x) return x % 2 == 0 @primitive("odd?") def scheme_oddp(x): _check_nums(x) return x % 2 == 1 @primitive("zero?") def scheme_zerop(x): _check_nums(x) return x == 0 ## ## Other operations ## @primitive("atom?") def scheme_atomp(x): if scheme_booleanp(x): return True if scheme_numberp(x): return True if scheme_symbolp(x): return True if scheme_nullp(x): return True return False @primitive("display") def scheme_display(val): if scheme_stringp(val): val = eval(val) print(str(val), end="") return okay @primitive("print") def scheme_print(val): print(str(val)) return okay @primitive("newline") def scheme_newline(): print() sys.stdout.flush() return okay @primitive("error") def scheme_error(msg = None): msg = "" if msg is None else str(msg) raise SchemeError(msg) @primitive("exit") def scheme_exit(): raise EOFError ## ## Turtle graphics (non-standard) ## _turtle_screen_on = False def turtle_screen_on(): return _turtle_screen_on def _tscheme_prep(): global _turtle_screen_on if not _turtle_screen_on: _turtle_screen_on = True turtle.title("Scheme Turtles") turtle.mode('logo') @primitive("forward", "fd") def tscheme_forward(n): """Move the turtle forward a distance N units on the current heading.""" _check_nums(n) _tscheme_prep() turtle.forward(n) return okay @primitive("backward", "back", "bk") def tscheme_backward(n): """Move the turtle backward a distance N units on the current heading, without changing direction.""" _check_nums(n) _tscheme_prep() turtle.backward(n) return okay @primitive("left", "lt") def tscheme_left(n): """Rotate the turtle's heading N degrees counterclockwise.""" _check_nums(n) _tscheme_prep() turtle.left(n) return okay @primitive("right", "rt") def tscheme_right(n): """Rotate the turtle's heading N degrees clockwise.""" _check_nums(n) _tscheme_prep() turtle.right(n) return okay @primitive("circle") def tscheme_circle(r, extent = None): """Draw a circle with center R units to the left of the turtle (i.e., right if N is negative. If EXTENT is not None, then draw EXTENT degrees of the circle only. Draws in the clockwise direction if R is negative, and otherwise counterclockwise, leaving the turtle facing along the arc at its end.""" if extent is None: _check_nums(r) else: _check_nums(r, extent) _tscheme_prep() turtle.circle(r, extent and extent) return okay @primitive("setposition", "setpos", "goto") def tscheme_setposition(x, y): """Set turtle's position to (X,Y), heading unchanged.""" _check_nums(x, y) _tscheme_prep() turtle.setposition(x, y) return okay @primitive("setheading", "seth") def tscheme_setheading(h): """Set the turtle's heading H degrees clockwise from north (up).""" _check_nums(h) _tscheme_prep() turtle.setheading(h) return okay @primitive("penup", "pu") def tscheme_penup(): """Raise the pen, so that the turtle does not draw.""" _tscheme_prep() turtle.penup() return okay @primitive("pendown", "pd") def tscheme_pendown(): """Lower the pen, so that the turtle starts drawing.""" _tscheme_prep() turtle.pendown() return okay @primitive("showturtle", "st") def tscheme_showturtle(): """Make turtle visible.""" _tscheme_prep() turtle.showturtle() return okay @primitive("hideturtle", "ht") def tscheme_hideturtle(): """Make turtle visible.""" _tscheme_prep() turtle.hideturtle() return okay @primitive("clear") def tscheme_clear(): """Clear the drawing, leaving the turtle unchanged.""" _tscheme_prep() turtle.clear() return okay @primitive("color") def tscheme_color(c): """Set the color to C, a string such as '"red"' or '"#ffc0c0"' (representing hexadecimal red, green, and blue values.""" _tscheme_prep() check_type(c, scheme_stringp, 0, "color") turtle.color(eval(c)) return okay @primitive("begin_fill") def tscheme_begin_fill(): """Start a sequence of moves that outline a shape to be filled.""" _tscheme_prep() turtle.begin_fill() return okay @primitive("end_fill") def tscheme_end_fill(): """Fill in shape drawn since last begin_fill.""" _tscheme_prep() turtle.end_fill() return okay @primitive("exitonclick") def tscheme_exitonclick(): """Wait for a click on the turtle window, and then close it.""" global _turtle_screen_on if _turtle_screen_on: print("Close or click on turtle window to complete exit") turtle.exitonclick() _turtle_screen_on = False return okay @primitive("speed") def tscheme_speed(s): """Set the turtle's animation speed as indicated by S (an integer in 0-10, with 0 indicating no animation (lines draw instantly), and 1-10 indicating faster and faster movement.""" check_type(s, scheme_integerp, 0, "speed") _tscheme_prep() turtle.speed(s) return okay