fx92-interpreter/fx92/interpreter.py

240 lines
6.9 KiB
Python

# fx-92 Scientifique Collège+ language interpreter: AST interpreter
from fx92.ast import N, Node
from decimal import Decimal
from fx92.util import decfun, degree2decimal, decimal2degree, decimal_repr
import math
class Context:
"""
An execution context, tied to a window, in which a script can be run.
Provides both variables, state management, and interpretation.
"""
def __init__(self, window):
"""
Create a new execution context with a clean variable state and a
window.
"""
self.vars = { name: 0 for name in "MABCDEF" }
self.w = window
self.x = Decimal(0)
self.y = Decimal(0)
self.theta = Decimal(0)
self.pen = False
# TODO: Also specify the style!
self.w.clear(self.w.WHITE)
self.w.update()
def settheta(self, newtheta):
self.theta = newtheta % 360
def run(self, n):
"""
Execute a full program tree. For expression nodes, returns a value; for
statements returns None. Also draws as a side-effect.
"""
# Core nodes
if n.type == N.PROGRAM:
for stmt in n.args:
self.run(stmt)
# Basic statements
elif n.type == N.FORWARD:
dist, = self.runs(n)
if self.pen:
self.w.linef(self.x, self.y, self.theta, dist, self.w.BLACK)
self.w.update()
dx = dist * degree2decimal(math.cos)(self.theta)
dy = dist * degree2decimal(math.sin)(self.theta)
self.x = round(self.x + dx, 9)
self.y = round(self.y + dy, 9)
elif n.type == N.ROTATE:
angle, = self.runs(n)
self.settheta(self.theta + angle)
elif n.type == N.ORIENT:
angle, = self.runs(n)
self.settheta(angle)
elif n.type == N.GOTO:
x, y = self.runs(n)
if self.pen:
self.w.line(self.x, self.y, x, y, self.w.BLACK)
self.w.update()
self.x = x
self.y = y
elif n.type == N.PENDOWN:
self.pen = True
elif n.type == N.PENUP:
self.pen = False
elif n.type == N.ASSIGN:
val = self.run(n.args[0])
var = n.args[1]
if var == "x":
self.x = val
elif var == "y":
self.y = val
elif var == "theta":
self.theta = val
else:
self.vars[var] = val
elif n.type == N.INPUT:
# TODO: Input stmt should call into the lexer+parser (urg)
raise Exception("Input statement not supported yet x_x")
elif n.type == N.PRINT:
x, = self.runs(n)
s = decimal_repr(x)
print(s)
diff = abs(Decimal(s) - x)
approx = Decimal(1e-9) * abs(x)
assert diff <= approx, \
"Incorrect representation of {} as {}".format(x,s)
elif n.type == N.STYLE:
# TODO: Support style
raise Exception("Style statement not supported yet x_x")
elif n.type == N.WAIT:
print("---- pause")
print("x={} y={} theta={}".format(self.x, self.y, self.theta))
print("A={} B={}".format(self.vars['A'], self.vars['B']))
print("C={} D={}".format(self.vars['C'], self.vars['D']))
print("E={} F={}".format(self.vars['E'], self.vars['F']))
print("M={}".format(self.vars['M']))
self.w.pause()
# Flow control
elif n.type == N.REPEAT:
count = self.run(n.args[0])
count = int(count)
for i in range(count):
self.run(n.args[1])
elif n.type == N.UNTIL:
while not self.run(n.args[0]):
self.run(n.args[1])
elif n.type == N.IF:
if self.run(n.args[0]):
self.run(n.args[1])
elif n.args[2] is not None:
self.run(n.args[2])
# Expression
elif n.type == N.ADD:
return sum(self.runs(n))
elif n.type == N.SUB:
x, y = self.runs(n)
return x - y
elif n.type == N.MUL:
prod = Decimal(1)
for x in self.runs(n):
prod *= x
return prod
elif n.type == N.DIV:
x, y = self.runs(n)
return x / y
elif n.type == N.MINUS:
x, = self.runs(n)
return -x
elif n.type == N.EXP:
x, y = self.runs(n)
return x ** y
elif n.type == N.VAR:
var = n.args[0]
if var == "x":
return self.x
if var == "y":
return self.y
if var == "theta":
return self.theta
return self.vars[var]
elif n.type == N.CONST:
return n.value
elif n.type == N.FUN:
args = [self.run(arg) for arg in n.args[1]]
f = {
"Abs": abs,
"Rnd": None,
"sinh": decfun(math.sinh),
"cosh": decfun(math.cosh),
"tanh": decfun(math.tanh),
"asinh": decfun(math.asinh),
"acosh": decfun(math.acosh),
"atanh": decfun(math.atanh),
"sqrt": decfun(math.sqrt),
"log": decfun(math.log),
"cbrt": lambda x: x ** (1/3),
"sin": degree2decimal(math.sin),
"cos": degree2decimal(math.cos),
"tan": degree2decimal(math.tan),
"asin": decimal2degree(math.asin),
"acos": decimal2degree(math.acos),
"atan": decimal2degree(math.atan),
"log10": None,
"Ent": lambda x: Decimal(int(x)),
"EntEx": lambda x: Decimal(int(x)) - (x < 0),
"RanInt": None,
"GCD": lambda x, y: Decimal(math.gcd(int(x), int(y))),
"LCM": lambda x, y: x*y/Decimal(math.gcd(int(x),int(y))),
"Arond": None,
}[n.args[0]]
if f is None:
raise Exception(
"Function {} not there yet x_x".format(n.args[0]))
return f(*args)
elif n.type == N.REL:
left = self.run(n.args[0])
right = self.run(n.args[2])
return {
">": left > right,
"<": left < right,
">=": left >= right,
"<=": left <= right,
"=": left == right,
"!=": left != right
}[n.args[1]]
else:
t = N(n.type).name
raise Exception("Unhandled node of type {}".format(t))
def runs(self, n):
"""Evaluate all the arguments of a node."""
return [self.run(e) for e in n.args]