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:
Lephe 2019-10-01 18:05:22 +02:00
parent 8269ab3f19
commit cd2936c582
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
5 changed files with 194 additions and 11 deletions

2
ast.py
View File

@ -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
View File

@ -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__":

182
interpreter.py Normal file
View File

@ -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]

View File

@ -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):

View File

@ -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)