Just use InstructionBlock, no more WITH

This commit is contained in:
Neil Webber 2023-10-23 10:12:36 -05:00
parent 33b771e700
commit df73e5dbf2

View file

@ -46,14 +46,6 @@ class PDP11InstructionAssembler:
B6MODES[f"@-({_rn})"] = 0o50 | _i # autodecr deferred B6MODES[f"@-({_rn})"] = 0o50 | _i # autodecr deferred
del _i, _rn, _rnames del _i, _rn, _rnames
# see InstructionBlock for explanation of 'with' syntax use
@classmethod
def __enter__(cls):
return InstructionBlock()
def __exit__(self, *args, **kwargs):
return None
def __iter__(self): def __iter__(self):
if self._fwdrefs: if self._fwdrefs:
raise ValueError(f"unresolved refs: " f"{list(self._fwdrefs)}") raise ValueError(f"unresolved refs: " f"{list(self._fwdrefs)}")
@ -354,26 +346,34 @@ class BranchTarget(FwdRef):
# of results from calling the instruction methods. # of results from calling the instruction methods.
# #
# Instead of: # Instead of:
# a = PDP11InstructionAssembler()
# insts = ( # insts = (
# a.mov('r1', 'r2'), # a.mov('r1', 'r2'),
# a.clr('r0'), # a.clr('r0'),
# etc ... # etc ...
# ) # )
# #
# The context manager can be used to write it this way: # An InstructionBlock can be used this way:
# #
# with ASM() as a: # a = InstructionBlock()
# a.mov('r1', 'r2') # a.mov('r1', 'r2')
# a.clr('r0') # a.clr('r0')
# etc ... # etc ...
# #
# which, subject to opinion, may be notationally cleaner/clearer and also # Each call to an instruction method appends that instruction to the block.
# opens the possibility of if/for/etc full programming constructs as needed. # Subject to opinion, this may be notationally cleaner/clearer and also
# opens the possibility of if/for/etc full programming constructs in
# forming the instruction sequence itself.
# #
# The context manager also supports bare-bones labels, helpful for branches # An InstructionBlock adds simple label support as well, useful for branching.
# #
# If treated as an iterable, returns a list of instruction words. # If care is taken to write position-independent code, an InstructionBlock
# Typically used like this: # can eventually be placed at any arbitrary location in memory. When labels
# are used in jmp or jsr instructions, they are automatically assembled
# as PC-relative offsets (mode = 0o67) rather than absolute values.
#
# The current list of instruction words is available by using the
# instruction block as an iterable. For example:
# #
# instlist = list(a) # instlist = list(a)
# #
@ -383,21 +383,9 @@ class BranchTarget(FwdRef):
# raises a ValueError. This can be suppressed (usually only useful # raises a ValueError. This can be suppressed (usually only useful
# for debugging) by requesting a._instructions() # for debugging) by requesting a._instructions()
# #
#
# NOTE: The "with" construct is just notationally convenient; nothing
# happens in context exit and the instruction block continues to
# be valid afterwards. This:
# with ASM() as a:
# a.mov('r1', 'r2')
#
# is completely equivalent to:
#
# a = InstructionBlock()
# a.mov('r1', 'r2')
#
class InstructionBlock(PDP11InstructionAssembler, AbstractContextManager): class InstructionBlock(PDP11InstructionAssembler):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._instblock = [] self._instblock = []
@ -628,7 +616,18 @@ class InstructionBlock(PDP11InstructionAssembler, AbstractContextManager):
"""Generate lines of SIMH deposit commands.""" """Generate lines of SIMH deposit commands."""
for offs, w in enumerate(self): for offs, w in enumerate(self):
yield f"D {oct(startaddr + (2 * offs))[2:]} {oct(w)[2:]}" yield f"D {oct(startaddr + (2 * offs))[2:]} {oct(w)[2:]}\n"
# This method shows one typical way to use the simh generator
#
# A .ini file full of deposit ('D') commands starting at startaddr
# will be created from the instructions in the InstructionBlock
def export_to_simh_ini(self, outfilename, /, *, startaddr=0o10000):
with open(outfilename, 'w') as f:
for s in self.simh(startaddr=startaddr):
f.write(s)
# and set the PC to the start address
f.write(f"D PC {oct(startaddr)[2:]}\n")
if __name__ == "__main__": if __name__ == "__main__":
@ -642,27 +641,27 @@ if __name__ == "__main__":
def test_bne_label_distance(self): def test_bne_label_distance(self):
# this should just execute without any issue # this should just execute without any issue
for i in range(127): for i in range(127):
with ASM() as a: a = InstructionBlock()
a.label('foo') a.label('foo')
for _ in range(i): for _ in range(i):
a.mov('r0', 'r0') a.mov('r0', 'r0')
a.bne('foo') a.bne('foo')
# but this should ValueError ... branch too far # but this should ValueError ... branch too far
with ASM() as a: a = InstructionBlock()
a.label('foo') a.label('foo')
for _ in range(128): for _ in range(128):
a.mov('r0', 'r0') a.mov('r0', 'r0')
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
a.bne('foo') a.bne('foo')
def test_labelmath_dot(self): def test_labelmath_dot(self):
with ASM()as a: a = InstructionBlock()
a.mov('bozo', 'r0') a.mov('bozo', 'r0')
a.label('B') a.label('B')
a.label('BP2', value='. + 2') a.label('BP2', value='. + 2')
a.clr('r0') a.clr('r0')
a.label('bozo') a.label('bozo')
self.assertEqual(a.getlabel('B'), 4) self.assertEqual(a.getlabel('B'), 4)
self.assertEqual(a.getlabel('BP2'), 6) self.assertEqual(a.getlabel('BP2'), 6)
@ -670,50 +669,50 @@ if __name__ == "__main__":
self.assertEqual(list(a)[1], 6) self.assertEqual(list(a)[1], 6)
def test_labelmath_plus(self): def test_labelmath_plus(self):
with ASM() as a: a = InstructionBlock()
a.label('L1', value=17) a.label('L1', value=17)
a.label('L2', value='L1 + 25.') a.label('L2', value='L1 + 25.')
self.assertEqual(a.getlabel('L2'), 42) self.assertEqual(a.getlabel('L2'), 42)
def test_labelmath_minus(self): def test_labelmath_minus(self):
with ASM() as a: a = InstructionBlock()
a.label('L1') a.label('L1')
a.clr('r0') a.clr('r0')
a.label('L2', value='. - L1') a.label('L2', value='. - L1')
self.assertEqual(a.getlabel('L2'), 2) self.assertEqual(a.getlabel('L2'), 2)
def test_unresolved(self): def test_unresolved(self):
with ASM() as a: a = InstructionBlock()
a.br('bozo') a.br('bozo')
a.clr('r0') a.clr('r0')
a.mov(a.getlabel('xyzzy'), 'r0') a.mov(a.getlabel('xyzzy'), 'r0')
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
foo = list(a) foo = list(a)
def test_nocontext(self): def test_identity(self):
a = InstructionBlock() a = InstructionBlock()
a.mov('r0', 'r1') a.mov('r0', 'r1')
a.br('bozo') a.br('bozo')
a.mov('r1', 'r2') a.mov('r1', 'r2')
a.label('bozo') a.label('bozo')
a.mov('r2', 'r3') a.mov('r2', 'r3')
with ASM() as b: b = InstructionBlock()
b.mov('r0', 'r1') b.mov('r0', 'r1')
b.br('bozo') b.br('bozo')
b.mov('r1', 'r2') b.mov('r1', 'r2')
b.label('bozo') b.label('bozo')
b.mov('r2', 'r3') b.mov('r2', 'r3')
self.assertEqual(list(a), list(b)) self.assertEqual(list(a), list(b))
def test_sob(self): def test_sob(self):
for i in range(63): # 0..62 because the sob also counts for i in range(63): # 0..62 because the sob also counts
with self.subTest(i=i): with self.subTest(i=i):
with ASM() as a: a = InstructionBlock()
a.label('foosob') a.label('foosob')
for _ in range(i): for _ in range(i):
a.mov('r0', 'r0') a.mov('r0', 'r0')
inst = a.sob(0, 'foosob') inst = a.sob(0, 'foosob')
self.assertEqual(len(inst), 1) self.assertEqual(len(inst), 1)
self.assertEqual(inst[0] & 0o77, i+1) self.assertEqual(inst[0] & 0o77, i+1)
unittest.main() unittest.main()