initial
This commit is contained in:
239
asm/asm.py
Executable file
239
asm/asm.py
Executable file
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
from typing import List
|
||||
from dataclasses import dataclass
|
||||
from struct import pack
|
||||
|
||||
class AssemblerError(Exception):
|
||||
pass
|
||||
|
||||
@dataclass
|
||||
class Instruction:
|
||||
name: str
|
||||
number: int
|
||||
load_jr: bool = False
|
||||
|
||||
class Assembler:
|
||||
def __init__(self):
|
||||
self.prog = bytearray()
|
||||
self.lineno = 0
|
||||
self.labels = {}
|
||||
self.label_references = []
|
||||
self.logger = logging.getLogger("asm")
|
||||
instructions = [
|
||||
Instruction("NOOP", 0), # Come on, we just need one
|
||||
Instruction("ID", 1),
|
||||
Instruction("TAG", 2),
|
||||
|
||||
Instruction("MOVE", 3),
|
||||
Instruction("SENSE", 4),
|
||||
Instruction("PROBE", 5),
|
||||
|
||||
Instruction("PICKUP", 6),
|
||||
Instruction("CARRYING", 7),
|
||||
Instruction("DROP", 8),
|
||||
|
||||
Instruction("SMELL", 9),
|
||||
Instruction("SNIFF", 0xa),
|
||||
Instruction("MARK", 0xb),
|
||||
|
||||
Instruction("SET", 0x10),
|
||||
Instruction("AND", 0x11),
|
||||
Instruction("OR", 0x12),
|
||||
Instruction("XOR", 0x13),
|
||||
Instruction("LSHIFT", 0x14),
|
||||
Instruction("RSHIFT", 0x15),
|
||||
Instruction("ADD", 0x16),
|
||||
Instruction("SUB", 0x17),
|
||||
Instruction("MUL", 0x18),
|
||||
Instruction("DIV", 0x19),
|
||||
Instruction("MOD", 0x1a),
|
||||
Instruction("RANDOM", 0x1b),
|
||||
|
||||
Instruction("SJR", 0x20), # New 'hidden' instruction to support jmp instructions
|
||||
Instruction("MJR", 0x21), # New 'hidden' instruction to support jmp instruction from reg
|
||||
Instruction("JMP", 0x22, True),
|
||||
Instruction("CALL", 0x23, True),
|
||||
Instruction("JEQ", 0x24, True),
|
||||
Instruction("JNE", 0x25, True),
|
||||
Instruction("JGT", 0x26, True),
|
||||
Instruction("JLT", 0x27, True)
|
||||
|
||||
|
||||
]
|
||||
self.instructions = {i.name:i for i in instructions}
|
||||
self.constants = {
|
||||
"r0": 0x80,
|
||||
"r1": 0x81,
|
||||
"r2": 0x82,
|
||||
"r3": 0x83,
|
||||
"r4": 0x84,
|
||||
"r5": 0x85,
|
||||
"r6": 0x86,
|
||||
"r7": 0x87,
|
||||
"jr": 0x88,
|
||||
"empty": 0,
|
||||
"wall": 1,
|
||||
"food": 2,
|
||||
"nest": 3,
|
||||
"ant": 4,
|
||||
"here": 0,
|
||||
"n": 1,
|
||||
"e": 2,
|
||||
"s": 3,
|
||||
"w": 4,
|
||||
"north": 1,
|
||||
"east": 2,
|
||||
"south": 3,
|
||||
"west": 4,
|
||||
"random": 5,
|
||||
"ch_red": 0,
|
||||
"ch_blue": 1,
|
||||
"ch_green": 2,
|
||||
"ch_yellow": 3,
|
||||
|
||||
}
|
||||
|
||||
def write(self, bt):
|
||||
if type(bt) == int:
|
||||
self.prog += bytes([bt])
|
||||
else:
|
||||
self.prog += bt
|
||||
#print(self.prog.hex())
|
||||
|
||||
def handle_label(self, iname):
|
||||
lblname = iname[:-1].lower()
|
||||
if lblname in self.labels:
|
||||
raise AssemblerError(f"Label {lblname} defined twice")
|
||||
self.labels[lblname] = len(self.prog) // 4
|
||||
|
||||
def handle_directive(self, iname, args):
|
||||
if iname in ['.alias', '.const', '.tag']:
|
||||
source = args[0] if iname == '.tag' else args[1]
|
||||
dest = args[1].lower() if iname == '.tag' else args[0].lower()
|
||||
|
||||
self.logger.debug(f"{iname} {dest} = {self.resolve_arg(source)}")
|
||||
if dest in self.constants:
|
||||
raise AssemblerError(f"Overwriting const {dest}")
|
||||
self.constants[dest] = self.resolve_arg(source)
|
||||
else:
|
||||
raise AssemblerError(f"Invalid directive {iname}")
|
||||
|
||||
def resolve_arg(self, arg, raise_if_fail=True):
|
||||
arg = arg.lower()
|
||||
if arg in self.constants:
|
||||
result = self.constants[arg]
|
||||
elif arg.isnumeric():
|
||||
result = int(arg)
|
||||
elif arg.startswith("0x"):
|
||||
result = int(arg, 16)
|
||||
elif raise_if_fail:
|
||||
raise AssemblerError(f"Cant parse argument {arg}")
|
||||
else:
|
||||
result = 0
|
||||
if result > 255:
|
||||
raise AssemblerError(f"Arg {arg} value bigger than 255")
|
||||
return result
|
||||
|
||||
def handle_load_jr(self, instruction, target):
|
||||
target_reg = self.resolve_arg(target, raise_if_fail=False)
|
||||
if instruction.name == "JMP" and target_reg in range(0x80, 0x88):
|
||||
mjr = self.instructions["MJR"]
|
||||
data = [mjr.number, target_reg, 0, 0]
|
||||
else:
|
||||
sjr = self.instructions["SJR"]
|
||||
data = [sjr.number, 0, 0, 0]
|
||||
offset = len(self.prog) + 1
|
||||
self.label_references.append((target, offset))
|
||||
self.write(bytes(data))
|
||||
|
||||
def handle_instruction(self, iname, args):
|
||||
instruction = self.instructions[iname.upper()]
|
||||
if len(args) > 3:
|
||||
raise AssemblerError("Too many arguments")
|
||||
data = [instruction.number]
|
||||
if instruction.load_jr:
|
||||
target = args[-1]
|
||||
self.handle_load_jr(instruction, target)
|
||||
args = args[:-1]
|
||||
for i in range(3):
|
||||
if i >= len(args):
|
||||
data.append(0)
|
||||
else:
|
||||
data.append(self.resolve_arg(args[i]))
|
||||
|
||||
self.write(bytes(data))
|
||||
|
||||
def assemble_line(self, line):
|
||||
pre_offset = len(self.prog)
|
||||
line = line.strip()
|
||||
# remove comments
|
||||
parts = line.split(";", 1)
|
||||
if len(parts) == 2:
|
||||
line = parts[0]
|
||||
|
||||
# split into parts
|
||||
parts = line.split()
|
||||
if len(parts) == 0:
|
||||
return
|
||||
iname = parts[0]
|
||||
|
||||
if iname.endswith(":"):
|
||||
self.handle_label(iname)
|
||||
elif iname.startswith("."):
|
||||
self.handle_directive(iname, parts[1:])
|
||||
elif iname.upper() in self.instructions:
|
||||
self.handle_instruction(iname, parts[1:])
|
||||
else:
|
||||
raise AssemblerError(f"Instruction {iname} not found")
|
||||
new_data = self.prog[pre_offset:]
|
||||
self.logger.debug(f"{line.strip()} : {new_data.hex()}")
|
||||
|
||||
def resolve_references(self):
|
||||
for reflabel, refoffset in self.label_references:
|
||||
if not reflabel in self.labels:
|
||||
raise AssemblerError(f"Could not resolve label {reflabel}")
|
||||
lblloc = self.labels[reflabel]
|
||||
self.prog[refoffset] = lblloc & 0xff
|
||||
self.prog[refoffset + 1] = lblloc >> 8 & 0xff
|
||||
self.prog[refoffset + 2] = lblloc >> 16 & 0xff
|
||||
|
||||
def assemble_text(self, data):
|
||||
for line in data.split("\n"):
|
||||
self.lineno += 1
|
||||
self.assemble_line(line)
|
||||
self.resolve_references()
|
||||
return self.prog
|
||||
|
||||
def assemble_file(self, inf, outf):
|
||||
with open(inf) as asm:
|
||||
assy = asm.read()
|
||||
|
||||
assy = "JMP main\n" + assy
|
||||
prog = self.assemble_text(assy)
|
||||
|
||||
output = sys.stdout.buffer if outf is None else open(outf, 'wb')
|
||||
output.write(pack("<I", len(prog) >> 2)) # This is the header of the file: num_instructions
|
||||
output.write(prog)
|
||||
if output != sys.stdout:
|
||||
output.close()
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
assembler = Assembler()
|
||||
parser = argparse.ArgumentParser(description='Assemble a program.')
|
||||
parser.add_argument('file', help='File to assemble')
|
||||
parser.add_argument('--output', metavar='FILE',
|
||||
help='Output file (stdout if ommitted)')
|
||||
parser.add_argument('--debug', '-d', action='store_true')
|
||||
args = parser.parse_args()
|
||||
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
|
||||
try:
|
||||
assembler.assemble_file(args.file, args.output)
|
||||
except AssemblerError as e:
|
||||
sys.stderr.write(f"Error at line: {assembler.lineno}: {e}\n")
|
||||
sys.exit(1)
|
||||
11
asm/test.ant
Normal file
11
asm/test.ant
Normal file
@@ -0,0 +1,11 @@
|
||||
SET r5 0xaa
|
||||
main:
|
||||
ID r0
|
||||
SET r5 0x55
|
||||
ADD r5 0x5
|
||||
RSHIFT r5 4
|
||||
LSHIFT r5 4
|
||||
RANDOM r1 0x1f
|
||||
RANDOM r2 10
|
||||
SNIFF CH_RED
|
||||
MOVE RANDOM
|
||||
135
asm/twotrail.ant
Normal file
135
asm/twotrail.ant
Normal file
@@ -0,0 +1,135 @@
|
||||
; ── SWARM FORAGER ────────────────────────────
|
||||
; Random walk to find food, dead reckoning to
|
||||
; navigate home. Pheromone marks food for others.
|
||||
; Hit play, then try improving it!
|
||||
|
||||
; ── Registers ──
|
||||
.alias dx r1 ; x displacement from nest
|
||||
.alias dy r2 ; y displacement from nest
|
||||
.alias ret r3 ; return address for CALL
|
||||
|
||||
; ── Pheromone ──
|
||||
.const PH_FOOD CH_RED ; marks food locations
|
||||
.const PH_NEST CH_BLUE
|
||||
.const PHER_INIT 255
|
||||
.const PHER_DECAY 2
|
||||
; ── Roles (visible in simulation viewer) ──
|
||||
.tag 0 foraging
|
||||
.tag 1 homing
|
||||
|
||||
; ──────────────────────────────────────────────
|
||||
main:
|
||||
set r7 PHER_INIT
|
||||
loop:
|
||||
CARRYING
|
||||
JNE r0 0 go_home
|
||||
TAG foraging
|
||||
|
||||
; ── SEARCH: find food ─────────────────────────
|
||||
search:
|
||||
SENSE FOOD
|
||||
JNE r0 0 grab
|
||||
|
||||
; if we are too far from the food, dont mark
|
||||
JLT r7 PHER_DECAY smell_trail
|
||||
SUB r7 PHER_DECAY
|
||||
; if there is already a trail here, dont mark
|
||||
SNIFF PH_NEST HERE r0
|
||||
JNE r0 0 smell_trail
|
||||
MARK PH_NEST r7
|
||||
|
||||
smell_trail:
|
||||
SMELL PH_FOOD
|
||||
JNE r0 0 step
|
||||
SENSE EMPTY
|
||||
step:
|
||||
CALL ret move_track
|
||||
JMP loop
|
||||
|
||||
grab:
|
||||
CALL ret move_track
|
||||
PICKUP
|
||||
MARK PH_FOOD 255
|
||||
SET r6 PHER_INIT
|
||||
JMP loop
|
||||
|
||||
; ── GO HOME: dead reckon toward nest ──────────
|
||||
go_home:
|
||||
TAG homing
|
||||
; if we are too far from the food, dont mark
|
||||
JLT r6 PHER_DECAY sense_nest
|
||||
SUB r6 PHER_DECAY
|
||||
; if there is already a trail here, dont mark
|
||||
SNIFF PH_FOOD HERE r0
|
||||
JNE r0 0 sense_nest
|
||||
MARK PH_FOOD r6
|
||||
sense_nest:
|
||||
SENSE NEST
|
||||
JNE r0 0 deliver
|
||||
smell_nest:
|
||||
SMELL PH_NEST
|
||||
JNE r0 0 move_track
|
||||
RANDOM r0 4
|
||||
JEQ r0 0 wander ; 25% random to unstick walls
|
||||
CALL ret home_dir
|
||||
CALL ret move_track
|
||||
JMP main
|
||||
wander:
|
||||
SENSE EMPTY
|
||||
CALL ret move_track
|
||||
JMP main
|
||||
|
||||
deliver:
|
||||
CALL ret move_track
|
||||
DROP
|
||||
SET r7 PHER_INIT
|
||||
JMP main
|
||||
|
||||
; ══════════════════════════════════════════════
|
||||
; SUBROUTINES — CALL ret <label>, return JMP ret
|
||||
; ══════════════════════════════════════════════
|
||||
|
||||
; home_dir: set r0 = direction toward nest
|
||||
home_dir:
|
||||
SET r4 dx
|
||||
JGT dx 0 hd_dxp
|
||||
SET r4 0
|
||||
SUB r4 dx
|
||||
hd_dxp:
|
||||
SET r5 dy
|
||||
JGT dy 0 hd_dyp
|
||||
SET r5 0
|
||||
SUB r5 dy
|
||||
hd_dyp:
|
||||
JGT r5 r4 hd_y
|
||||
JGT dx 0 hd_w
|
||||
SET r0 2
|
||||
JMP ret
|
||||
hd_w:
|
||||
SET r0 4
|
||||
JMP ret
|
||||
hd_y:
|
||||
JGT dy 0 hd_n
|
||||
SET r0 3
|
||||
JMP ret
|
||||
hd_n:
|
||||
SET r0 1
|
||||
JMP ret
|
||||
|
||||
; move_track: MOVE r0, then update dx/dy
|
||||
move_track:
|
||||
MOVE r0
|
||||
JEQ r0 1 mt_n
|
||||
JEQ r0 2 mt_e
|
||||
JEQ r0 3 mt_s
|
||||
SUB dx 1
|
||||
JMP ret
|
||||
mt_n:
|
||||
SUB dy 1
|
||||
JMP ret
|
||||
mt_e:
|
||||
ADD dx 1
|
||||
JMP ret
|
||||
mt_s:
|
||||
ADD dy 1
|
||||
JMP ret
|
||||
Reference in New Issue
Block a user