Dodal generator za obj datoteke, in teste, popravil parserctx da pravilno obravnava RSUB

This commit is contained in:
Timon 2026-01-05 16:43:07 +01:00
parent 8ea00ddb32
commit b6e5f3d593
30 changed files with 177 additions and 57 deletions

View file

@ -1,9 +1,8 @@
from zbirnik.opcodes import OPCODES
class EmitContext:
REGISTERS = {
'A': 0, 'X': 1, 'L': 2,
'B': 3, 'S': 4, 'T': 5, 'F': 6
}
REGISTERS = {'A': 0, 'X': 1, 'L': 2,'B': 3, 'S': 4, 'T': 5, 'F': 6}
def __init__(self, symtab):
self.opcodes = OPCODES

View file

@ -5,3 +5,4 @@ class AddrMode(Enum):
SIMPLE = auto()
IMMEDIATE = auto()
INDIRECT = auto()
EXTENDED = auto()

View file

@ -0,0 +1,89 @@
from zbirnik.code import Code
from zbirnik.EmitCtx import EmitContext
class Generate_obj:
def __init__(self, code: Code, emit_ctx: EmitContext):
self.code = code
self.records = []
self.emit_ctx = emit_ctx
def generate(self) -> str:
h_record = self.generate_h_record()
t_records = self.generate_t_records()
#m_records = self.generate_m_records() nimam podpore za EXTREF tako da ne bo M recordou
e_record = self.generate_e_record()
return '\n'.join([h_record] + t_records + [e_record])
def generate_h_record(self) -> str:
ime = (self.code.name).ljust(6)
start = f"{self.code.start_address:06X}"
dolzina= f"{self.code.program_length:06X}"
return f"H^{ime}^{start}^{dolzina}"
def generate_t_records(self) -> list[str]:
t_records = []
current_start_addr = None
current_bytes = bytearray()
for node in self.code.nodes:
chunk = node.emit(self.emit_ctx)
# If node generates no code (like RESB, RESW, START, END)
if not chunk:
# Flush current T record if we have accumulated bytes
if current_bytes:
t_record = self._make_t_record(current_start_addr, current_bytes)
t_records.append(t_record)
current_bytes = bytearray()
current_start_addr = None
continue
# If this is the first byte in a new T record, set starting address
if current_start_addr is None:
current_start_addr = node.address
# Add this node's bytes to current accumulation
current_bytes.extend(chunk)
# If we've accumulated 30 or more bytes, flush a T record
while len(current_bytes) >= 30:
# Take first 30 bytes
record_bytes = current_bytes[:30]
t_record = self._make_t_record(current_start_addr, record_bytes)
t_records.append(t_record)
# Remove those bytes and update start address
current_bytes = current_bytes[30:]
current_start_addr += 30
# Flush any remaining bytes
if current_bytes:
t_record = self._make_t_record(current_start_addr, current_bytes)
t_records.append(t_record)
return t_records
def _make_t_record(self, start_address: int, bytes_data: bytearray) -> str:
start = f"{start_address:06X}"
length = f"{len(bytes_data):02X}"
hex_code = bytes_data.hex().upper()
return f"T^{start}^{length}^{hex_code}"
def generate_m_records(self) -> list[str]:
pass
def generate_e_record(self) -> str:
entry = self.code.entry_point
if isinstance(entry, str):
entry = self.code.symtab.get(entry, self.code.start_address)
if entry is None:
entry = self.code.start_address
return f"E^{entry:06X}"

View file

@ -3,17 +3,18 @@ from zbirnik.code import Code
from zbirnik.EmitCtx import EmitContext
from zbirnik.mnemoniki.mnemoniki_tabela import MNEMONICS
from zbirnik.parserctx import ParserContext
from zbirnik.gen_obj import Generate_obj
import os
ime = input("Vnesite ime programa (.asm): ")
ime = os.path.join(os.path.dirname(__file__), ime)
ime1 = os.path.join(os.path.dirname(__file__), ime)
if not ime.endswith(".asm"):
if not ime1.endswith(".asm"):
raise ValueError("Ime programa ni v pravi obliki, mora biti: ime.asm")
code = Code()
with open(ime) as f:
with open(ime1) as f:
for line in f:
print("LINE:", line.rstrip())
@ -35,8 +36,19 @@ with open(ime) as f:
code.pass1()
ctx_emit = EmitContext(symtab=code.symtab)
binary = code.pass2(ctx_emit)
print(binary.hex())
print("Rezultat pass2: " + binary.hex())
print("-"*40)
obj_generator = Generate_obj(code=code, emit_ctx=ctx_emit)
obj_data = obj_generator.generate()
izhod = "output/"+ ime.replace(".asm", ".obj")
izhod = os.path.join(os.path.dirname(__file__), izhod)
with open(izhod, "w") as obj:
obj.write(obj_data)
print(obj_data)

View file

@ -63,6 +63,7 @@ MNEMONICS = {
'TD': MnemonicF3m('TD', opcode=0xE0),
'RD': MnemonicF3m('RD', opcode=0xD8),
'WD': MnemonicF3m('WD', opcode=0xDC),
'TIX': MnemonicF3m('TIX', opcode=0x2C),
# Format 4 (razširjeni)
'+LDA': MnemonicF4m('+LDA', opcode=0x00),
'+JSUB': MnemonicF4m('+JSUB', opcode=0x48),

View file

@ -0,0 +1,3 @@
H^TEST ^000000^00000C
T^000000^09^0320030F2003000005
E^000000

View file

@ -0,0 +1,6 @@
H^COPY ^0003E8^00005A
T^0003E8^1E^0320271B20270F202707202703A02A192C070FA0242F201E3B2FF1022039
T^000406^12^292C5B3320033F2FE54F000000000A000014
T^00041B^06^000000000005
T^00043F^03^000418
E^0003E8

View file

@ -1,6 +1,5 @@
import ply.lex
import ply.yacc
#import sys
global at_line_start, seen_mnemonic_or_directive
# Lexer
@ -179,13 +178,3 @@ def p_error(p):
print('Syntax error at EOF')
parser = ply.yacc.yacc()
#if __name__ == '__main__':
# for line in sys.stdin:
# line = line.rstrip('\n\r')
# if line and not line.lstrip().startswith('.'):
# at_line_start = True
# seen_mnemonic_or_directive = False
# result = parser.parse(line, lexer=lexer)
# print(result)

View file

@ -1,6 +1,6 @@
class ParserContext:
def __init__(self, parsed):
# Handle COMMENT special case (if you still need it)
#Comment
if isinstance(parsed, tuple) and parsed[0] == 'COMMENT':
self.label = None
self.mnemonic = 'COMMENT'
@ -8,13 +8,13 @@ class ParserContext:
self.comment = parsed[1]
return
# New parser format: (label, mnemonic, *operands)
#parsed=(label, mnemonic, *operands)
if not isinstance(parsed, tuple) or len(parsed) < 2:
raise ValueError(f"Invalid parsed format: {parsed}")
self.label = parsed[0] # Can be None
self.label = parsed[0]
self.mnemonic = parsed[1]
# All remaining elements are operands
#Ostali so operandi
self.operands = list(parsed[2:]) if len(parsed) > 2 else []
self.comment = None
@ -29,12 +29,10 @@ class ParserContext:
def read_reg(self) -> str:
"""Read a register operand (e.g., 'A', 'X', 'L')"""
op = self.next_op()
# If it's a plain string, return it
if isinstance(op, str):
return op
# If it's a tuple (shouldn't happen for registers), take first element
if isinstance(op, tuple):
return op[0]
return str(op)
def read_num_sym(self):
@ -59,25 +57,22 @@ class ParserContext:
addr_mode = AddrMode.SIMPLE
op = self.next_op()
# Check if operand has a prefix (immediate/indirect/indexed)
# Check for prefix (immediate/indirect/indexed)
if isinstance(op, tuple) and len(op) == 2:
prefix, value = op
if prefix == '#':
addr_mode = AddrMode.IMMEDIATE
elif prefix == '@':
addr_mode = AddrMode.INDIRECT
elif prefix == '+':
# Extended format (SIC/XE)
elif prefix == '+':
addr_mode = AddrMode.EXTENDED
else:
# Unknown prefix, treat as simple
value = op
else:
# Simple operand (no prefix)
# Simple
value = op
# Check for indexed addressing (X register)
# In new parser, indexed would be a second operand that's just 'X'
if self.operands and self.operands[0] == 'X':
self.operands.pop(0)
indexed = True

View file

@ -0,0 +1,3 @@
H^TEST ^000000^00000C
T^000000^09^0320030F2003000005
E^000000

View file

@ -0,0 +1,23 @@
COPY START 1000
LDA ALPHA
ADD BETA
STA GAMMA
LDX ZERO
LOOP LDA BUFFER,X
ADD #1
STA BUFFER,X
TIX COUNT
JLT LOOP
LDA @PTR
COMP #100
JEQ DONE
J LOOP
DONE RSUB
ALPHA WORD 10
BETA WORD 20
GAMMA RESW 1
ZERO WORD 0
COUNT WORD 5
BUFFER RESW 10
PTR WORD GAMMA
END COPY

View file

@ -1,25 +1,7 @@
OPCODES = {
"FIX": 0xC4,
"ADDR": 0x90,
"LDA": 0x00,
"STA": 0x0C,
"JSUB": 0x48,
}
REGISTERS = {
"A": 0,
"X": 1,
"L": 2,
"B": 3,
"S": 4,
"T": 5,
"F": 6,
}
from zbirnik.ukazi.f1 import f1
from zbirnik.EmitCtx import EmitContext
ctx = EmitContext(OPCODES, REGISTERS, {})
ctx = EmitContext({})
node = f1("FIX", None)
result = node.emit(ctx)

View file

@ -10,7 +10,7 @@ class directive(Node):
return 0
def emit(self, ctx):
# BASE directive
# BASE
if self.name == "BASE":
if isinstance(self.operand, str):
ctx.base = ctx.symtab[self.operand]
@ -20,7 +20,7 @@ class directive(Node):
raise ValueError("Invalid BASE operand")
return None
# NOBASE directive
# NOBASE
if self.name == "NOBASE":
ctx.base = None
return None

View file

@ -13,15 +13,20 @@ class f2(Node):
def emit(self, ctx):
opcode = ctx.opcodes[self.mnemonic]
#f2r,f2rr,f2rn
if isinstance(self.r1, str):
r1_val = ctx.REGISTERS[self.r1]
#f2n
else:
r1_val = self.r1
#f2r
if self.r2 is None:
r2_val = 0
#f2rr
elif isinstance(self.r2, str):
r2_val = ctx.REGISTERS[self.r2]
#f2rn
else:
r2_val = self.r2

View file

@ -14,6 +14,16 @@ class f3(Node):
def emit(self, ctx):
opcode = ctx.opcodes[self.mnemonic]
# RSUB
if self.mnemonic == "RSUB":
base_opcode = opcode & 0b11111100
n, i = 1, 1
byte1 = base_opcode | (n << 1) | i
byte2 = 0x00
byte3 = 0x00
return bytes([byte1, byte2, byte3])
base_opcode = opcode & 0b11111100
if self.adr_mode == AddrMode.SIMPLE:
@ -38,6 +48,7 @@ class f3(Node):
disp = target - pc
b = 0
p = 1
#PC relativno
if -2048 <= disp <= 2047:
b = 0

View file

@ -34,6 +34,7 @@ class f4(Node):
raise ValueError("Invalid addressing mode")
byte1 = base_opcode | (n << 1) | i
x = 1 if self.index else 0
b = 0
p = 0

View file

@ -37,7 +37,7 @@ class storage(Node):
if s.startswith("C'") and s.endswith("'"):
chars = s[2:-1]
return bytes(ord(c) for c in chars)
# X'F1A3'
# X'FA3'
if s.startswith("X'") and s.endswith("'"):
hexstr = s[2:-1]
return bytes.fromhex(hexstr)