Fix bug found in project 04

This commit is contained in:
Jan-Niclas Loosen
2026-01-16 13:50:04 +01:00
parent 20ff73a29c
commit a2cc0adb52
54 changed files with 1 additions and 1 deletions

View File

@@ -0,0 +1,175 @@
import syntax
from vistram.tram import *
label_counter = 0
def new_label():
global label_counter
label_counter += 1
return Label(text=f"L{label_counter}")
# Rules E1, E2
def elab_def(decls, rho, nl):
r = dict(rho)
for d in decls:
r = d.elab(r, nl)
return r
# Rule K6
class CONST(syntax.CONST):
def code(self, rho, nl):
return [const(self.value)]
# Rule K3
class ID(syntax.ID):
def code(self, rho, nl):
k, nl_def = rho[self.name]
return [load(k, nl - nl_def)]
# Rule K7
class AOP(syntax.AOP):
def code(self, rho, nl):
c1 = self.arg1.code(rho, nl)
c2 = self.arg2.code(rho, nl)
match self.operator:
case "+":
return c1 + c2 + [add()]
case "-":
return c1 + c2 + [sub()]
case "*":
return c1 + c2 + [mul()]
case "/":
return c1 + c2 + [div()]
case _:
raise Exception(f"Unknown AOP operator.")
# Rule K5
class COMP(syntax.COMP):
def code(self, rho, nl):
c1 = self.arg1.code(rho, nl)
c2 = self.arg2.code(rho, nl)
match self.operator:
case "<":
return c1 + c2 + [lt()]
case ">":
return c1 + c2 + [gt()]
case "<=":
# leq is not implemented in vistra.tram
return LOP("||", COMP("<", self.arg1, self.arg2), EQOP("==", self.arg1, self.arg2)).code(rho, nl)
case ">=":
# geq is not implemented in vistra.tram
return LOP("||", COMP(">", self.arg1, self.arg2), EQOP("==", self.arg1, self.arg2)).code(rho, nl)
case _:
raise ValueError(f"Unknown COMP operator.")
# Rule K5
class EQOP(syntax.EQOP):
def code(self, rho, nl):
c1 = self.arg1.code(rho, nl)
c2 = self.arg2.code(rho, nl)
match self.operator:
case "==":
return c1 + c2 + [eq()]
case "!=":
return c1 + c2 + [neq()]
case _:
raise ValueError(f"Unknown EQOP operator.")
# see lecture code generation AND/OR
class LOP(syntax.LOP):
def code(self, rho, nl):
c1 = self.arg1.code(rho, nl)
c2 = self.arg2.code(rho, nl)
l1 = new_label()
l2 = new_label()
match self.operator:
case "&&":
return c1 + [ifzero(l1)] + [const(1)] + c2 + [mul()] + [goto(l2)] + [nop(l1)] + [const(0)] + [nop(l2)]
case "||":
return c1 + [ifzero(l1)] + [const(1)] + [goto(l2)] + [nop(l1)] + c2 + [nop(l2)]
case _:
raise ValueError("Unknown LOP operator.")
# Rule K4
class ASSIGN(syntax.ASSIGN):
def code(self, rho, nl):
c = self.expr.code(rho, nl)
k, nl_def = rho[self.var.name]
dif = nl - nl_def
return c + [store(k, dif), load(k, dif)]
# Rule K9
class SEQ(syntax.SEQ):
def code(self, rho, nl):
c1 = self.exp1.code(rho, nl)
c2 = self.exp2.code(rho, nl)
return c1 + [pop()] + c2
# Rule K10
class IF(syntax.IF):
def code(self, rho, nl):
l1 = new_label()
l2 = new_label()
c1 = self.cond.code(rho, nl)
c2 = self.exp1.code(rho, nl)
c3 = self.exp2.code(rho, nl)
return c1 + [ifzero(l1)] + c2 + [goto(l2)] + [nop(assigned_label=l1)] + c3 + [nop(assigned_label=l2)]
# Rule K11
class WHILE(syntax.WHILE):
def code(self, rho, nl):
l1 = new_label()
l2 = new_label()
l3 = new_label()
l4 = new_label()
c1 = self.cond.code(rho, nl)
c2 = self.body.code(rho, nl)
return c1 + [ifzero(l3), goto(l4)] + [nop(l1)] + c1 + [ifzero(l2), pop()] + [nop(l4)] + c2 + [goto(l1)] + [nop(l3), const(None), nop(l2)]
# Rule K2
class LET(syntax.LET):
def code(self, rho, nl):
l = new_label()
# Normalize to list
decls = self.decl if isinstance(self.decl, list) else [self.decl]
shared_rho = elab_def(decls, rho, nl)
ds = []
for d in decls:
ds += d.code(shared_rho, nl)
es = self.body.code(shared_rho, nl)
return [goto(l)] + ds + [nop(assigned_label=l)] + es
# Rule K13
class DECL(syntax.DECL):
def elab(self, rho, nl):
nested_rho = dict(rho)
nested_rho[self.f_name] = (new_label(), nl)
return nested_rho
def code(self, rho, nl):
l, _ = rho[self.f_name]
nested_rho = dict(rho)
for i, p in enumerate(self.params):
nested_rho[p] = (i, nl + 1)
c = self.body.code(nested_rho, nl + 1)
return [nop(assigned_label=l)] + c + [ireturn()]
# Rule K12
class CALL(syntax.CALL):
def code(self, rho, nl):
c = []
for a in self.arg:
c += a.code(rho, nl)
l, nl_def = rho[self.f_name]
return c + [invoke(len(self.arg), l, nl - nl_def)]

View File

View File

@@ -0,0 +1,22 @@
from pathlib import Path
def prompt_choice(prompt: str, options: list) -> int:
print(prompt)
for i, opt in enumerate(options, 1):
print(f" {i}. {opt}")
while True:
try:
s = input(f"Enter choice (1-{len(options)}): ").strip()
idx = int(s) - 1
if 0 <= idx < len(options):
return idx
except Exception:
pass
print(f"Invalid. Enter a number between 1-{len(options)}.")
def prompt_confirmation(question: str, default="y"):
s = input(f"{question} (y/n) [{default}]: ").strip().lower()
if not s:
s = default
return s.startswith("y")

146
Project-02-03-04/main.py Normal file
View File

@@ -0,0 +1,146 @@
import triplayacc as yacc
import syntax
from pathlib import Path
from graphviz import Source
from vistram.tram import *
import tkinter as tk
from vistram.vistram import MachineUI
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import helpers.console as cnsl
import os
import matplotlib
matplotlib.use("TkAgg")
# Assembles the AST into TRAM code
def assemble(ast):
code = ast.code({}, 0)
return code + [halt()]
# Renders a diagram of the AST
def render_diagram(dot_string: str):
# Set DPI for PNG
os.environ["GV_FILE_DPI"] = "300"
src = Source(dot_string, format="png", engine="dot")
png_path = src.render(cleanup=True)
img = mpimg.imread(png_path)
fig = plt.figure(figsize=(12, 12))
fig.canvas.manager.window.wm_title("TRIPLA AST Viewer")
plt.imshow(img)
plt.axis("off")
plt.show()
# Pretty prints the AST
def pretty_print(node, indent=0):
prefix = " " * indent
print(f"{prefix}{type(node).__name__}:")
for key, value in node.__dict__.items():
if isinstance(value, syntax.EXPRESSION):
pretty_print(value, indent + 4)
elif isinstance(value, list):
print(f"{prefix} {key}: [")
for element in value:
if isinstance(element, syntax.EXPRESSION):
pretty_print(element, indent + 4)
else:
print(" " * (indent + 4) + str(element))
print(f"{prefix} ]")
else:
print(f"{prefix} {key}: {value}")
if __name__ == "__main__":
print("\nTRIPLA Parser and TRIPLA to TRAM Compiler")
while True:
mode = cnsl.prompt_choice("\nSelect action:", ["Parse .tripla", "Compile .tripla", "Exit"])
if mode == 2:
print("\nBye Bye.")
break
base = Path(__file__).parent / "triplaprograms"
programs = sorted([f for f in base.glob("*.tripla")])
if not programs:
print("\nNo .tripla files found.")
continue
idx = cnsl.prompt_choice("\nSelect TRIPLA program:", [p.name for p in programs])
path = programs[idx]
source = path.read_text()
ast = yacc.parser.parse(source)
if mode == 0:
# Pretty print
if cnsl.prompt_confirmation("\nPretty-print AST?"):
print("")
pretty_print(ast)
# Export DOT
dot_str = ast.to_dot()
if cnsl.prompt_confirmation("Export AST as .dot file?"):
default = f"{path.stem}.dot"
filename = input(f"Filename [{default}]: ").strip()
if not filename:
filename = default
try:
base_dir = Path(__file__).parent
out_path = base_dir / filename
with open(out_path, "w") as f:
f.write(dot_str)
print(f"Saved DOT file as: {out_path}")
except Exception as e:
print(f"Could not save DOT file: {e}")
# Display AST diagram
if cnsl.prompt_confirmation("Display AST diagram?"):
render_diagram(dot_str)
print("Rendered AST diagram.")
elif mode == 1:
tram_code = assemble(ast)
# Print TRAM code
if cnsl.prompt_confirmation("\nPrint TRAM code to console?"):
print("\nGenerated TRAM code:\n")
for instr in tram_code:
print(instr.toString())
# Save TRAM code
if cnsl.prompt_confirmation("Save TRAM code as .tram file?"):
base_dir = Path(__file__).parent / "tramcodes"
base_dir.mkdir(exist_ok=True)
default = f"{path.stem}.tram"
filename = input(f"Filename [{default}]: ").strip()
if not filename:
filename = default
out_path = base_dir / filename
with open(out_path, "w") as f:
for instr in tram_code:
f.write(instr.toString() + "\n")
print(f"Saved TRAM code to: {out_path}")
# Display TRAM code in Visual TRAM UI
if cnsl.prompt_confirmation("Display TRAM code in Visual TRAM UI?"):
root = tk.Tk()
ui = MachineUI(root)
ui.machine.initial_program = tram_code
ui.machine.reset()
root.mainloop()

1135
Project-02-03-04/parser.out Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
# parsetab.py
# This file is automatically generated. Do not edit.
# pylint: disable=W,C,R
_tabversion = '3.10'
_lr_method = 'LALR'
_lr_signature = 'ErightINleftSEMICOLONrightASSIGNleftCOMPleftEQOPleftAOPAOP ASSIGN COMMA COMP CONST DO ELSE EQOP FALSE ID IF IN LBRACE LET LOP LPAREN RBRACE RPAREN SEMICOLON THEN TRUE WHILEE : LET D IN EE : IDE : ID LPAREN A RPARENE : E AOP EE : LPAREN E RPARENE : CONSTE : ID ASSIGN EE : E SEMICOLON EE : IF B THEN E ELSE EE : WHILE B DO LBRACE E RBRACEA : EA : A COMMA ED : ID LPAREN V RPAREN LBRACE E RBRACED : D DV : IDV : V COMMA VB : E EQOP EB : E COMP EB : B EQOP BB : B LOP BB : TRUEB : FALSEB : LPAREN B RPAREN'
_lr_action_items = {'LET':([0,4,6,7,8,9,12,13,19,24,30,31,32,33,34,42,49,53,55,],[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,]),'ID':([0,2,4,6,7,8,9,10,12,13,19,23,24,25,30,31,32,33,34,42,49,51,53,55,60,],[3,11,3,3,3,3,3,11,3,3,3,11,3,39,3,3,3,3,3,3,3,39,3,3,-13,]),'LPAREN':([0,3,4,6,7,8,9,11,12,13,19,24,30,31,32,33,34,42,49,53,55,],[4,12,4,19,19,4,4,25,4,4,19,4,4,19,19,4,4,4,4,4,4,]),'CONST':([0,4,6,7,8,9,12,13,19,24,30,31,32,33,34,42,49,53,55,],[5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,]),'IF':([0,4,6,7,8,9,12,13,19,24,30,31,32,33,34,42,49,53,55,],[6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,]),'WHILE':([0,4,6,7,8,9,12,13,19,24,30,31,32,33,34,42,49,53,55,],[7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,]),'$end':([1,3,5,21,22,28,29,38,41,57,58,],[0,-2,-6,-4,-8,-7,-5,-1,-3,-9,-10,]),'AOP':([1,3,5,14,16,21,22,27,28,29,36,38,41,43,46,47,52,54,57,58,59,],[8,-2,-6,8,8,-4,8,8,8,-5,8,8,-3,8,8,8,8,8,8,-10,8,]),'SEMICOLON':([1,3,5,14,16,21,22,27,28,29,36,38,41,43,46,47,52,54,57,58,59,],[9,-2,-6,9,9,-4,-8,9,-7,-5,9,9,-3,9,9,9,9,9,9,-10,9,]),'RPAREN':([3,5,14,17,18,21,22,26,27,28,29,35,36,38,39,40,41,44,45,46,47,48,52,56,57,58,],[-2,-6,29,-21,-22,-4,-8,41,-11,-7,-5,48,29,-1,-15,50,-3,-19,-20,-17,-18,-23,-12,-16,-9,-10,]),'EQOP':([3,5,15,16,17,18,20,21,22,28,29,35,36,38,41,44,45,46,47,48,57,58,],[-2,-6,31,33,-21,-22,31,-4,-8,-7,-5,31,33,-1,-3,-19,31,-17,-18,-23,-9,-10,]),'COMP':([3,5,16,21,22,28,29,36,38,41,57,58,],[-2,-6,34,-4,-8,-7,-5,34,-1,-3,-9,-10,]),'COMMA':([3,5,21,22,26,27,28,29,38,39,40,41,52,56,57,58,],[-2,-6,-4,-8,42,-11,-7,-5,-1,-15,51,-3,-12,51,-9,-10,]),'ELSE':([3,5,21,22,28,29,38,41,43,57,58,],[-2,-6,-4,-8,-7,-5,-1,-3,53,-9,-10,]),'THEN':([3,5,15,17,18,21,22,28,29,38,41,44,45,46,47,48,57,58,],[-2,-6,30,-21,-22,-4,-8,-7,-5,-1,-3,-19,-20,-17,-18,-23,-9,-10,]),'LOP':([3,5,15,17,18,20,21,22,28,29,35,38,41,44,45,46,47,48,57,58,],[-2,-6,32,-21,-22,32,-4,-8,-7,-5,32,-1,-3,-19,32,-17,-18,-23,-9,-10,]),'DO':([3,5,17,18,20,21,22,28,29,38,41,44,45,46,47,48,57,58,],[-2,-6,-21,-22,37,-4,-8,-7,-5,-1,-3,-19,-20,-17,-18,-23,-9,-10,]),'RBRACE':([3,5,21,22,28,29,38,41,54,57,58,59,],[-2,-6,-4,-8,-7,-5,-1,-3,58,-9,-10,60,]),'ASSIGN':([3,],[13,]),'TRUE':([6,7,19,31,32,],[17,17,17,17,17,]),'FALSE':([6,7,19,31,32,],[18,18,18,18,18,]),'IN':([10,23,60,],[24,-14,-13,]),'LBRACE':([37,50,],[49,55,]),}
_lr_action = {}
for _k, _v in _lr_action_items.items():
for _x,_y in zip(_v[0],_v[1]):
if not _x in _lr_action: _lr_action[_x] = {}
_lr_action[_x][_k] = _y
del _lr_action_items
_lr_goto_items = {'E':([0,4,6,7,8,9,12,13,19,24,30,31,32,33,34,42,49,53,55,],[1,14,16,16,21,22,27,28,36,38,43,16,16,46,47,52,54,57,59,]),'D':([2,10,23,],[10,23,23,]),'B':([6,7,19,31,32,],[15,20,35,44,45,]),'A':([12,],[26,]),'V':([25,51,],[40,56,]),}
_lr_goto = {}
for _k, _v in _lr_goto_items.items():
for _x, _y in zip(_v[0], _v[1]):
if not _x in _lr_goto: _lr_goto[_x] = {}
_lr_goto[_x][_k] = _y
del _lr_goto_items
_lr_productions = [
("S' -> E","S'",1,None,None,None),
('E -> LET D IN E','E',4,'p_E_let','triplayacc.py',27),
('E -> ID','E',1,'p_E_id','triplayacc.py',31),
('E -> ID LPAREN A RPAREN','E',4,'p_E_call','triplayacc.py',35),
('E -> E AOP E','E',3,'p_E_aop','triplayacc.py',40),
('E -> LPAREN E RPAREN','E',3,'p_E_paren','triplayacc.py',44),
('E -> CONST','E',1,'p_E_const','triplayacc.py',49),
('E -> ID ASSIGN E','E',3,'p_E_assign','triplayacc.py',53),
('E -> E SEMICOLON E','E',3,'p_E_seq','triplayacc.py',57),
('E -> IF B THEN E ELSE E','E',6,'p_E_if','triplayacc.py',61),
('E -> WHILE B DO LBRACE E RBRACE','E',6,'p_E_while','triplayacc.py',65),
('A -> E','A',1,'p_A_single','triplayacc.py',74),
('A -> A COMMA E','A',3,'p_A_multiple','triplayacc.py',78),
('D -> ID LPAREN V RPAREN LBRACE E RBRACE','D',7,'p_D_single','triplayacc.py',86),
('D -> D D','D',2,'p_D_concat','triplayacc.py',90),
('V -> ID','V',1,'p_V_single','triplayacc.py',98),
('V -> V COMMA V','V',3,'p_V_multiple','triplayacc.py',102),
('B -> E EQOP E','B',3,'p_B_eqop_E','triplayacc.py',110),
('B -> E COMP E','B',3,'p_B_comp','triplayacc.py',114),
('B -> B EQOP B','B',3,'p_B_eqop_B','triplayacc.py',118),
('B -> B LOP B','B',3,'p_B_lop','triplayacc.py',122),
('B -> TRUE','B',1,'p_B_true','triplayacc.py',126),
('B -> FALSE','B',1,'p_B_false','triplayacc.py',130),
('B -> LPAREN B RPAREN','B',3,'p_B_paren','triplayacc.py',134),
]

183
Project-02-03-04/syntax.py Normal file
View File

@@ -0,0 +1,183 @@
class EXPRESSION:
pp_count = 0
def __init__(self):
self.pp = EXPRESSION.pp_count
EXPRESSION.pp_count += 1
@staticmethod
def copy():
return EXPRESSION()
# Returns a list of tuples (edge_name, child_expression)
def children(self):
out = []
for key, value in self.__dict__.items():
if key == "pp":
continue
if isinstance(value, EXPRESSION):
out.append((key, value))
elif isinstance(value, list):
for i, elem in enumerate(value):
if isinstance(elem, EXPRESSION):
out.append((f"{key}{i}", elem))
return out
# Export AST to dot format
def to_dot(self, visited=None, root=True):
if visited is None:
visited = set()
# Prevent infinite recursion
if id(self) in visited:
return ""
visited.add(id(self))
parts = []
# Add a header at the root node
if root:
parts.append("digraph AST {\n")
# Append to label
label = type(self).__name__
if hasattr(self, "operator"):
label += f"({self.operator})"
if hasattr(self, "name"):
label += f"({self.name})"
if hasattr(self, "value"):
label += f"({self.value})"
parts.append(f' node{self.pp} [label="{label}"];\n')
# Draw edges
for edge_name, child in self.children():
parts.append(f' node{self.pp} -> node{child.pp} [label="{edge_name}"];\n')
parts.append(child.to_dot(visited, root=False))
# Add footer at the root node
if root:
parts.append("}\n")
return "".join(parts)
class LET(EXPRESSION):
def __init__(self, decls, body):
super().__init__()
self.decl = decls
self.body = body
def __str__(self):
return "let " + ", ".join(str(d) for d in self.decl) + " in " + str(self.body)
class DECL(EXPRESSION):
def __init__(self, f_name, params, body):
super().__init__()
self.f_name = f_name
self.params = params
self.body = body
def __str__(self):
return f"{self.f_name}({self.params}) {{ {self.body} }}"
class CALL(EXPRESSION):
def __init__(self, f_name, args):
super().__init__()
self.f_name = f_name
self.arg = args
def __str__(self):
return self.f_name + "(" + ",".join(str(a) for a in self.arg) + ")"
class ID(EXPRESSION):
def __init__(self, name):
super().__init__()
self.name = name
def __str__(self):
return self.name
class CONST(EXPRESSION):
def __init__(self, value):
super().__init__()
self.value = value
def __str__(self):
return str(self.value)
class AOP(EXPRESSION):
def __init__(self, operator, arg1, arg2):
super().__init__()
self.operator = operator
self.arg1 = arg1
self.arg2 = arg2
def __str__(self):
return "(" + str(self.arg1) + " " + str(self.operator) + " " + str(self.arg2) + ")"
class EQOP(EXPRESSION):
def __init__(self, operator, arg1, arg2):
super().__init__()
self.operator = operator
self.arg1 = arg1
self.arg2 = arg2
def __str__(self):
return "(" + str(self.arg1) + " " + str(self.operator) + " " + str(self.arg2) + ")"
class COMP(EXPRESSION):
def __init__(self, operator, arg1, arg2):
super().__init__()
self.operator = operator
self.arg1 = arg1
self.arg2 = arg2
def __str__(self):
return "(" + str(self.arg1) + " " + str(self.operator) + " " + str(self.arg2) + ")"
class LOP(EXPRESSION):
def __init__(self, operator, arg1, arg2):
super().__init__()
self.operator = operator
self.arg1 = arg1
self.arg2 = arg2
def __str__(self):
return "(" + str(self.arg1) + " " + str(self.operator) + " " + str(self.arg2) + ")"
class ASSIGN(EXPRESSION):
def __init__(self, var, expr):
super().__init__()
self.var = var
self.expr = expr
def __str__(self):
return self.var.name + " = " + str(self.expr)
class SEQ(EXPRESSION):
def __init__(self, exp1, exp2):
super().__init__()
self.exp1 = exp1
self.exp2 = exp2
def __str__(self):
return str(self.exp1) + "; " + str(self.exp2)
class IF(EXPRESSION):
def __init__(self, cond, exp1, exp2):
super().__init__()
self.cond = cond
self.exp1 = exp1
self.exp2 = exp2
def __str__(self):
return "if (" + str(self.cond) + ") then { " + str(self.exp1) + " } else { " + str(self.exp2) + " }"
class WHILE(EXPRESSION):
def __init__(self, cond, body):
super().__init__()
self.cond = cond
self.body = body
def __str__(self):
return "while (" + str(self.cond) + ") do { " + str(self.body) + " }"

View File

@@ -0,0 +1,10 @@
GOTO L1
L2: NOP
LOAD 0 0
RETURN
L1: NOP
CONST 1
CONST 2
CONST 3
INVOKE 3 L2 0
HALT

View File

@@ -0,0 +1,65 @@
GOTO L1
L2: NOP
LOAD 0 0
CONST 0
EQ
IFZERO L4
CONST 0
GOTO L5
L4: NOP
LOAD 0 0
CONST 1
SUB
INVOKE 1 L2 1
L5: NOP
RETURN
L3: NOP
LOAD 0 0
LOAD 1 0
GT
IFZERO L6
LOAD 0 0
INVOKE 1 L2 1
GOTO L7
L6: NOP
LOAD 1 0
INVOKE 1 L2 1
POP
GOTO L8
L9: NOP
LOAD 0 1
LOAD 1 1
MUL
LOAD 0 0
MUL
RETURN
L8: NOP
LOAD 0 0
LOAD 1 0
MUL
INVOKE 1 L9 0
L7: NOP
RETURN
L1: NOP
CONST 10
INVOKE 1 L2 0
POP
CONST 10
GOTO L10
L11: NOP
LOAD 0 0
LOAD 1 0
GT
IFZERO L12
LOAD 0 0
GOTO L13
L12: NOP
LOAD 1 0
L13: NOP
RETURN
L10: NOP
CONST 20
CONST 30
INVOKE 2 L11 0
INVOKE 2 L3 0
HALT

View File

@@ -0,0 +1,18 @@
CONST 1
CONST 0
GT
IFZERO L3
CONST 1
GOTO L4
L3: NOP
CONST 1
CONST 0
EQ
L4: NOP
IFZERO L1
CONST 1
GOTO L2
L1: NOP
CONST 0
L2: NOP
HALT

View File

@@ -0,0 +1,71 @@
GOTO L1
L2: NOP
LOAD 0 0
CONST 0
EQ
IFZERO L4
GOTO L6
L7: NOP
LOAD 0 0
LOAD 1 1
MUL
LOAD 1 0
ADD
RETURN
L6: NOP
CONST 2
LOAD 1 0
MUL
LOAD 0 0
INVOKE 2 L7 0
GOTO L5
L4: NOP
LOAD 0 0
CONST 1
ADD
L5: NOP
RETURN
L3: NOP
LOAD 1 0
CONST 0
GT
IFZERO L10
GOTO L11
L8: NOP
LOAD 1 0
CONST 0
GT
IFZERO L9
POP
L11: NOP
LOAD 0 0
LOAD 0 0
LOAD 1 0
INVOKE 2 L2 1
MUL
STORE 0 0
LOAD 0 0
POP
LOAD 1 0
CONST 1
SUB
STORE 1 0
LOAD 1 0
GOTO L8
L10: NOP
CONST None
L9: NOP
POP
LOAD 0 0
CONST 42
ADD
RETURN
L1: NOP
CONST 1
CONST 2
INVOKE 2 L2 0
POP
CONST 3
CONST 3
INVOKE 2 L3 0
HALT

View File

@@ -0,0 +1,92 @@
# ------------------------------------------------------------
# Tokenizer for the TRIPLA parser
# ------------------------------------------------------------
import ply.lex as lex
reserved = {
'let': 'LET',
'in': 'IN',
'if': 'IF',
'then': 'THEN',
'else': 'ELSE',
'while': 'WHILE',
'do': 'DO',
'true': 'TRUE',
'false': 'FALSE'
}
# List of token names.
tokens = [
'ID',
'CONST',
'AOP',
'COMP',
'EQOP',
'LOP',
'ASSIGN',
'LPAREN', 'RPAREN',
'LBRACE', 'RBRACE',
'COMMA',
'SEMICOLON',
] + list(reserved.values())
# Simple tokens
t_LPAREN = r'\('
t_RPAREN = r'\)'
t_LBRACE = r'\{'
t_RBRACE = r'\}'
t_COMMA = r','
t_SEMICOLON = r';'
t_ASSIGN = r'='
# Arithmetic operators
t_AOP = r'\+|\-|\*|/'
# Comparison operators
t_COMP = r'<=|>=|<|>'
# Equality operators
t_EQOP = r'==|!='
# Logical operators
t_LOP = r'\|\||&&'
# IDs
def t_ID(t):
r'[A-Za-z_][A-Za-z0-9_]*'
t.type = reserved.get(t.value, 'ID')
return t
# Constants
def t_CONST(t):
r'0|[1-9][0-9]*'
t.value = int(t.value)
return t
# Linebreaks
def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)
# Ignore whitespace
t_ignore = ' \t'
# Single-line comment
def t_comment_single(t):
r'//.*'
pass
# Multi-line comment
def t_comment_multi(t):
r'/\*([^*]|\*+[^*/])*\*/'
t.lexer.lineno += t.value.count('\n')
pass
# Error handling
def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)
# Build the lexer
lexer = lex.lex()

View File

@@ -0,0 +1,2 @@
let a(x,y,z) {x}
in a(1,2,3)

View File

@@ -0,0 +1,15 @@
let
f1(b) {
if(b==0) then 0 else f1(b-1)
}
f2(a, b) {
if(a > b) then f1(a) else f1(b);
let g(c) {
a*b*c
} in g(a*b)
}
in
f1(10); f2(10, let max(a, b) {
if(a > b) then a else b
}
in max(20, 30))

View File

@@ -0,0 +1 @@
if 2 < x && x > 9 then 1 else 0

View File

@@ -0,0 +1,4 @@
let a(x) {x}
b(y) {y}
c(z) {z}
in a(1); b(2); c(3)

View File

@@ -0,0 +1,38 @@
let
fac(x) {
if (x == 1) then 1
else fac(x-1)*x
}
fac(x) {
if (x == 1) then 1
else fac(x-1)*x
}
fac(x) {
if (x == 1) then 1
else fac(x-1)*x
}
fac(x) {
if (x == 1) then 1
else fac(x-1)*x
}
fac(x) {
if (x == 1) then 1
else fac(x-1)*x
}
fac(x) {
if (x == 1) then 1
else fac(x-1)*x
}
fac(x) {
if (x == 1) then 1
else fac(x-1)*x
}
fac(x) {
if (x == 1) then 1
else fac(x-1)*x
}
fac(x) {
if (x == 1) then 1
else fac(x-1)*x
}
in fac(3)

View File

@@ -0,0 +1 @@
let n(a) {a} in n(5) + m(5) ; let m(a) {a+1} in m(5)

View File

@@ -0,0 +1 @@
if (2 && 7 > 2) then 1 else 0

View File

@@ -0,0 +1,7 @@
let func(a,b) {
while ( a > 0 && b != a ) do {
b = b + 1;
a = a - 1
}
}
in func(10, 8)

View File

@@ -0,0 +1 @@
if 1 >= 0 then 1 else 0

View File

@@ -0,0 +1,12 @@
let ggT(a, b) {
if (a == b) then
a
else
do {
if (a > b) then
a = a - b
else
b = b - a
} while (a != b);
a
} in ggT(3528, 3780) // result should be 252

View File

@@ -0,0 +1,8 @@
let ggT(a, b) {
if (a == b) then
a
else if (a > b) then
ggT(a-b, b)
else
ggT(b-a, a)
} in ggT(3528, 3780) // result should be 252

View File

@@ -0,0 +1,16 @@
let f(x, y) {
if (x == 0) then
let g(x, z) {
x * y + z
} in g(2 * y, x)
else
x + 1
}
g (a, i) {
while (i > 0) do {
a = a * f(a, i);
i = i - 1
};
a + 42
}
in f (1, 2); g(3, 3)

View File

@@ -0,0 +1,3 @@
let f1(b) { if (b == 0) then 0 else f1(b-1) }
f2(a,b) { if (a > b) then f1(a) else f1(b) }
in f1(10); f2(10,-20)

View File

@@ -0,0 +1 @@
let m(a){a} in m(5);let m(b){b+1} in m(5)

View File

@@ -0,0 +1 @@
let n(a) {a} in n(5) ; let m(a) {a+1} in n(5) + m(5)

View File

@@ -0,0 +1 @@
if(true||true==true) then 1 else 0

View File

@@ -0,0 +1,3 @@
let g(a) {
a*a
} in g(5)

View File

@@ -0,0 +1,9 @@
let f1(b) {
if (b==0) then 0 else f1(b-1)
} in f1(10)
/*
f2(a,b) {
if (a>b) then f1(a) else f1(b)
}
in f1(10); f2(10,20)
*/

View File

@@ -0,0 +1,14 @@
let f1(b) {
if (b==0) then 0 else f1(b-1)
}
f2(a,b) {
if (a>b) then f1(a) else f1(b);
let g(c) {
a*b*c
}
in g(a*b)
}
in f1(10); f2(10, let max(a,b) {
if (a>b) then a else b
}
in max(20,30) )

View File

@@ -0,0 +1,4 @@
let func(a,b) {
a = b + 1
}
in func(10, 8)

View File

@@ -0,0 +1,9 @@
let func(a, b) {
let func(a, b) {
a * b
} in
do {
b = b + 1 * func(3, 1);
a = a - 1
} while ( a > 0 && b != a )
} in func(10, 8)

View File

@@ -0,0 +1,13 @@
let f(a, b) {
if (a == 0) then
2 + b
else
let g(a, c) {
a + c + b
} in 2 + g(a, b)
} g(c, d) {
if (c == 0) then
1 + d
else
c * f(c - 1, d)
} in f (2, 3); g (3, 2)

View File

@@ -0,0 +1,5 @@
let f(x, y) {
let g(x) {
y = x + 7
} in x = g(5); y
} in f(1, 2)

View File

@@ -0,0 +1,4 @@
let g(x, y) {
y = 3;
x
} in g(2)

View File

@@ -0,0 +1 @@
if (true) then 1 else 0

View File

@@ -0,0 +1 @@
if (true && false) then 1 else 0

View File

@@ -0,0 +1 @@
if (true || false) then 1 else 0

View File

@@ -0,0 +1 @@
if (1 > 2) then 1 else 0

View File

@@ -0,0 +1 @@
if (2 > 3 + 5) then 1 else 0

View File

@@ -0,0 +1 @@
if (1 > 2 || 3 < 5) then 1 else 0

View File

@@ -0,0 +1 @@
if (2 == 0 == false) then 1 else 0

View File

@@ -0,0 +1,2 @@
let square(x) { x*x }
in square(10)

View File

@@ -0,0 +1,4 @@
let mult(a,b) { a*b }
add(a,b) { let inc(a) { if (b!=0) then b=b-1;inc(a+1) else mult(a,1) }
in inc(a) }
in add(mult(2,3),add(4,5))

View File

@@ -0,0 +1,7 @@
let func(a,b) {
while ( a > 0 && b != a ) do {
b = b + 1;
a = a - 1
}
}
in func(10, 8)

View File

@@ -0,0 +1,3 @@
while (true) do {
3
}

View File

@@ -0,0 +1,8 @@
let wrapper(a, b) {
let ggt(noneSense) {
if a == b then a
else
if a > b then wrapper(a-b, b)
else wrapper(b-a, a)
} in ggt(0)
} in wrapper(21, 49)

View File

@@ -0,0 +1,9 @@
let wrapper(number, threshold) {
let square(x) {
if x*x > threshold
then x
else x*x
}
in square(number)
}
in wrapper(4, 10)

View File

@@ -0,0 +1,143 @@
# ------------------------------------------------------------
# Grammar of the TRIPLA language
# ------------------------------------------------------------
import ply.yacc as yacc
import compiler as ast
from triplalex import tokens
precedence = (
('right', 'IN'),
('left', 'SEMICOLON'),
('right', 'ASSIGN'),
('left', 'COMP'),
('left', 'EQOP'),
('left', 'AOP'),
)
start = 'E'
# ------------------------------------------------------------
# Rules for E
# ------------------------------------------------------------
def p_E_let(p):
'E : LET D IN E'
p[0] = ast.LET(p[2], p[4])
def p_E_id(p):
'E : ID'
p[0] = ast.ID(p[1])
def p_E_call(p):
'E : ID LPAREN A RPAREN'
# E : ID(A)
p[0] = ast.CALL(p[1], p[3])
def p_E_aop(p):
'E : E AOP E'
p[0] = ast.AOP(p[2], p[1], p[3])
def p_E_paren(p):
'E : LPAREN E RPAREN'
# E : (E)
p[0] = p[2]
def p_E_const(p):
'E : CONST'
p[0] = ast.CONST(p[1])
def p_E_assign(p):
'E : ID ASSIGN E'
p[0] = ast.ASSIGN(ast.ID(p[1]), p[3])
def p_E_seq(p):
'E : E SEMICOLON E'
p[0] = ast.SEQ(p[1], p[3])
def p_E_if(p):
'E : IF B THEN E ELSE E'
p[0] = ast.IF(p[2], p[4], p[6])
def p_E_while(p):
'E : WHILE B DO LBRACE E RBRACE'
p[0] = ast.WHILE(p[2], p[5])
# ------------------------------------------------------------
# Rules for A
# ------------------------------------------------------------
def p_A_single(p):
'A : E'
p[0] = [p[1]]
def p_A_multiple(p):
'A : A COMMA E'
p[0] = p[1] + [p[3]]
# ------------------------------------------------------------
# Rules for D
# ------------------------------------------------------------
def p_D_single(p):
'D : ID LPAREN V RPAREN LBRACE E RBRACE'
p[0] = [ast.DECL(p[1], p[3], p[6])]
def p_D_concat(p):
'D : D D'
p[0] = p[1] + p[2]
# ------------------------------------------------------------
# Rules for V
# ------------------------------------------------------------
def p_V_single(p):
'V : ID'
p[0] = [p[1]]
def p_V_multiple(p):
'V : V COMMA V'
p[0] = p[1] + p[3]
# ------------------------------------------------------------
# Rules for B
# ------------------------------------------------------------
def p_B_eqop_E(p):
'B : E EQOP E'
p[0] = ast.EQOP(p[2], p[1], p[3])
def p_B_comp(p):
'B : E COMP E'
p[0] = ast.COMP(p[2], p[1], p[3])
def p_B_eqop_B(p):
'B : B EQOP B'
p[0] = ast.EQOP(p[2], p[1], p[3])
def p_B_lop(p):
'B : B LOP B'
p[0] = ast.LOP(p[2], p[1], p[3])
def p_B_true(p):
'B : TRUE'
p[0] = ast.CONST(True)
def p_B_false(p):
'B : FALSE'
p[0] = ast.CONST(False)
def p_B_paren(p):
'B : LPAREN B RPAREN'
# B : (B)
p[0] = p[2]
# Error handling
def p_error(p):
if p:
print("Syntax error at token:", p.type, "value:", p.value)
else:
print("Syntax error at EOF")
parser = yacc.yacc()

View File

View File

@@ -0,0 +1,76 @@
# (c) Stephan Diehl, University of Trier, Germany, 2025
from . import tram
class Assembler:
@staticmethod
def read_tram_code(filename):
code = []
lines = []
labelmap = {}
linemap = {}
try:
with open(filename, 'r', encoding='utf-8') as f:
line_number = 0
for raw_line in f:
line = raw_line.strip()
if line.startswith("//") or line.startswith("#") or not line:
continue
if ":" in line:
labels_part = line[:line.index(':')]
labels = [label.strip() for label in labels_part.split(',')]
linemap[line_number] = labels
for label in labels:
labelmap[label] = tram.Label(line_number,label)
line = line[line.index(':') + 1:].strip()
lines.append(line)
line_number += 1
except IOError as e:
print(f"Fehler beim Lesen der Datei: {e}")
for i, line in enumerate(lines):
labels = [labelmap.get(label,None) for label in linemap.get(i,[])]
instr = Assembler.convert_to_instruction(line, labelmap, labels)
code.append(instr)
return code
@staticmethod
def convert_to_instruction(line, labelmap={}, labels=None):
parts = line.split()
instr =parts[0].upper()
arg1 = Assembler.arg_to_number(parts, 1, labelmap)
arg2 = Assembler.arg_to_number(parts, 2, labelmap)
arg3 = Assembler.arg_to_number(parts, 3, labelmap)
if instr == "CONST": code=tram.const(arg1,assigned_label=labels)
elif instr == "LOAD": code=tram.load(arg1, arg2,assigned_label=labels)
elif instr == "STORE": code=tram.store(arg1,arg2,assigned_label=labels)
elif instr == "ADD": code=tram.add(assigned_label=labels)
elif instr == "SUB": code=tram.sub(assigned_label=labels)
elif instr == "MUL": code=tram.mul(assigned_label=labels)
elif instr == "DIV": code=tram.div(assigned_label=labels)
elif instr == "LT": code=tram.lt(assigned_label=labels)
elif instr == "GT": code=tram.gt(assigned_label=labels)
elif instr == "EQ": code=tram.eq(assigned_label=labels)
elif instr == "NEQ": code=tram.neq(assigned_label=labels)
elif instr == "IFZERO": code=tram.ifzero(arg1,assigned_label=labels)
elif instr == "GOTO": code=tram.goto(arg1,assigned_label=labels)
elif instr == "HALT": code=tram.halt(assigned_label=labels)
elif instr == "NOP": code=tram.nop(assigned_label=labels)
elif instr == "INVOKE": code=tram.invoke(arg1,arg2,arg3,assigned_label=labels)
elif instr == "RETURN": code=tram.ireturn(assigned_label=labels)
elif instr == "POP": code=tram.pop(assigned_label=labels)
else:
print(f"Keine gültige Instruktion: {line}")
code = None
return code
@staticmethod
def arg_to_number(parts, index, labelmap):
if index >= len(parts):
return 0
arg = parts[index]
try:
return int(arg)
except ValueError:
return labelmap.get(arg, 0)

View File

@@ -0,0 +1,340 @@
# (c) Stephan Diehl, University of Trier, Germany, 2025
class TRAM:
def __init__(self, prog):
self.set_label_addresses(prog)
self.program=prog+[halt()]
self.print_prog(self.program)
self.pc = 0
self.stack = []
self.top = -1
self.pp = 0 #-1
self.fp = 0 #-1
def start(self):
while self.pc>=0:
self.print_instruction(self.program[self.pc])
self.program[self.pc].execute(self)
print(self.stack)
def set_label_addresses(self,code):
pos = 0
for instr in code:
for label in instr.assigned_labels:
label.address = pos
pos = pos + 1
def pop(self):
del self.stack[self.top]
self.top = self.top - 1
def push(self,v):
self.stack.append(v)
self.top = self.top + 1
def spp(self,d,pp,fp):
if (d==0):
return pp
else:
return self.spp(d-1,self.stack[self.fp+3],self.stack[self.fp+4])
def sfp(self, d, pp, fp):
if (d==0):
return fp
else:
return self.sfp(d-1, self.stack[self.fp+3], self.stack[self.fp+4])
def print_prog(self,prog):
pos=0
for instr in prog:
print(str(pos)+": ",end="")
pos=pos+1
self.print_instruction(instr)
def print_instruction(self, instr):
print(type(instr).__name__, end = "")
for attr in vars(instr).keys():
value=getattr(instr,attr)
if isinstance(value,list): continue
if isinstance(value,Label):
value="#"+str(value.address)
else:
value=str(value)
print(" "+value,end="")
print()
#################
class Label:
count=0
address=-1
def __init__(self,address=-1,text=""):
Label.count+=1
self.id=Label.count
self.address=address
if text=="":
self.text=f"L{Label.count}"
else:
self.text=text
def toString(self): return self.text
##################
class Instruction:
def __init__(self,assigned_label=None):
self.assigned_labels = []
if not assigned_label is None:
if isinstance(assigned_label,Label):
self.assigned_labels+=[assigned_label]
else:
self.assigned_labels+=assigned_label
def toString(self):
s=self.toStringx()
#print(s)
return(s)
def toStringx(self):
if (len(self.assigned_labels)==0):
return " "
else:
return ','.join( [ label.toString()
for label in self.assigned_labels] )+": "
class halt(Instruction):
def __init__(self,assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
tram.pc=-1
def toString(self): return super().toString()+"HALT"
class nop(Instruction):
def __init__(self,assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
tram.pc=tram.pc+1
def toString(self): return super().toString()+"NOP"
class pop(Instruction):
def __init__(self,assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
tram.pop()
tram.pc=tram.pc+1
def toString(self): return super().toString()+"POP"
class const(Instruction):
def __init__(self, k, assigned_label=None):
super().__init__(assigned_label=assigned_label)
self.k=k
def execute(self,tram):
tram.push(self.k)
tram.pc=tram.pc+1
def toString(self): return super().toString()+"CONST "+str(self.k)
class store(Instruction):
def __init__(self, k, d, assigned_label=None):
super().__init__(assigned_label=assigned_label)
self.k = k
self.d = d
def execute(self,tram):
tram.stack[tram.spp(self.d,tram.pp,tram.fp)+self.k]=tram.stack[tram.top]
tram.pop()
tram.pc=tram.pc+1
def toString(self):
return super().toString()+"STORE "+str(self.k)+" "+str(self.d)
class load(Instruction):
def __init__(self, k, d, assigned_label=None):
super().__init__(assigned_label=assigned_label)
self.k=k
self.d=d
def execute(self,tram):
tram.push(tram.stack[tram.spp(self.d,tram.pp,tram.fp)+self.k])
tram.pc=tram.pc+1
def toString(self):
return super().toString()+"LOAD "+str(self.k)+" "+str(self.d)
class add(Instruction):
def __init__(self, assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
tram.stack[tram.top-1]=tram.stack[tram.top-1]+tram.stack[tram.top]
tram.pop()
tram.pc=tram.pc+1
def toString(self): return super().toString()+"ADD"
class sub(Instruction):
def __init__(self, assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
tram.stack[tram.top-1]=tram.stack[tram.top-1]-tram.stack[tram.top]
tram.pop()
tram.pc=tram.pc+1
def toString(self): return super().toString() + "SUB"
class mul(Instruction):
def __init__(self, assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
tram.stack[tram.top-1]=tram.stack[tram.top-1]*tram.stack[tram.top]
tram.pop()
tram.pc=tram.pc+1
def toString(self): return super().toString() + "MUL"
class div(Instruction):
def __init__(self, assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
tram.stack[tram.top-1]=tram.stack[tram.top-1]/tram.stack[tram.top]
tram.pop()
tram.pc=tram.pc+1
def toString(self): return super().toString() + "DIV"
class lt(Instruction):
def __init__(self, assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
if tram.stack[tram.top-1]<tram.stack[tram.top]:
tram.stack[tram.top-1]=1
else:
tram.stack[tram.top-1]=0
tram.pop()
tram.pc=tram.pc+1
def toString(self): return super().toString() + "LT"
class gt(Instruction):
def __init__(self, assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
if tram.stack[tram.top-1]>tram.stack[tram.top]:
tram.stack[tram.top-1]=1
else:
tram.stack[tram.top-1]=0
tram.pop()
tram.pc=tram.pc+1
def toString(self): return super().toString() + "GT"
class eq(Instruction):
def __init__(self, assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
if tram.stack[tram.top-1]==tram.stack[tram.top]:
tram.stack[tram.top-1]=1
else:
tram.stack[tram.top-1]=0
tram.pop()
tram.pc=tram.pc+1
def toString(self): return super().toString() + "EQ"
class neq(Instruction):
def __init__(self, assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
if tram.stack[tram.top-1]!=tram.stack[tram.top]:
tram.stack[tram.top-1]=1
else:
tram.stack[tram.top-1]=0
tram.pop()
tram.pc=tram.pc+1
def toString(self): return super().toString() + "NEQ"
class goto(Instruction):
def __init__(self, label, assigned_label=None):
super().__init__(assigned_label=assigned_label)
self.label=label
def execute(self,tram):
tram.pc=self.label.address
def toString(self):
return super().toString() + "GOTO "+self.label.toString()
class ifzero(Instruction):
def __init__(self, label, assigned_label=None):
super().__init__(assigned_label=assigned_label)
self.label=label
def execute(self,tram):
if tram.stack[tram.top]==0:
tram.pc=self.label.address
else:
tram.pc=tram.pc+1
tram.pop()
def toString(self):
return super().toString() + "IFZERO "+self.label.toString()
class invoke(Instruction):
def __init__(self,n,label,d, assigned_label=None):
super().__init__(assigned_label=assigned_label)
self.n=n
self.label=label
self.d=d
def execute(self,tram):
tmp_top=tram.top
tram.push(tram.pc+1)
tram.push(tram.pp)
tram.push(tram.fp)
tram.push(tram.spp(self.d,tram.pp,tram.fp))
tram.push(tram.sfp(self.d,tram.pp,tram.fp))
tram.pp=tmp_top-self.n+1
tram.fp=tmp_top+1
tram.pc=self.label.address
def toString(self):
return super().toString() \
+ "INVOKE "+str(self.n)+" "+self.label.toString()+" "+str(self.d)
class ireturn(Instruction):
def __init__(self, assigned_label=None):
super().__init__(assigned_label=assigned_label)
def execute(self,tram):
res=tram.stack[tram.top]
tram.top=tram.pp
tram.pc=tram.stack[tram.fp]
tram.pp=tram.stack[tram.fp+1]
tram.fp=tram.stack[tram.fp+2]
del tram.stack[tram.top:]
tram.top=tram.top-1
tram.push(res)
def toString(self): return super().toString() + "RETURN"

View File

@@ -0,0 +1,220 @@
# (c) Stephan Diehl, University of Trier, Germany, 2025
import tkinter as tk
from copy import deepcopy
from tkinter import ttk
from tkinter import filedialog
import os
from .assembler import *
from .tram import *
import inspect
import re
"""
test_prog1=[const(1), const(2), const(5), store(1, 0), load(1, 0), add(), halt()]
L4=Label(4)
L7=Label(7)
L15=Label(15)
test_prog2=[const(4),
const(10),
invoke(2,L4,0),
halt(),
load(0,0,assigned_label=L4),
invoke(1,L7,0),
ireturn(),
load(0,0,assigned_label=L7),
load(0,0),
mul(),
load(1,1),
gt(),
ifzero(L15),
load(0,0),
ireturn(),
load(0,0,assigned_label=L15),
load(0,0),
mul(),
ireturn()]
"""
class AbstractMachine(TRAM):
def __init__(self):
self.initial_program = [] #Assembler.read_tram_code("examples/tramprograms/test.tram")
#self.initial_program = test_prog2
print(self.initial_program)
self.reset()
def reset(self):
super().__init__(self.initial_program)
self.program_text = [f"{instr.toString()}" for i, instr in enumerate(self.program)]
self.running = False
def step(self):
if self.pc >= len(self.program):
self.running = False
return ("Error: End of program store reached!", None)
if self.pc == -1:
self.running = False
return ("The program terminated normally!", None)
instr=self.program[self.pc]
self.print_instruction(instr)
instr.execute(self)
return (f"Ausgeführt: {instr.toString()}", instr)
class MachineUI:
def __init__(self, root):
self.root = root
self.machine = AbstractMachine()
self.previous_state = None
self.minimal_vis = False
self.current_instruction = None
root.title("Visual TRAM")
# Frames
control_frame = ttk.Frame(root)
control_frame.pack(pady=10)
display_frame = ttk.Frame(root)
display_frame.pack(padx=10, pady=5, fill="both", expand=True)
# Buttons
ttk.Button(control_frame, text="Start", command=self.start).grid(row=0, column=0, padx=5)
ttk.Button(control_frame, text="Stop", command=self.stop).grid(row=0, column=1, padx=5)
ttk.Button(control_frame, text="Step", command=self.step).grid(row=0, column=2, padx=5)
ttk.Button(control_frame, text="Reset", command=self.reset).grid(row=0, column=3, padx=5)
button = ttk.Button(control_frame, text="Open File", command=self.open_file).grid(row=0, column=4, padx=5)
# Add a label to show the selected file
self.label = ttk.Label(root, text="No file selected.", wraplength=800, justify="center")
self.label.pack(pady=20)
# Add a button to open the file browser
#button = ttk.Button(root, text="Open File", command=self.open_file)
#button.pack(pady=10)
# Textfelder
self.code_text = tk.Text(display_frame, width=35, height=32, wrap="none", bg="#f8f8f8")
self.code_text.pack(side="left", padx=10, pady=10, fill="both", expand=True)
self.code_text.tag_configure('highlight', background='#AAFFAA')
self.prev_state_text = tk.Text(display_frame, width=35, height=32, wrap="none", bg="#f0f8ff")
self.prev_state_text.pack(side="left", padx=10, pady=10, fill="both", expand=True)
self.state_text = tk.Text(display_frame, width=35, height=32, wrap="none", bg="#f0f8ff")
self.state_text.pack(side="left", padx=10, pady=10, fill="both", expand=True)
self.instr_text = tk.Text(display_frame, width=70, height=32, wrap="none", bg="#f0f8ff")
self.instr_text.pack(side="right", padx=10, pady=10, fill="both", expand=True)
self.state_text.tag_configure('blue', background='#AAAAFF')
self.prev_state_text.tag_configure('blue', background='#AAAAFF')
self.update_display()
def update_display(self):
# Programmkode anzeigen
self.code_text.delete("1.0", tk.END)
for i, line in enumerate(self.machine.program_text):
if i == self.machine.pc:
prefix = "-> "
self.code_text.insert(tk.END, f"{prefix}{i:02d}: {line}\n",'highlight')
else:
self.code_text.insert(tk.END, f" {i:02d}: {line}\n")
# Maschinenzustand anzeigen
self.update_state_display(self.machine, self.state_text)
if self.previous_state!=None:
self.update_state_display(self.previous_state, self.prev_state_text)
else:
self.prev_state_text.delete("1.0", tk.END)
self.previous_state = deepcopy(self.machine)
# Aktuell ausgeführte Instruktion anzeigen
if self.current_instruction != None:
(msg,instr) = self.current_instruction
self.instr_text.delete("1.0", tk.END)
self.instr_text.insert(tk.END, f"\n{msg}\n\n")
if instr!=None:
source_text= inspect.getsource(instr.execute)
source_text=source_text.replace("tram.","")
rest = source_text.split('\n', 1)[1] if '\n' in source_text else source_text
source_text = '\n'.join(line.lstrip() for line in rest.splitlines())
self.instr_text.insert(tk.END, source_text)
else:
self.instr_text.delete("1.0", tk.END)
def update_state_display(self, machine, tk_text):
tk_text.delete("1.0", tk.END)
tk_text.insert(tk.END, f"PC : {machine.pc}\n")
tk_text.insert(tk.END, f"FP : {machine.fp}\n")
tk_text.insert(tk.END, f"PP : {machine.pp}\n")
tk_text.insert(tk.END, f"TOP: {machine.top}\n\n")
if (self.minimal_vis == True):
tk_text.insert(tk.END, f"Stack: {machine.stack}\n")
else:
for i, value in enumerate(machine.stack):
suffix = " "
if i == machine.top: suffix += "<-TOP "
if i == machine.pp: suffix += "<-PP "
if i == machine.fp: suffix += "<-FP "
if i == machine.fp + 4: suffix += "<-end of frame "
if i >= machine.pp and i < machine.fp + 5:
tk_text.insert(tk.END, f"{i:02d}: {value}{suffix}\n", 'blue')
else:
tk_text.insert(tk.END, f"{i:02d}: {value}{suffix}\n")
def start(self):
self.machine.running = True
self.run_next()
def stop(self):
self.machine.running = False
def step(self):
self.current_instruction = self.machine.step()
self.update_display()
def run_next(self):
if self.machine.running:
self.step()
#msg = self.machine.step()
#self.update_display()
#if msg:
# self.state_text.insert(tk.END, f"\n{msg}\n")
if self.machine.running:
self.root.after(500, self.run_next) # Automatische Ausführung
def reset(self):
self.machine.reset()
self.previous_state=None
self.current_instruction=None
self.update_display()
def open_file(self):
# Define the starting directory
start_dir = os.path.expanduser("examples/tramprograms")
# Open the file browser starting in start_dir
filename = filedialog.askopenfilename(
initialdir=start_dir,
title="Select a File",
filetypes=(("TRAM files", "*.tram"), ("All files", "*.*"))
)
# Display the selected file path in the label
if filename:
self.label.config(text=f"Selected file:\n{filename}")
self.machine.initial_program = Assembler.read_tram_code(filename) #examples/tramprograms/test.tram")
self.reset()
if __name__ == "__main__":
root = tk.Tk()
app = MachineUI(root)
root.mainloop()