diff options
Diffstat (limited to 'subv.py')
| -rw-r--r-- | subv.py | 235 |
1 files changed, 170 insertions, 65 deletions
@@ -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') |
