aboutsummaryrefslogtreecommitdiff
path: root/interpreter.py
diff options
context:
space:
mode:
authormryouse2022-05-19 03:24:44 +0000
committermryouse2022-05-19 03:24:44 +0000
commit887402efda7584835b1c6242bcde2a2f6059e565 (patch)
tree50d5567d85b75829342a6289609b7afe814479b9 /interpreter.py
parent9035e50ee06a4f3b452ad80f6dc59b1941a51678 (diff)
interpreter with some builtins
Diffstat (limited to 'interpreter.py')
-rw-r--r--interpreter.py162
1 files changed, 162 insertions, 0 deletions
diff --git a/interpreter.py b/interpreter.py
new file mode 100644
index 0000000..fd327f9
--- /dev/null
+++ b/interpreter.py
@@ -0,0 +1,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