First implementation of the compiler

This commit is contained in:
Jan-Niclas Loosen
2025-12-08 00:03:18 +01:00
parent 88b8de363d
commit 4e6f93b353
11 changed files with 886 additions and 32 deletions

170
Project-02-03/compiler.py Normal file
View File

@@ -0,0 +1,170 @@
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}")
def elab_def(decls, rho, nl):
r = dict(rho)
for d in decls:
r = d.elab(r, nl)
return r
class CONST(syntax.CONST):
def code(self, rho, nl):
return [const(self.value)]
class ID(syntax.ID):
def code(self, rho, nl):
k, nlp = rho[self.name]
return [load(k, nl - nlp)]
class AOP(syntax.AOP):
def code(self, rho, nl):
c1 = self.arg1.code(rho, nl)
c2 = self.arg2.code(rho, nl)
op = self.operator
if op == "+":
return c1 + c2 + [add()]
if op == "-":
return c1 + c2 + [sub()]
if op == "*":
return c1 + c2 + [mul()]
return c1 + c2 + [div()]
class COMP(syntax.COMP):
def code(self, rho, nl):
c1 = self.arg1.code(rho, nl)
c2 = self.arg2.code(rho, nl)
if self.operator == "<":
return c1 + c2 + [lt()]
if self.operator == ">":
return c1 + c2 + [gt()]
if self.operator == "<=":
return c1 + c2 + [gt(), nop()] # NOT IMPLEMENTED IN TRAM
if self.operator == ">=":
return c1 + c2 + [lt(), nop()] # NOT IMPLEMENTED IN TRAM
return c1 + c2
class EQOP(syntax.EQOP):
def code(self, rho, nl):
c1 = self.arg1.code(rho, nl)
c2 = self.arg2.code(rho, nl)
if self.operator == "==":
return c1 + c2 + [eq()]
return c1 + c2 + [neq()]
class LOP(syntax.LOP):
def code(self, rho, nl):
# TRIPLA: logical operators must be done by short-circuiting (not strict)
# but your TRAM has no boolean ops, so fallback to == and pop
c1 = self.arg1.code(rho, nl)
c2 = self.arg2.code(rho, nl)
if self.operator == "&&":
l = new_label()
return (
c1 +
[ifzero(l)] +
c2 +
[ifzero(l)] +
[const(1)] +
[nop(assigned_label=l)]
)
if self.operator == "||":
l = new_label()
return (
c1 +
[ifzero(l)] +
[const(1)] +
[goto(l)] +
[nop(assigned_label=l)] +
c2
)
return c1 + c2
class ASSIGN(syntax.ASSIGN):
def code(self, rho, nl):
c = self.expr.code(rho, nl)
k, nlp = rho[self.var.name]
d = nl - nlp
return c + [store(k, d), load(k, d)]
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
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)]
)
class WHILE(syntax.WHILE):
def code(self, rho, nl):
l1 = new_label() # loop header
l2 = new_label() # exit
l3 = new_label() # repeat
l4 = new_label() # evaluate body
return (
self.cond.code(rho, nl)
+ [ifzero(l2), goto(l4)]
+ [nop(assigned_label=l1)]
+ self.cond.code(rho, nl)
+ [ifzero(l3), pop()]
+ [nop(assigned_label=l4)]
+ self.body.code(rho, nl)
+ [goto(l1)]
+ [nop(assigned_label=l2), const(None), nop(assigned_label=l3)]
)
class LET(syntax.LET):
def code(self, rho, nl):
l = new_label()
rho2 = elab_def(self.decl, rho, nl)
cd = []
for d in self.decl:
cd += d.code(rho2, nl)
ce = self.body.code(rho2, nl)
return [goto(l)] + cd + [nop(assigned_label=l)] + ce
class DECL(syntax.DECL):
def elab(self, rho, nl):
r = dict(rho)
r[self.f_name] = (new_label(), nl)
return r
def code(self, rho, nl):
l, _ = rho[self.f_name]
rho2 = dict(rho)
for i, p in enumerate(self.params):
rho2[p] = (i, nl + 1)
return (
[nop(assigned_label=l)]
+ self.body.code(rho2, nl + 1)
+ [ireturn()]
)
class CALL(syntax.CALL):
def code(self, rho, nl):
c = []
for a in self.arg:
c += a.code(rho, nl)
l, nlp = rho[self.f_name]
return c + [invoke(len(self.arg), l, nl - nlp)]

View File

View File

@@ -1,3 +1,5 @@
from pathlib import Path
def prompt_choice(prompt: str, options: list) -> int: def prompt_choice(prompt: str, options: list) -> int:
print(prompt) print(prompt)
for i, opt in enumerate(options, 1): for i, opt in enumerate(options, 1):
@@ -18,9 +20,3 @@ def prompt_confirmation(question: str, default="y"):
if not s: if not s:
s = default s = default
return s.startswith("y") return s.startswith("y")
def search_programs():
base = Path(__file__).parent / "triplaprograms"
if not base.exists():
return []
return sorted([f for f in base.glob("*.tripla")])

View File

@@ -1,18 +1,26 @@
import triplayacc as yacc import triplayacc as yacc
import triplalex as lex
import syntax import syntax
from pathlib import Path from pathlib import Path
from graphviz import Source from graphviz import Source
from vistram.tram import *
import tkinter as tk
from vistram.vistram import MachineUI
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.image as mpimg import matplotlib.image as mpimg
import lib.console as cnsl import helpers.console as cnsl
import os import os
import matplotlib import matplotlib
matplotlib.use("TkAgg") 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 # Renders a diagram of the AST
def render_diagram(dot_string: str): def render_diagram(dot_string: str):
# Set DPI for PNG # Set DPI for PNG
@@ -60,41 +68,77 @@ def export_dot_file(dot_string: str, filename: str):
print(f"Could not save DOT file: {e}") print(f"Could not save DOT file: {e}")
if __name__ == "__main__": if __name__ == "__main__":
print("\nTRIPLA Parser Tool") print("\nTRIPLA Parser and TRIPLA to TRAM Compiler")
while True: while True:
choice = cnsl.prompt_choice("\nSelect action:", ["Parse .tripla", "Exit"]) mode = cnsl.prompt_choice("\nSelect action:", ["Parse .tripla", "Compile .tripla", "Exit"])
if choice == 1: if mode == 2:
print("\nBye Bye.") print("\nBye Bye.")
break break
programs = cnsl.search_programs() base = Path(__file__).parent / "triplaprograms"
programs = sorted([f for f in base.glob("*.tripla")])
if not programs: if not programs:
print("\nNo .tripla files found.") print("\nNo .tripla files found.")
continue continue
idx = cnsl.prompt_choice("\nSelect program to parse:", [p.name for p in programs]) idx = cnsl.prompt_choice("\nSelect TRIPLA program:", [p.name for p in programs])
path = programs[idx] path = programs[idx]
source = path.read_text() source = path.read_text()
ast = yacc.parser.parse(source) ast = yacc.parser.parse(source)
# Pretty print if mode == 0:
if cnsl.prompt_confirmation("\nPretty-print AST?"): # Pretty print
print("") if cnsl.prompt_confirmation("\nPretty-print AST?"):
pretty_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"
cnsl = input(f"Filename [{default}]: ").strip()
if not cnsl:
cnsl = default
export_dot_file(dot_str, cnsl)
# 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)
if cnsl.prompt_confirmation("\nPrint TRAM code to console?"):
print("\nGenerated TRAM code:\n")
for instr in tram_code:
print(instr.toString())
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}")
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()
# Export DOT
dot_str = ast.to_dot()
if cnsl.prompt_confirmation("Export AST as .dot file?"):
default = f"{path.stem}.dot"
cnsl = input(f"Filename [{default}]: ").strip()
if not cnsl:
cnsl = default
export_dot_file(dot_str, cnsl)
# Display AST diagram
if cnsl.prompt_confirmation("Display AST diagram?"):
render_diagram(dot_str)
print("Rendered AST diagram.")

View File

@@ -61,7 +61,6 @@ class EXPRESSION:
return "".join(parts) return "".join(parts)
class LET(EXPRESSION): class LET(EXPRESSION):
def __init__(self, decls, body): def __init__(self, decls, body):
super().__init__() super().__init__()

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

@@ -3,7 +3,7 @@
# ------------------------------------------------------------ # ------------------------------------------------------------
import ply.yacc as yacc import ply.yacc as yacc
import syntax as ast import compiler as ast
from triplalex import tokens from triplalex import tokens
# Operator precedence # Operator precedence

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,339 @@
# (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, 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()