aboutsummaryrefslogtreecommitdiff
path: root/interpreter.py
blob: fd327f904039dfc75a2a950551476f45a861fd9b (plain)
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
from parser import Expr


def interpret(exprs):
    ret = None
    for expr in exprs:
        ret = evaluate(expr)
    return ret

def evaluate(expr):
    if isinstance(expr, Expr.Literal):
        return expr.value
    elif expr.symbol.name == "not":
        return interpretNot(expr)
    elif expr.symbol.name in ("*", "/"):
        return interpretFactor(expr)
    elif expr.symbol.name in ("-", "+"):
        return interpretTerm(expr)
    elif expr.symbol.name in (">", ">=", "<", "<="):
        return interpretComparison(expr)
    elif expr.symbol.name == "eq?":
        return interpretEq(expr)
    elif expr.symbol.name == "and":
        return interpretAnd(expr)
    elif expr.symbol.name == "or":
        return interpretOr(expr)

    # flow control
    elif expr.symbol.name == "if":
        return interpretIf(expr)

    # io
    elif expr.symbol.name == "print":
        return interpretPrint(expr)

    else:
        raise Exception(f"unable to evaluate: {expr}")


def interpretOr(expr):
    # or returns true for the first expression that returns true
    if len(expr.args) < 2:
        raise Exception("'or' has at least two operands")
    for arg in expr.args:
        ev = evaluate(arg)
        if ev not in (True, False):
            raise Exception("'or' needs boolean arguments")
        if ev == True:
            return True
    return False

def interpretAnd(expr):
    # and returns false for the first expression that returns false
    if len(expr.args) < 2:
        raise Exception("'and' has at least two operands")
    for arg in expr.args:
        ev = evaluate(arg)
        if ev not in (True, False):
            raise Exception("'and' needs boolean arguments")
        if ev == False:
            return False
    return True

def interpretEq(expr):
    # equal
    if len(expr.args) != 2:
        raise Exception("'eq?' needs two arguments")
    first = evaluate(expr.args[0])
    second = evaluate(expr.args[1])
    return first == second
            
def interpretComparison(expr):
    if len(expr.args) != 2:
        raise Exception("comparisons have two operands")
    left = evaluate(expr.args[0])
    if type(left) not in (int, float):
        raise Exception("'left' must be a number")
    right = evaluate(expr.args[1])
    if type(right) not in (int, float):
        raise Exception("'right' must be a number")

    if expr.symbol.name == ">":
        return left > right
    elif expr.symbol.name == ">=":
        return left >= right
    elif expr.symbol.name == "<":
        return left < right
    elif expr.symbol.name == "<=":
        return left <= right

def interpretTerm(expr):
    if len(expr.args) < 1:
        raise Exception("term has at least one operand")
    res = 0
    for arg in expr.args:
        ev = evaluate(arg)
        if type(ev) not in (int, float):
            raise Exception("term must be a number")
        if expr.symbol.name == "+":
            res += ev
        elif expr.symbol.name == "-":
            res -= ev
    return res

def interpretFactor(expr):
    if expr.symbol.name == "/":
        if len(expr.args) != 2:
            raise Exception("'/' requires two operands")
        num = evaluate(expr.args[0])
        if type(num) not in (int, float):
            raise Exception("numerator must be a number")
        denom = evaluate(expr.args[1])
        if type(denom) not in (int, float):
            raise Exception("denominator must be a number")
        return num / denom  # TODO floats and ints
    else:
        if len(expr.args) < 2:
            raise Exception("'*' requires at least two operands")
        first = evaluate(expr.args[0])
        if type(first) not in (int, float):
            raise Exception("'*' operand must be a number")
        res = first
        for arg in expr.args[1:]:
            tmp = evaluate(arg)
            if type(tmp) not in (int, float):
                raise Exception("'*' operand must be a number")
            res = res * tmp
        return res

def interpretNot(expr):
    if len(expr.args) != 1:
        raise Exception("'not' takes one operand")
    res = evaluate(expr.args[0])
    if res not in (True, False):
        raise Exception("'not' only works on booleans")
    return not res

def interpretIf(expr):
    # if cond t-branch [f-branch]
    if len(expr.args) not in (2, 3):
        raise Exception("too many or too few operands")
    cond = evaluate(expr.args[0])
    if cond not in (True, False):
        raise Exception("'if' condition must be boolean")
    if cond:
        return evaluate(expr.args[1])
    elif len(expr.args) == 3:
        return evaluate(expr.args[2])
    return None  # this shouldn't be reached

def interpretPrint(expr):
    if len(expr.args) == 0:
        print()
    elif len(expr.args) == 1:
        ev = evaluate(expr.args[0])
        if not isinstance(ev, str):
            raise Exception("can only 'print' strings")
        print(ev)
    else:
        raise Exception("'print' takes zero or one argument")

    return None  # print returns nothing