add relations, and most functions
This change adds a [cond] grammar symbol corresponding to binary relations in IF, IFELSE and WHILE conditions. It also adds support for unary functions with parenthesis syntax. Other functions will need specific rules depending on their operator precedence level. Also adds the Window.save() function that implements the --save option to save the output of the program into a bitmap file. This will be used to perform automated unit tests.
This commit is contained in:
parent
0e9a2ee944
commit
a983faedeb
4
ast.py
4
ast.py
|
@ -28,7 +28,7 @@ class N(enum.IntEnum):
|
|||
# Flow control
|
||||
REPEAT = enum.auto()
|
||||
WHILE = enum.auto()
|
||||
IFELSE = enum.auto()
|
||||
IF = enum.auto()
|
||||
|
||||
# Expressions
|
||||
ADD = enum.auto()
|
||||
|
@ -39,6 +39,8 @@ class N(enum.IntEnum):
|
|||
EXP = enum.auto()
|
||||
VAR = enum.auto()
|
||||
CONST = enum.auto()
|
||||
FUN = enum.auto()
|
||||
REL = enum.auto()
|
||||
|
||||
#---
|
||||
# AST nodes
|
||||
|
|
13
drawing.py
13
drawing.py
|
@ -126,6 +126,19 @@ class Window:
|
|||
if event.type == SDL_QUIT:
|
||||
break
|
||||
|
||||
def save(self, out):
|
||||
"""Save the output into a BMP file."""
|
||||
SDL_SetRenderTarget(self.r, self.t)
|
||||
|
||||
surface = SDL_CreateRGBSurface(0, self.width, self.height, 32,0,0,0,0)
|
||||
fmt = surface.contents.format.contents.format
|
||||
pixels = surface.contents.pixels
|
||||
pitch = surface.contents.pitch
|
||||
|
||||
SDL_RenderReadPixels(self.r, None, fmt, pixels, pitch);
|
||||
SDL_SaveBMP(surface, out.encode())
|
||||
SDL_FreeSurface(surface)
|
||||
|
||||
def update(self):
|
||||
"""Push window contents on-screen."""
|
||||
|
||||
|
|
2
fx92.py
2
fx92.py
|
@ -99,6 +99,8 @@ def main():
|
|||
ctx = Context(w)
|
||||
ctx.run(ast)
|
||||
|
||||
if out is not None:
|
||||
w.save(out)
|
||||
w.wait()
|
||||
|
||||
return 0
|
||||
|
|
|
@ -137,7 +137,7 @@ class Context:
|
|||
while self.run(n.args[0]):
|
||||
self.run(n.args[1])
|
||||
|
||||
elif n.type == N.IFELSE:
|
||||
elif n.type == N.IF:
|
||||
if self.run(n.args[0]):
|
||||
self.run(n.args[1])
|
||||
elif n.args[2] is not None:
|
||||
|
@ -181,6 +181,57 @@ class Context:
|
|||
elif n.type == N.CONST:
|
||||
return n.value
|
||||
|
||||
elif n.type == N.FUN:
|
||||
arg = self.run(n.args[1])
|
||||
|
||||
f = {
|
||||
"Abs": abs,
|
||||
"Rnd": None,
|
||||
"sinh": math.sinh,
|
||||
"cosh": math.cosh,
|
||||
"tanh": math.tanh,
|
||||
"asinh": math.asinh,
|
||||
"acosh": math.acosh,
|
||||
"atanh": math.atanh,
|
||||
"sqrt": math.sqrt,
|
||||
"log": math.log,
|
||||
"cbrt": lambda x: x ** (1/3),
|
||||
"sin": math.sin,
|
||||
"cos": math.cos,
|
||||
"tan": math.tan,
|
||||
"asin": math.asin,
|
||||
"acos": math.acos,
|
||||
"atan": math.atan,
|
||||
"log10": math.log10,
|
||||
"Ent": lambda x: float(int(x)),
|
||||
"EntEx": lambda x: float(int(x)) - (x < 0),
|
||||
"RanInt": None,
|
||||
"GCD": None,
|
||||
"LCM": None,
|
||||
"Arond": None,
|
||||
}[n.args[0]]
|
||||
|
||||
if f is None:
|
||||
raise Exception(f"Function {n.args[0]} not there yet x_x")
|
||||
return f(arg)
|
||||
|
||||
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(f"Unhandled node of type {t}")
|
||||
|
||||
def runs(self, n):
|
||||
"""Evaluate all the arguments of a node."""
|
||||
return [self.run(e) for e in n.args]
|
||||
|
|
43
lexer.py
43
lexer.py
|
@ -68,6 +68,7 @@ class T(enum.IntEnum):
|
|||
CONST = -1
|
||||
VAR = -2
|
||||
REL = -3
|
||||
FUN = -4
|
||||
|
||||
class Token:
|
||||
def __init__(self, type, *args):
|
||||
|
@ -104,7 +105,7 @@ class LexerBase:
|
|||
|
||||
while not self.at_end():
|
||||
x = self.lex()
|
||||
print(x)
|
||||
print("{:5d}: {}".format(self.position, x))
|
||||
|
||||
#---
|
||||
# Bitcode lexer
|
||||
|
@ -123,11 +124,13 @@ class BitcodeLexer(LexerBase):
|
|||
def rewind(self):
|
||||
"""Restart lexing the same input."""
|
||||
self.pos = 0
|
||||
self.position = 0
|
||||
self.errors = 0
|
||||
|
||||
def lex(self):
|
||||
"""Return the next token in the stream."""
|
||||
h, p = self.hex, self.pos
|
||||
self.position += 1
|
||||
|
||||
if self.at_end():
|
||||
return Token(T.END)
|
||||
|
@ -205,10 +208,44 @@ class BitcodeLexer(LexerBase):
|
|||
if code == 0x49:
|
||||
return Token(T.VAR, "y")
|
||||
|
||||
# Functions
|
||||
fun = {
|
||||
0x68: "Abs",
|
||||
0x69: "Rnd",
|
||||
0x6C: "sinh",
|
||||
0x6D: "cosh",
|
||||
0x6E: "tanh",
|
||||
0x6F: "asinh",
|
||||
0x70: "acosh",
|
||||
0x71: "atanh",
|
||||
# 0x72: "exp",
|
||||
# 0x73: "exp10",
|
||||
0x74: "sqrt",
|
||||
0x75: "log",
|
||||
0x76: "cbrt",
|
||||
0x77: "sin",
|
||||
0x78: "cos",
|
||||
0x79: "tan",
|
||||
0x7A: "asin",
|
||||
0x7B: "acos",
|
||||
0x7C: "atan",
|
||||
0x7D: "log10",
|
||||
0x83: "Ent",
|
||||
0x84: "EntEx",
|
||||
0x87: "RanInt",
|
||||
# 0x88: "GCD",
|
||||
# 0x89: "LCM",
|
||||
0x8A: "Arond",
|
||||
}
|
||||
|
||||
if code in fun:
|
||||
return Token(T.FUN, fun[code])
|
||||
|
||||
print(f"[lexer] Unknown opcode {hex(code)}")
|
||||
self.errors += 1
|
||||
|
||||
# Try to read another token
|
||||
# Try to read another token after skipping one byte
|
||||
self.position -= 1
|
||||
return self.lex()
|
||||
|
||||
def at_end(self):
|
||||
|
@ -279,11 +316,13 @@ class TextLexer(LexerBase):
|
|||
"""Restart lexing the same input."""
|
||||
|
||||
self.code = self.base_code
|
||||
self.position = 0
|
||||
self.errors = 0
|
||||
self.pending_param = False
|
||||
|
||||
def lex(self):
|
||||
"""Return the next token in the stream."""
|
||||
self.position += 1
|
||||
|
||||
c = self.code.lstrip(" \t")
|
||||
|
||||
|
|
50
parser.py
50
parser.py
|
@ -19,15 +19,17 @@ class Parser:
|
|||
PENDOWN | PENUP | SETVAR arg argvar | INPUT argvar |
|
||||
MESSAGE arg | PRINT arg | STYLE style | WAIT arg |
|
||||
REPEAT arg program REPEAT_END |
|
||||
WHILE arg program WHILE_END |
|
||||
IF arg program IF_END |
|
||||
IFELSE arg program ELSE program IFELSE_END
|
||||
WHILE cond PARAM program WHILE_END |
|
||||
IF cond PARAM program IF_END |
|
||||
IFELSE cond PARAM program ELSE program IFELSE_END
|
||||
arg -> expr PARAM
|
||||
argvar -> var PARAM
|
||||
|
||||
cond -> expr REL expr
|
||||
expr -> factor | factor + expr | factor - expr
|
||||
factor -> atom | atom * factor | atom / factor
|
||||
atom -> const (var | "(" expr ")")* | (var | "(" expr ")")+
|
||||
atom -> const (var | "(" expr ")" | FUN expr ")")* |
|
||||
(var | "(" expr ")" | FUN expr ")")+
|
||||
const -> (+|-)? CONST
|
||||
var -> VAR
|
||||
|
||||
|
@ -71,7 +73,8 @@ class Parser:
|
|||
|
||||
expected = [T(t).name for t in types]
|
||||
got = T(self.la.type).name
|
||||
err = f"Expected one of {expected}, got {got}"
|
||||
pos = self.lexer.position
|
||||
err = f"Expected one of {expected}, got {got} (at token {pos})"
|
||||
print("[urlparser] " + err)
|
||||
raise Exception("Syntax error: " + err)
|
||||
|
||||
|
@ -145,27 +148,30 @@ class Parser:
|
|||
return Node(N.REPEAT, arg, prg)
|
||||
|
||||
if op.type == T.WHILE:
|
||||
arg = self.arg()
|
||||
cond = self.cond()
|
||||
self.expect(T.PARAM)
|
||||
self.expect(T.EOL, optional=True)
|
||||
prg = self.program()
|
||||
self.expect(T.WHILE_END)
|
||||
return Node(N.WHILE, arg, prg)
|
||||
return Node(N.WHILE, cond, prg)
|
||||
|
||||
if op.type == T.IF:
|
||||
arg = self.arg()
|
||||
cond = self.cond()
|
||||
self.expect(T.PARAM)
|
||||
self.expect(T.EOL, optional=True)
|
||||
prg = self.program()
|
||||
self.expect(T.IF_END)
|
||||
return Node(N.IF, arg, prg, None)
|
||||
return Node(N.IF, cond, prg, None)
|
||||
|
||||
if op.type == T.IFELSE:
|
||||
arg = self.arg()
|
||||
cond = self.cond()
|
||||
self.expect(T.PARAM)
|
||||
self.expect(T.EOL, optional=True)
|
||||
p1 = self.program()
|
||||
self.expect(T.ELSE)
|
||||
p2 = self.program()
|
||||
self.expect(T.IFELSE_END)
|
||||
return Node(N.IF, arg, p1, p2)
|
||||
return Node(N.IF, cond, p1, p2)
|
||||
|
||||
# arg -> expr PARAM
|
||||
def arg(self):
|
||||
|
@ -173,6 +179,13 @@ class Parser:
|
|||
self.expect(T.PARAM)
|
||||
return e
|
||||
|
||||
# cond -> expr REL expr
|
||||
def cond(self):
|
||||
e1 = self.expr()
|
||||
rel = self.expect(T.REL).args[0]
|
||||
e2 = self.expr()
|
||||
return Node(N.REL, e1, rel, e2)
|
||||
|
||||
# expr -> factor | factor + expr | factor - expr
|
||||
def expr(self):
|
||||
f = [self.factor()]
|
||||
|
@ -216,7 +229,20 @@ class Parser:
|
|||
elif lat == T.LPAR:
|
||||
self.expect(T.LPAR)
|
||||
factors.append(self.expr())
|
||||
self.expect(T.RPAR)
|
||||
|
||||
# Allow a parenthesis to be removed at the end of a parameter
|
||||
optional = (self.la.type == T.PARAM)
|
||||
self.expect(T.RPAR, optional=optional)
|
||||
|
||||
elif lat == T.FUN:
|
||||
name = self.expect(T.FUN).args[0]
|
||||
e = self.expr()
|
||||
|
||||
# Allow a parenthesis to be removed at the end of a parameter
|
||||
optional = (self.la.type == T.PARAM)
|
||||
self.expect(T.RPAR, optional=optional)
|
||||
|
||||
factors.append(Node(N.FUN, name, e))
|
||||
else:
|
||||
break
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ def print_ast(n, lang="en", indent=0):
|
|||
if lang == "en": lang = MessageEnglish
|
||||
if lang == "ast": lang = MessageAST
|
||||
|
||||
if n.type == N.PROGRAM:
|
||||
if isinstance(n, Node) and n.type == N.PROGRAM:
|
||||
for arg in n.args:
|
||||
print_ast(arg, lang=lang, indent=indent)
|
||||
return
|
||||
|
@ -45,14 +45,10 @@ def print_ast(n, lang="en", indent=0):
|
|||
print(f"{type(n)}({n})")
|
||||
return
|
||||
|
||||
if n.type == N.CONST:
|
||||
if n.type in [N.CONST, N.VAR, N.REL]:
|
||||
print(n.args[0])
|
||||
return
|
||||
|
||||
if n.type == N.VAR:
|
||||
print(f"{n.args[0]}")
|
||||
return
|
||||
|
||||
id = n.type.name.lower()
|
||||
|
||||
if hasattr(lang, id):
|
||||
|
|
Loading…
Reference in New Issue