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

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