240 lines
6.9 KiB
Python
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]
|