-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathscheme_primitives.py
1032 lines (819 loc) · 26.6 KB
/
scheme_primitives.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""This module implements the primitives of the Scheme language."""
import math
import operator
import sys
import numbers
import re
try:
import turtle
except:
print("warning: could not import the turtle module.", file=sys.stderr)
#############################
# Errors and Error Checking #
#############################
class SchemeError(Exception):
"""Exception indicating an error in a Scheme program."""
def bad_type(val, k, name):
"""Raise a SchemeError using "argument K of NAME" to describe the
offending value (VAL)."""
msg = "argument {0} of {1} has wrong type ({2})"
raise SchemeError(msg.format(k, name, type(val).__name__))
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):
bad_type(val, k, name)
return val
#################
# Scheme Values #
#################
# Every value used in the interpreter to represent a Scheme value "is a"
# SchemeValue. Thus, if x is such a value, isinstance(x, SchemeValue).
# In proper object-oriented fashion, however, you will only need to use
# this test in 'assert' statements that catch bugs in your interpreter.
# The SchemeValue class has methods for testing for Scheme integers,
# procedures, etc. The SchemeValue class documents these methods by
# giving default 'def's for all of them. The various subclasses of
# SchemeValue, which stand for Scheme integers, Scheme pairs, etc., override
# these methods with appropriate methods of their own.
# As an example, consider the method pairp (returns a true Scheme value iff
# its argument is a pair). The default definition (in SchemeValue) returns
# scheme_false (which represents #f). Most subtypes of SchemeValue simply
# inherit this definition, and so also return #f. The type Pair, however,
# overrides pairp to return scheme_true (which represents #t). As a result,
# No 'if' statements are necessary to check for pairs.
# In other examples, such as the length method, the default implementation
# causes an error, since length is defined only for proper lists (so it
# is overridden in the classes Pair and Nil).
class SchemeValue:
"""The parent class of all Scheme values manipulated by the interpreter.
The methods here give default implementations, and are overridden in the
subclasses of SchemeValue."""
def __bool__(self):
"""True if I am supposed to count as a "true value" in Python. This
is the method used by Python's conditionals (if, and, or, not, while)
to determine whether some value is to be treated as meaning 'true'.
As a result, any SchemeValue that Scheme considers to represent true
also means true to Python. It is therefore unnecessary in Python to
write things like 'if EXPRESSION is not scheme_false: ...'"""
return True
def print_repr(self):
"""The string printed for me by the Scheme 'print' procedure."""
return str(self)
# The following methods return SchemeValues. Those ending in 'p' (for
# 'predicate') return scheme_false to mean 'false', and some other
# SchemeValue (often scheme_true) to mean 'true'.
def booleanp(self):
return scheme_false
def notp(self):
return scbool(not self)
def eqp(self, y):
return scbool(self is y)
def eqvp(self, y):
"""True iff I am equivalent to Y (same object, same numeric or boolean
value."""
return scbool(self is y)
def equalp(self, y):
"""True iff I am equal in value to Y."""
return scbool(self == y)
def atomp(self):
return scheme_true
def pairp(self):
return scheme_false
def nullp(self):
return scheme_false
def listp(self):
return scheme_false
def length(self):
bad_type(self, 0, "length")
def neg(self):
"""Unary negation (as in -x), returning a SchemeValue."""
bad_type(self, 0, "sub")
def quo(self, y):
bad_type(self, 0, "quotient")
def modulo(self, y):
bad_type(self, 0, "modulo")
def rem(self, y):
bad_type(self, 0, "remainder")
def floor(self):
bad_type(self, 0, "floor")
def ceil(self):
bad_type(self, 0, "ceil")
def eq(self, y):
bad_type(self, 0, "=")
def ltp(self, y):
bad_type(self, 0, "<")
def gtp(self, y):
bad_type(self, 0, ">")
def lep(self, y):
bad_type(self, 0, "<=")
def gep(self, y):
bad_type(self, 0, ">=")
def evenp(self):
bad_type(self, 0, "even?")
def oddp(self):
bad_type(self, 0, "odd?")
def zerop(self):
bad_type(self, 0, "zero?")
def cons(self, y):
return Pair(self, y)
def append(self, y):
bad_type(self, 0, "append")
def car(self):
bad_type(self, 0, "car")
def cdr(self):
bad_type(self, 0, "cdr")
def stringp(self):
return scheme_false
def symbolp(self):
return scheme_false
def numberp(self):
return scheme_false
def integerp(self):
return scheme_false
def evaluate_arguments(self, arg_list, env):
"""Evaluate the expressions in ARG_LIST in ENV to produce
arguments for this procedure. Returns an iterable of the results."""
msg = "attempt to call something of non-function type ({0})"
raise SchemeError(msg.format(type(self).__name__))
def apply(self, args, env):
"""Apply me to a Scheme list of ARGS in environment ENV. Returns
either
A. A tuple (val, None), where val is the value this procedure
returns when given ARGS and called in environment ENV, or
B. A tuple (expr1, env1), where evaluating the Scheme
expression expr1 in environment env1 will yield the same result
and the same effects as evaluating ARGS in ENV.
This method is overridden in procedure types. THis default
implementation causes an error.
NOTE: Until you complete extra-credit question 22, this method
will always return (val, None)."""
msg = "attempt to call something of non-function type ({0})"
raise SchemeError(msg.format(type(self).__name__))
def get_actual_value(self):
"""Returns any desired transformation of a value newly fetched
from a symbol before it is used. The default implementation
is simply the identity. [This method is useful for the call-by-name
extension in extra-credit Problem 23.]"""
return self
def scheme_coerce(x):
"""Returns the Scheme value corresponding to X. Converts numbers to
SchemeNumbers and strings to interned symbols. SchemeValues are unchanged.
Other values raise a TypeError."""
if isinstance(x, SchemeValue):
return x
elif isinstance(x, numbers.Number):
return scnum(x)
elif isinstance(x, str):
return intern(x)
else:
raise TypeError("cannot convert type {} to SchemeValue".format(type(x)))
########
# okay #
########
class okay(SchemeValue):
"""Signifies an undefined value."""
def __repr__(self):
return "okay"
okay = okay() # Assignment hides the okay class; there is only one instance
############
# Booleans #
############
class scheme_true(SchemeValue):
def booleanp(self):
return scheme_true
def __repr__(self):
return 'scheme_true'
def __str__(self):
return "#t"
class scheme_false(SchemeValue):
def __bool__(self):
return False
def booleanp(self):
return scheme_true
def __repr__(self):
return 'scheme_false'
def __str__(self):
return "#f"
# scheme_true and scheme_false hide the classes scheme_true and scheme_false.
# Thus they are the only instances of those classes.
scheme_true = scheme_true()
scheme_false = scheme_false()
def scbool(x):
"""The Scheme boolean value (#t or #f) that corresponds to the Python value
X. True Python values yield scheme_true, and false values yield
scheme_false."""
return scheme_true if x else scheme_false
###########
# Numbers #
###########
def _check_num(x, name):
"""If X is a number, return it. Otherwise, indicate a type error in
argument 1 of operation NAME."""
return check_type(x, scheme_numberp, 1, name)
class SchemeNumber(SchemeValue):
"""The parent class of all Scheme numeric types."""
def numberp(self):
return scheme_true
def __repr__(self):
return "scnum({})".format(self)
# Tests
def eq(self, y):
return scbool(self == _check_num(y, "="))
def ltp(self, y):
return scbool(self < _check_num(y, "<"))
def gtp(self, y):
return scbool(self > _check_num(y, ">"))
def lep(self, y):
return scbool(self <= _check_num(y, "<="))
def gep(self, y):
return scbool(self >= _check_num(y, ">="))
def zerop(self):
return scbool(self == 0)
# Types SchemeInt and SchemeFloat illustrate *multiple inheritance*.
# That is, they inherits methods from both SchemeValue and int or float.
# Thus, SchemeInts print as integers, and respond to all the
# same operators (+, -, etc.) as integers. To convert an ordinary Python
# integer, x, into a SchemeInt, use SchemeInt(x); likewise for SchemeFloat.
class SchemeInt(SchemeNumber, int):
def integerp(self):
return scheme_true
def neg(self):
return SchemeInt(-self)
# Scheme quotient rounds toward 0; Pythons rounds toward negative infinity.
def quo(self, y):
check_type(y, scheme_integerp, 1, "quotient")
try:
if (y < 0) != (self < 0):
return SchemeInt(- (abs(self) // abs(y)))
else:
return SchemeInt(self // y)
except ZeroDivisionError as err:
raise SchemeError(err)
def modulo(self, y):
check_type(y, scheme_integerp, 1, "modulo")
try:
return SchemeInt(self % y)
except ZeroDivisionError as err:
raise SchemeError(err)
def rem(self, y):
q = self.quo(y)
return SchemeInt(self - q * y)
def floor(self):
return self
def ceil(self):
return self
def eqvp(self, y):
return scbool(self == y)
def evenp(self):
return scbool(self % 2 == 0)
def oddp(self):
return scbool(self % 2 == 1)
class SchemeFloat(SchemeNumber, float):
def neg(self):
return SchemeFloat(-self)
def floor(self):
return SchemeInt(int(math.floor(self)))
def ceil(self):
return SchemeInt(int(math.ceil(self)))
def eqvp(self, y):
return scbool(self == y)
# Shorthand for numeric types.
scint = SchemeInt
scfloat = SchemeFloat
def scnum(num):
r = round(num)
if r == num:
return scint(r)
else:
return scfloat(num)
###########
# Symbols #
###########
class SchemeSymbol(SchemeValue):
"""Represents a Scheme symbol. Normally, one creates new symbols with
the static intern factory method. Each distinct symbol name is associated
with exactly one symbol. Symbol equality is simply object identity, so
that SchemeSymbols are hashable (so may be used as dictionary keys and
in sets)."""
def __init__(self, name):
"""A new symbol whose name is NAME. Normally, you should create new
symbols with intern (below). This function is for internal use."""
assert type(name) is str, \
"invalid type of symbol name: {}".format(type(name))
self.name = name
def symbolp(self):
return scheme_true
def __repr__(self):
if self is _all_symbols.get(self.name, None):
return "intern('{}')".format(self.name)
else:
return "SchemeSymbol('{}')".format(self.name)
def __str__(self):
return self.name
# The symbols corresponding to each unique symbol name.
_all_symbols = {}
def intern(name):
"""If NAME is a string, the canonical symbol named NAME. If NAME is a
symbol, a canonical symbol with that name."""
if isinstance(name, SchemeSymbol):
if str(name) in _all_symbols:
return _all_symbols[str(name)]
else:
_all_symbols[str(name)] = name
return name
if name in _all_symbols:
return _all_symbols[name]
else:
sym = SchemeSymbol(name)
_all_symbols[name] = sym
return sym
###########
# Strings #
###########
class SchemeStr(SchemeValue, str):
def stringp(self):
return scheme_true
def __repr__(self):
return "scstr({!r})".format(str(self))
_escape = re.compile(r'''"|\.''')
def print_repr(self):
s = str.__repr__(self)
if s[0] == "'":
s = s[1:-1]
s = SchemeStr._escape.sub(lambda x: (r'\"' if x == '"'
else "'" if x == r"\'"
else x), s)
s = '"' + s + '"'
return s
scstr = SchemeStr
##########################
# Pairs and Scheme lists #
##########################
class Pair(SchemeValue):
"""A pair has two instance attributes: first and second. For a Pair to be
a well-formed list, second is either a well-formed list or nil. Some
methods only apply to well-formed lists. As a convenience, the constructor
for Pair converts arguments that are numbers into SchemeNumbers and
arguments that are strings into SchemeStrs (NOT symbols, which the caller
must convert explictly, generally using intern). Likewise, the __repr__
function will abbreviate scnum(x) to x and scstr(x) to x.
>>> s = Pair(1, Pair(2, nil))
>>> s
Pair(1, Pair(2, nil))
>>> print(s)
(1 2)
>>> len(s)
2
>>> s[1]
scnum(2)
>>> print(s.map(lambda x: x+4))
(5 6)
"""
def __init__(self, first, second):
"""The pair (FIRST . SECOND). As a convenience, FIRST and SECOND
are coerced from Python numbers to SchemeNumbers or from Python
strings to SchemeStrs."""
first = scheme_coerce(first)
second = scheme_coerce(second)
assert isinstance(first, SchemeValue) and \
isinstance(second, SchemeValue), \
"bad types to Pair: {}, {}".format(type(first), type(second))
self.first = first
self.second = second
def atomp(self):
return scheme_false
def pairp(self):
return scheme_true
def car(self):
return self.first
def cdr(self):
return self.second
def set_car(self, v):
self.first = v
return okay
def set_cdr(self, v):
self.second = v
return okay
def length(self):
return SchemeInt(self.__len__())
def equalp(self, y):
return scbool(self == y)
def listp(self):
return self._list_end().nullp()
def _list_end(self):
"""The 'end' of the list of which I am the head. This is nil if
I am a normal list. If I am a dot list---e.g. (1 2 . 3)---, then
the atom after the dot. If the list is circular, then the first
repeated pair in the list."""
p0 = self
p1 = self.second
while p1 is not p0 and p1.pairp():
p1 = p1.second
if p1 is p0 or not p1.pairp():
break
p0 = p0.second
return p1
def __repr__(self):
def uncoerce(x):
if scheme_numberp(x):
return x + 0
elif scheme_symbolp(x):
return str(x)
else:
return x
return "Pair({0!r}, {1!r})".format(uncoerce(self.first),
uncoerce(self.second))
def __str__(self):
s = "(" + str(self.first)
second = self.second
while second.pairp():
s += " " + str(second.car())
second = second.cdr()
if not second.nullp():
s += " . " + str(second)
return s + ")"
def __len__(self):
if not self._list_end().nullp():
raise SchemeError("length attempted on improper list")
n, second = 1, self.second
while second.pairp():
n += 1
second = second.second
return n
def __getitem__(self, k):
if k < 0:
raise IndexError("negative index into list")
y = self
for _ in range(k):
if y.second is nil:
raise IndexError("list index out of bounds")
elif not isinstance(y.second, Pair):
raise SchemeError("ill-formed list")
y = y.second
return y.first
def __eq__(self, p):
if not isinstance(p, Pair):
return False
return bool(self.first.equalp(p.first) and self.second.equalp(p.second))
def map(self, fn):
"""Return a Scheme list after mapping Python function FN to SELF."""
mapped = fn(self.first)
if self.second.nullp() or self.second.pairp():
return Pair(mapped, self.second.map(fn))
else:
raise SchemeError("ill-formed list")
def append(self, y):
if not self.listp():
raise SchemeError("attempt to append to improper list")
result = last = Pair(self.first, y)
p = self.second
while p is not nil:
last.second = Pair(p.first, y)
last = last.second
p = p.second
return result
class nil(SchemeValue):
"""The empty list"""
def listp(self):
return scheme_true
def nullp(self):
return scheme_true
def length(self):
return SchemeInt(0)
def __repr__(self):
return "nil"
def __str__(self):
return "()"
def __len__(self):
return 0
def __getitem__(self, k):
if k < 0:
raise IndexError("negative index into list")
raise IndexError("list index out of bounds")
def append(self, y):
return y
def map(self, fn):
return self
nil = nil() # Assignment hides the nil class; there is only one instance
########################
# Primitive Operations #
########################
_PRIMITIVES = []
def primitive(*names):
"""An annotation to record a Python function as a primitive procedure.
NAMES is the set of names to use in the Scheme global environment for
the function."""
def add(fn):
_PRIMITIVES.append((names, fn))
return fn
return add
def get_primitive_bindings():
"""The list of all (names, function) bindings recorded by @primitive
annotations."""
return _PRIMITIVES
@primitive("boolean?")
def scheme_booleanp(x):
"""True iff X is #t or #f."""
return x.booleanp()
@primitive("not")
def scheme_not(x):
"""True iff X is #f."""
return x.notp()
@primitive("eq?")
def scheme_eqp(x, y):
return x.eqp(y)
@primitive("eqv?")
def scheme_eqvp(x, y):
return x.eqvp(y)
@primitive("equal?")
def scheme_equalp(x, y):
return x.equalp(y)
@primitive("pair?")
def scheme_pairp(x):
return x.pairp()
@primitive("null?")
def scheme_nullp(x):
return x.nullp()
@primitive("list?")
def scheme_listp(x):
return x.listp()
@primitive("length")
def scheme_length(x):
return x.length()
@primitive("cons")
def scheme_cons(x, y):
return x.cons(y)
@primitive("car")
def scheme_car(x):
return x.car()
@primitive("cdr")
def scheme_cdr(x):
return x.cdr()
@primitive("list")
def scheme_list(*vals):
result = nil
for i in range(len(vals)-1, -1, -1):
result = scheme_cons(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):
result = vals[i].append(result)
return result
@primitive("string?")
def scheme_stringp(x):
return x.stringp()
@primitive("symbol?")
def scheme_symbolp(x):
return x.symbolp()
@primitive("number?")
def scheme_numberp(x):
return x.numberp()
@primitive("integer?")
def scheme_integerp(x):
return x.integerp()
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 operation 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:
return SchemeInt(round(s))
else:
return SchemeFloat(s)
@primitive("+")
def scheme_add(*vals):
return _arith(operator.add, 0, vals)
@primitive("-")
def scheme_sub(val0, *vals):
if len(vals) == 0:
return val0.neg()
return _arith(operator.sub, val0, vals)
@primitive("*")
def scheme_mul(*vals):
return _arith(operator.mul, 1, vals)
@primitive("/")
def scheme_div(*vals):
try:
if len(vals) == 1:
return _arith(operator.truediv, scnum(1), vals)
elif len(vals) == 0:
raise SchemeError("/ takes at least one argument")
return _arith(operator.truediv, vals[0], vals[1:])
except ZeroDivisionError as err:
raise SchemeError(err)
@primitive("quotient")
def scheme_quo(val0, val1):
return val0.quo(val1)
@primitive("modulo")
def scheme_modulo(val0, val1):
return val0.modulo(val1)
@primitive("remainder")
def scheme_rem(val0, val1):
return val0.rem(val1)
@primitive("floor")
def scheme_floor(val):
return val.floor()
@primitive("ceil")
def scheme_ceil(val):
return val.ceil()
@primitive("=")
def scheme_eq(x, y):
return x.eq(y)
@primitive("<")
def scheme_lt(x, y):
return x.ltp(y)
@primitive(">")
def scheme_gt(x, y):
return x.gtp(y)
@primitive("<=")
def scheme_le(x, y):
return x.lep(y)
@primitive(">=")
def scheme_ge(x, y):
return x.gep(y)
@primitive("even?")
def scheme_evenp(x):
return x.evenp()
@primitive("odd?")
def scheme_oddp(x):
return x.oddp()
@primitive("zero?")
def scheme_zerop(x):
return x.zerop()
##
## Other operations
##
# atom? is not standard.
@primitive("atom?")
def scheme_atomp(x):
return x.atomp()
@primitive("display")
def scheme_display(val):
print(str(val), end="")
return okay
@primitive("print")
def scheme_print(val):
print(val.print_repr())
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")