add a first interpreter
Note that there are still bugs and the output is really not what it should be. Will fix that with unit tests ASAP.
This commit is contained in:
parent
8269ab3f19
commit
cd2936c582
2
ast.py
2
ast.py
|
@ -62,7 +62,7 @@ class Node:
|
|||
|
||||
@property
|
||||
def value(self):
|
||||
"""Retrive the value of a CONST node."""
|
||||
"""Retrieve the value of a CONST node."""
|
||||
if self.type != N.CONST:
|
||||
raise Exception("Taking value of non-const node")
|
||||
return self.args[0]
|
||||
|
|
13
fx92.py
13
fx92.py
|
@ -5,12 +5,7 @@ import sys
|
|||
from parser import UrlParser
|
||||
from printer import print_ast
|
||||
from drawing import Window
|
||||
|
||||
#---
|
||||
# fx-92 SC+ interpreter
|
||||
#---
|
||||
|
||||
pass
|
||||
from interpreter import Context
|
||||
|
||||
#---
|
||||
# Main program
|
||||
|
@ -41,9 +36,9 @@ def main(argv):
|
|||
print_ast(ast, lang="ast")
|
||||
|
||||
with Window(width=96, height=32, scale=8) as w:
|
||||
w.clear(w.WHITE)
|
||||
w.line(1, 1, 20, 20, w.BLACK)
|
||||
w.update()
|
||||
ctx = Context(w)
|
||||
ctx.run(ast)
|
||||
|
||||
w.wait()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
# fx-92 Scientifique Collège+ language interpreter: AST interpreter
|
||||
|
||||
from ast import N, Node
|
||||
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 "MABCDEFxy" }
|
||||
self.w = window
|
||||
|
||||
self.x = float(0)
|
||||
self.y = float(0)
|
||||
self.pen = False
|
||||
|
||||
self.angle = float(0)
|
||||
self.sin = float(0)
|
||||
self.cos = float(1)
|
||||
|
||||
# TODO: Also specify the style!
|
||||
|
||||
self.w.clear(self.w.WHITE)
|
||||
self.w.update()
|
||||
|
||||
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)
|
||||
dx = self.cos * dist
|
||||
dy = self.sin * dist
|
||||
|
||||
if self.pen:
|
||||
coord = self.x, self.y, self.x + dx, self.y + dy
|
||||
coord = [int(round(x)) for x in coord]
|
||||
x1, y1, x2, y2 = coord
|
||||
|
||||
x1 += self.w.width // 2
|
||||
x2 += self.w.width // 2
|
||||
y1 = self.w.height // 2 - y1
|
||||
y2 = self.w.height // 2 - y2
|
||||
|
||||
self.w.line(x1, y1, x2, y2, self.w.BLACK)
|
||||
self.w.update()
|
||||
|
||||
self.x += dx
|
||||
self.y += dy
|
||||
|
||||
elif n.type == N.ROTATE:
|
||||
angle, = self.runs(n)
|
||||
self.angle += angle
|
||||
self.sin = math.sin(math.pi * self.angle / 180)
|
||||
self.cos = math.cos(math.pi * self.angle / 180)
|
||||
|
||||
elif n.type == N.ORIENT:
|
||||
angle, = self.runs(n)
|
||||
self.angle = angle
|
||||
self.sin = math.sin(math.pi * self.angle / 180)
|
||||
self.cos = math.cos(math.pi * self.angle / 180)
|
||||
|
||||
elif n.type == N.GOTO:
|
||||
x, y = self.runs(n)
|
||||
|
||||
if self.pen:
|
||||
coord = self.x, self.y, x, y
|
||||
coord = [int(round(x)) for x in coord]
|
||||
x1, y1, x2, y2 = coord
|
||||
|
||||
x1 += self.w.width // 2
|
||||
x2 += self.w.width // 2
|
||||
y1 = self.w.height // 2 - y1
|
||||
y2 = self.w.height // 2 - y2
|
||||
|
||||
self.w.line(x1, y1, x2, y2, 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]
|
||||
|
||||
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:
|
||||
val, = self.runs(n)
|
||||
print(val)
|
||||
|
||||
elif n.type == N.STYLE:
|
||||
# TODO: Support style
|
||||
raise Exception("Style statement not supported yet x_x")
|
||||
|
||||
elif n.type == N.WAIT:
|
||||
# TODO: Display "paused" and wait for a key press to continue"
|
||||
print("[interpreter] Delay ignored")
|
||||
|
||||
# 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.WHILE:
|
||||
while self.run(n.args[0]):
|
||||
self.run(n.args[1])
|
||||
|
||||
elif n.type == N.IFELSE:
|
||||
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 = 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]
|
||||
return self.vars[var]
|
||||
|
||||
elif n.type == N.CONST:
|
||||
return n.value
|
||||
|
||||
def runs(self, n):
|
||||
"""Evaluate all the arguments of a node."""
|
||||
return [self.run(e) for e in n.args]
|
|
@ -266,7 +266,7 @@ class UrlParser:
|
|||
def argvar(self):
|
||||
n = self.var()
|
||||
self.expect(T.PARAM)
|
||||
return n
|
||||
return n.args[0]
|
||||
|
||||
# var -> VAR
|
||||
def var(self):
|
||||
|
|
|
@ -65,5 +65,11 @@ def print_ast(n, lang="en", indent=0):
|
|||
n.constchildren():
|
||||
return
|
||||
|
||||
if n.type == N.ASSIGN:
|
||||
print_ast(n.args[0], lang=lang, indent=indent+2)
|
||||
print(" " * (indent+2), end="")
|
||||
print(f"->{n.args[1]}")
|
||||
return
|
||||
|
||||
for arg in n.args:
|
||||
print_ast(arg, lang=lang, indent=indent+2)
|
||||
|
|
Loading…
Reference in New Issue