aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormryouse2022-05-19 03:24:44 +0000
committermryouse2022-05-19 03:24:44 +0000
commit887402efda7584835b1c6242bcde2a2f6059e565 (patch)
tree50d5567d85b75829342a6289609b7afe814479b9
parent9035e50ee06a4f3b452ad80f6dc59b1941a51678 (diff)
interpreter with some builtins
-rw-r--r--interpreter.py162
-rw-r--r--neb.py42
-rw-r--r--repl.py3
3 files changed, 169 insertions, 38 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
diff --git a/neb.py b/neb.py
index 5e46049..ad645a5 100644
--- a/neb.py
+++ b/neb.py
@@ -1,7 +1,6 @@
from lexer import lex
from parser import parse
-from runner import evaluate
-from std import _get_debug
+from interpreter import interpret
import sys
@@ -15,44 +14,11 @@ def main():
data = fil.read()
try:
- lexed = lex(data, [])
- '''
- if _get_debug():
- acc = " ".join([f"{l}" for l in lexed])
- print(f" - LEX: {acc}")
- '''
- parsed = parse(lexed, [])
- '''
- if _get_debug():
- acc = " ".join([f"{p}" for p in parsed])
- print(f" - PARSE: {acc}")
- '''
- ev = evaluate(parsed, [])
+ lexed = lex(data)
+ parsed = parse(lexed)
+ ev = interpret(parsed)
except Exception as e:
print(f"panic! {e}")
- '''
- print("### neb :)(:")
- print("version: < 0")
- idx = 1
- while True:
- inp = input(f"#{idx}> ")
- if len(inp.strip()) == 0:
- continue
- try:
- lexed = lex(inp, [])
- if _get_debug():
- acc = " ".join([f"{l}" for l in lexed])
- print(f" - LEX: {acc}")
- parsed = parse(lexed, [])
- if _get_debug():
- acc = " ".join([f"{p}" for p in parsed])
- print(f" - PARSE: {acc}")
- ev = evaluate(parsed, [])
- print(f"=> {ev}")
- idx += 1
- except Exception as e:
- print(f"panic! {e}")
- '''
if __name__ == "__main__":
diff --git a/repl.py b/repl.py
index d22c8b8..831d57d 100644
--- a/repl.py
+++ b/repl.py
@@ -1,5 +1,6 @@
from lexer import lex
from parser import parse
+from interpreter import interpret
def _get_debug():
return True
@@ -21,6 +22,8 @@ def main():
if _get_debug():
acc = " ".join([f"{p}" for p in parsed])
print(f" - PARSE: {acc}")
+ inter = interpret(parsed)
+ print(f"=> {inter}")
idx += 1
except Exception as e:
print(f"panic! {e}")