forked from zerone0x/cs61a
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscalc.py
144 lines (128 loc) · 3.9 KB
/
scalc.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
"""An interpreter for the Scheme-Syntax Calculator Language
An interpreter for a calculator language that uses prefix-order call syntax.
Operator expressions must be operator symbols. Operand expressions are
separated by spaces.
Examples:
> (* 1 2 3)
6
> (+)
0
> (+ 2 (/ 4 8))
2.5
> (+ 2 2) (* 3 3)
4
9
> (+ 1
(- 23)
(* 4 2.5))
-12
> )
SyntaxError: unexpected token: )
> 2.3.4
ValueError: invalid numeral: 2.3.4
> +
TypeError: + is not a number or call expression
> (/ 5)
TypeError: / requires exactly 2 arguments
> (/ 1 0)
ZeroDivisionError: division by zero
"""
from ucb import trace, main, interact
from operator import add, sub, mul, truediv
from scheme_reader import Pair, nil, scheme_read, buffer_input
# Eval & Apply
def calc_eval(exp):
"""Evaluate a Calculator expression.
>>> calc_eval(as_scheme_list('+', 2, as_scheme_list('*', 4, 6)))
26
>>> calc_eval(as_scheme_list('+', 2, as_scheme_list('/', 40, 5)))
10
"""
if type(exp) in (int, float):
return simplify(exp)
elif isinstance(exp, Pair):
arguments = exp.second.map(calc_eval)
return simplify(calc_apply(exp.first, arguments))
else:
raise TypeError(str(exp) + ' is not a number or call expression')
def calc_apply(operator, args):
"""Apply the named operator to a list of args.
>>> calc_apply('+', as_scheme_list(1, 2, 3))
6
>>> calc_apply('-', as_scheme_list(10, 1, 2, 3))
4
>>> calc_apply('-', as_scheme_list(10))
-10
>>> calc_apply('*', nil)
1
>>> calc_apply('*', as_scheme_list(1, 2, 3, 4, 5))
120
>>> calc_apply('/', as_scheme_list(40, 5))
8.0
>>> calc_apply('/', as_scheme_list(10))
0.1
"""
if not isinstance(operator, str):
raise TypeError(str(operator) + ' is not a symbol')
if operator == '+':
return reduce(add, args, 0)
elif operator == '-':
if len(args) == 0:
raise TypeError(operator + ' requires at least 1 argument')
elif len(args) == 1:
return -args.first
else:
return reduce(sub, args.second, args.first)
elif operator == '*':
return reduce(mul, args, 1)
elif operator == '/':
if len(args) == 0:
raise TypeError(operator + ' requires at least 1 argument')
elif len(args) == 1:
return 1/args.first
else:
return reduce(truediv, args.second, args.first)
else:
raise TypeError(operator + ' is an unknown operator')
def simplify(value):
"""Return an int if value is an integer, or value otherwise.
>>> simplify(8.0)
8
>>> simplify(2.3)
2.3
>>> simplify('+')
'+'
"""
if isinstance(value, float) and int(value) == value:
return int(value)
return value
def reduce(fn, scheme_list, start):
"""Reduce a recursive list of Pairs using fn and a start value.
>>> reduce(add, as_scheme_list(1, 2, 3), 0)
6
"""
if scheme_list is nil:
return start
return reduce(fn, scheme_list.second, fn(start, scheme_list.first))
def as_scheme_list(*args):
"""Return a recursive list of Pairs that contains the elements of args.
>>> as_scheme_list(1, 2, 3)
Pair(1, Pair(2, Pair(3, nil)))
"""
if len(args) == 0:
return nil
return Pair(args[0], as_scheme_list(*args[1:]))
@main
def read_eval_print_loop():
"""Run a read-eval-print loop for Calculator."""
while True:
try:
src = buffer_input()
while src.more_on_line:
expression = scheme_read(src)
print(calc_eval(expression))
except (SyntaxError, TypeError, ValueError, ZeroDivisionError) as err:
print(type(err).__name__ + ':', err)
except (KeyboardInterrupt, EOFError): # <Control>-D, etc.
print('Calculation completed.')
return