aboutsummaryrefslogtreecommitdiffstats
path: root/subv.py
diff options
context:
space:
mode:
Diffstat (limited to 'subv.py')
-rw-r--r--subv.py235
1 files changed, 170 insertions, 65 deletions
diff --git a/subv.py b/subv.py
index 146d544..107f0e9 100644
--- a/subv.py
+++ b/subv.py
@@ -1,30 +1,98 @@
import re
-white = re.compile('[ \t\.\n]+')
+white = re.compile(r'[ \t\.\n]+')
hex = re.compile(r'^\-?(0x)?[0-9a-f]+$')
slice_re = re.compile(r'^([^\[]+)(?:\[(\d+):(\d+)\])?$')
field_re = re.compile(r'^(imm|off)(\d+)$')
# parsing
-
def parse_part(part):
+ """ parse a literal with attached metadata.
+
+ >>> parse_part('0')
+ (0,)
+ >>> parse_part('00')
+ (0,)
+ >>> parse_part('0x00')
+ (0,)
+
+ >>> parse_part('12')
+ (18,)
+ >>> parse_part('0x12')
+ (18,)
+
+ >>> parse_part('-12')
+ (-18,)
+ >>> parse_part('-0x12')
+ (-18,)
+
+ >>> parse_part('00/with/tag')
+ (0, 'with', 'tag')
+ >>> parse_part('-12/and/tag')
+ (-18, 'and', 'tag')
+
+ >>> parse_part('label/tag*')
+ ('label', 'tag*')
+ >>> parse_part('$label/tag*')
+ ('$label', 'tag*')
+ >>> parse_part('label:suff/tag*')
+ ('label:suff', 'tag*')
+ >>> parse_part('$label:suff/tag*')
+ ('$label:suff', 'tag*')
+ >>> parse_part('label[3:1]/tag*')
+ ('label[3:1]', 'tag*')
+ >>> parse_part('$label:suff[11:0]/tag*')
+ ('$label:suff[11:0]', 'tag*')
+ """
part = part.split('/')
if hex.match(part[0]):
part[0] = int(part[0], 16)
return tuple(part)
def parse_instr(line):
+ """ parse an instruction-line.
+
+ >>> parse_instr('ff/op 0/subop/add 1/rd/x1 label[11:0]/imm12')
+ [(255, 'op'), (0, 'subop', 'add'), (1, 'rd', 'x1'), ('label[11:0]', 'imm12')]
+ """
parts = white.split(line)
parts = [parse_part(part) for part in parts if part != '']
return parts
def parse_segment(line):
+ """ parse a segment-line.
+
+ >>> parse_segment('== code 0x8000')
+ ('code', 32768)
+ >>> parse_segment('== text')
+ ('text',)
+ """
parts = white.split(line)
- return (parts[1], int(parts[2], 16))
+ if len(parts) == 3:
+ return (parts[1], int(parts[2], 16))
+ elif len(parts) == 2:
+ return (parts[1],)
+ else:
+ raise ValueError("invalid segment line")
def parse_label(line):
+ """ parse a label-line.
+
+ >>> parse_label('some_label:')
+ 'some_label'
+ >>> parse_label('$some:label:')
+ '$some:label'
+ """
return line[:-1]
def parse_reference(part):
+ """ parse a sliced-label part.
+
+ >>> parse_reference(('lbl', 'imm32'))
+ {'label': 'lbl', 'mode': 'imm', 'size': 32, 'meta': ()}
+
+ >>> parse_reference(('x[11:0]', 'off12'))
+ {'label': 'x', 'mode': 'off', 'size': 12, 'meta': (), 'hi': 11, 'lo': 0}
+ """
ref = part[0]
field = part[1]
@@ -45,6 +113,21 @@ def parse_reference(part):
return ref
def classify(line):
+ """ classify cleaned lines.
+
+ >>> classify('')
+ 'empty'
+ >>> classify('== code')
+ 'segment'
+ >>> classify('== text 0x1000')
+ 'segment'
+ >>> classify('some_label:')
+ 'label'
+ >>> classify('$some:label:')
+ 'label'
+ >>> classify('ff/op 0/subop/add 1/rd/x1 label[11:0]/imm12')
+ 'instr'
+ """
if line == '':
return 'empty'
elif line.startswith('=='): # segment
@@ -55,6 +138,8 @@ def classify(line):
return 'instr'
def parse(line):
+ """ clean, classify and parse lines.
+ """
raw = line.strip()
split = raw.split('#', 1)
if len(split) == 1:
@@ -82,81 +167,101 @@ def parse(line):
type: parsed,
}
-def is_lref(part):
+def is_reference(part):
+ """ check whether a part is a label reference.
+
+ >>> is_reference(('hello',))
+ True
+ >>> is_reference(('hello:world',))
+ True
+ >>> is_reference(('$label',))
+ True
+ >>> is_reference(('$label:extra',))
+ True
+ >>> is_reference(('$label:extra', 'off20'))
+ True
+ >>> is_reference(('plain', 'off20'))
+ True
+
+ >>> is_reference((0,))
+ False
+ >>> is_reference((1,))
+ False
+ >>> is_reference((1, 'disp20'))
+ False
+ >>> is_reference((0x13f, 'imm12'))
+ False
+ """
return isinstance(part[0], str)
def untag(part, expect=None):
+ """ returns the value of a part and optionally verifies the first tag.
+
+ >>> untag((2, 'num'))
+ 2
+ >>> untag(('$label', 'imm20'))
+ '$label'
+ >>> untag((2, 'hello', 'things'))
+ 2
+
+ >>> untag((2, 'num'), expect='num')
+ 2
+ >>> untag(('$label', 'imm20'), expect='imm20')
+ '$label'
+
+ >>> untag((2, 'imm12'), expect='imm20')
+ Traceback (most recent call last):
+ ...
+ ValueError: expected (2, 'imm12') to be labelled imm20
+ >>> untag(('$label', 'imm20'), expect='off12')
+ Traceback (most recent call last):
+ ...
+ ValueError: expected ('$label', 'imm20') to be labelled off12
+ """
if expect and part[1] != expect:
raise ValueError("expected {} to be labelled {}".format(part, expect))
return part[0]
def format_part(part):
- if not is_lref(part):
+ """ oppposite of parse_part.
+
+ >>> format_part((0,))
+ '00'
+ >>> format_part((0x00,))
+ '00'
+
+ >>> format_part(('label', 'tag*'))
+ 'label/tag*'
+ >>> format_part(('$label', 'tag*'))
+ '$label/tag*'
+ >>> format_part(('label:suff', 'tag'))
+ 'label:suff/tag'
+ >>> format_part(('$label:suff', 'tag'))
+ '$label:suff/tag'
+ """
+ if not is_reference(part):
first = '{:02x}'.format(part[0])
part = (first, *part[1:])
return '/'.join([str(p) for p in part])
-def format_instr(inst, comment=None):
- packed = ' '.join(format_part(part) for part in inst)
- if comment:
- packed = packed + ' # ' + comment
- return packed
+def format(line):
+ """ oppposite of parse.
+
+ >>> format({
+ ... 'type': 'instr',
+ ... 'instr': [(255, 'op'), (0, 'subop', 'add'), (1, 'rd', 'x1'), ('label[11:0]', 'imm12')],
+ ... 'comment': "this does things."
+ ... })
+ 'ff/op 00/subop/add 01/rd/x1 label[11:0]/imm12 # this does things.'
+ """
+ if line['type'] == 'instr':
+ packed = ' '.join(format_part(part) for part in line['instr'])
+ if line['comment']:
+ packed = packed + ' # ' + line['comment']
+ return packed
+ else:
+ raise NotImplementedError()
def join_all(gen):
res = '\n'.join(gen)
return res
-
-import unittest
-class TestParsing(unittest.TestCase):
- def test_parse_part(self):
- self.assertEqual(parse_part('0'), (0,))
- self.assertEqual(parse_part('00'), (0,))
- self.assertEqual(parse_part('0x00'), (0,))
-
- self.assertEqual(parse_part('12'), (0x12,))
- self.assertEqual(parse_part('0x12'), (0x12,))
-
- self.assertEqual(parse_part('-12'), (-0x12,))
- self.assertEqual(parse_part('-0x12'), (-0x12,))
-
- self.assertEqual(parse_part('00/with/tag'), (0, 'with', 'tag'))
- self.assertEqual(parse_part('-12/and<</tag*'), (-0x12, 'and<<', 'tag*'))
-
- self.assertEqual(parse_part('label/tag*'), ('label', 'tag*'))
- self.assertEqual(parse_part('$label/tag*'), ('$label', 'tag*'))
- self.assertEqual(parse_part('label:suff/tag*'), ('label:suff', 'tag*'))
- self.assertEqual(parse_part('$label:suff/tag*'), ('$label:suff', 'tag*'))
-
- def test_parse_reference(self):
- self.assertEqual(
- parse_reference(('lbl', 'imm32')),
- { 'label': 'lbl', 'hi': None, 'lo': None, 'mode': 'imm', 'size': 32, 'meta': () }
- )
- self.assertEqual(
- parse_reference(('x[11:0]', 'off12')),
- { 'label': 'x', 'hi': 11, 'lo': 0, 'mode': 'off', 'size': 12, 'meta': () }
- )
-
-class TestChecks(unittest.TestCase):
- def test_is_lref(self):
- self.assertTrue(is_lref(('hello',)))
- self.assertTrue(is_lref(('hello:world',)))
- self.assertTrue(is_lref(('$label',)))
- self.assertTrue(is_lref(('$label:extra',)))
- self.assertTrue(is_lref(('$label:extra','disp20u')))
- self.assertTrue(is_lref(('plain','disp20u')))
-
- self.assertFalse(is_lref((0,)))
- self.assertFalse(is_lref((1,)))
- self.assertFalse(is_lref((1,'disp20u')))
- self.assertFalse(is_lref((0x13f,'imm12')))
-
-class TestFormatting(unittest.TestCase):
- def test_format_part(self):
- self.assertEqual(format_part((0,)), '00')
- self.assertEqual(format_part((0x00,)), '00')
-
- self.assertEqual(format_part(('label', 'tag*')), 'label/tag*')
- self.assertEqual(format_part(('$label', 'tag*')), '$label/tag*')
- self.assertEqual(format_part(('label:suff', 'tag')), 'label:suff/tag')
- self.assertEqual(format_part(('$label:suff', 'tag')), '$label:suff/tag')