aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-05-29 11:50:25 +0000
committers-ol <s-ol@users.noreply.github.com>2020-05-29 11:50:25 +0000
commit517f3e141c3e3bede4e7b0c0ea7237544f28ce90 (patch)
treed8e12207c006bc5e03a0fea3ddbab261ac09e6c7
parentnew label-slice syntax in format.py; switch to doctest (diff)
downloadsubv-517f3e141c3e3bede4e7b0c0ea7237544f28ce90.tar.gz
subv-517f3e141c3e3bede4e7b0c0ea7237544f28ce90.zip
switch bit.py, subv.py to doctest
-rw-r--r--bits.py156
-rwxr-xr-xformat.py90
-rw-r--r--subv.py235
-rwxr-xr-xsurvey.py2
4 files changed, 316 insertions, 167 deletions
diff --git a/bits.py b/bits.py
index 162bcb0..b3409da 100644
--- a/bits.py
+++ b/bits.py
@@ -1,80 +1,122 @@
def u(num, bits):
+ """ parse an unsigned integer into a bitfield.
+
+ >>> u(0x08, 8)
+ (8, 8)
+ >>> u(0xff, 8)
+ (255, 8)
+
+ >>> u(0xf0, 7)
+ Traceback (most recent call last):
+ ...
+ ValueError: value 240 (u8) too large for u7 field
+ >>> u(-1, 8)
+ Traceback (most recent call last):
+ ...
+ ValueError: negative value not allowed: -1
+ """
if num < 0:
raise ValueError("negative value not allowed: {}".format(num))
if num.bit_length() > bits:
- raise ValueError("value too large for u{} field: {} ({} bits)",
- bits, num, num.bit_length())
+ raise ValueError("value {} (u{}) too large for u{} field"
+ .format(num, num.bit_length(), bits))
return (num, bits)
def i(num, bits):
- if num < 0:
- num = (1 << bits) + num
+ """ parse a signed integer into a bitfield.
+
+ >>> i(8, 8)
+ (8, 8)
+ >>> i(-4, 8)
+ (252, 8)
+ >>> i(127, 8)
+ (127, 8)
+ >>> i(-128, 8)
+ (128, 8)
+
+ >>> i(128, 8)
+ Traceback (most recent call last):
+ ...
+ ValueError: value 128 (i9) too large for i8 field [-128;127]
+ >>> i(-129, 8)
+ Traceback (most recent call last):
+ ...
+ ValueError: value -129 (i9) too large for i8 field [-128;127]
+ """
+ min = -(1 << (bits - 1))
+ max = -1 - min
+
+ if num > max or num < min:
+ raise ValueError("value {} (i{}) too large for i{} field [{};{}]"
+ .format(num, num.bit_length()+1, bits, min, max))
+
+ if num < 0:
+ num = (1 << bits) + num
- return u(num, bits)
+ return u(num, bits)
def from_part(part):
- val, size = part
- return (val, int(size))
+ """ parse a size-tagged subv part into a bit.
+
+ >>> from_part((34, 2))
+ (34, 2)
+ >>> from_part((34, 2, 'extra'))
+ (34, 2)
+ """
+ val = int(part[0])
+ size = part[1]
+ return (val, int(size))
def concat(*parts):
+ """ concatenate multiple bitfields.
+
+ >>> concat((0b10, 2), (0b00, 2))
+ (2, 4)
+ >>> concat((0b1, 1), (0b0110, 4), (0b110, 3))
+ (205, 8)
+ """
val, size = 0, 0
for (pval, psize) in parts:
val = val | pval << size
size += psize
return (val, size)
-def slice(bits, top, bottom):
+def slice(bits, hi, lo):
+ """ slice a bitfield given hi and lo bit indices.
+
+ >>> slice((0x7f, 8), 7, 4)
+ (7, 4)
+ >>> slice((0b100, 3), 2, 2)
+ (1, 1)
+ >>> slice((0x12345678, 32), 7, 0)
+ (120, 8)
+ >>> slice((0x12345678, 32), 15, 8)
+ (86, 8)
+ >>> slice((0x12345678, 32), 23, 16)
+ (52, 8)
+
+ >>> slice((0xf, 4), 4, 0)
+ Traceback (most recent call last):
+ ...
+ ValueError: slice [4:0] out of range (4 bit value)
+ >>> slice((0xf, 4), 2, 3)
+ Traceback (most recent call last):
+ ...
+ ValueError: cant slice reverse range
+ >>> slice((0xf, 4), 3, -1)
+ Traceback (most recent call last):
+ ...
+ ValueError: slice [3:-1] out of range (4 bit value)
+ """
(val, size) = bits
- if top < bottom:
+ if hi < lo:
raise ValueError("cant slice reverse range")
- elif bottom < 0:
- raise ValueError("negative slice index")
- elif top >= size:
- raise ValueError("cant slice [{}:{}] from {} bit value".format(top, bottom, size))
-
- width = top - bottom + 1
- val = (val >> bottom) & ((1 << width) - 1)
- return (val, width)
-
-import unittest
-class TestHelpers(unittest.TestCase):
- def test_concat(self):
- self.assertEqual(
- concat((0b10, 2), (0b00, 2)),
- (0b0010, 4)
- )
- self.assertEqual(
- concat((0b1, 1), (0b0110, 4), (0b110, 3)),
- (0b11001101, 8)
- )
-
- def test_slice(self):
- self.assertEqual(
- slice((0x7f, 8), 7, 4),
- (0x7, 4)
- )
- self.assertEqual(
- slice((0b100, 3), 2, 2),
- (0b1, 1)
- )
- with self.assertRaises(ValueError):
- slice((0xf, 4), 4, 0)
- with self.assertRaises(ValueError):
- slice((0xf, 4), 2, 3)
- with self.assertRaises(ValueError):
- slice((0xf, 4), 3, -1)
- self.assertEqual(
- slice((0x12345678, 32), 7, 0),
- (0x78, 8)
- )
- self.assertEqual(
- slice((0x12345678, 32), 15, 8),
- (0x56, 8)
- )
- self.assertEqual(
- slice((0x12345678, 32), 23, 16),
- (0x34, 8)
- )
+ elif lo < 0 or hi >= size:
+ raise ValueError("slice [{}:{}] out of range ({} bit value)".format(hi, lo, size))
+
+ size = hi - lo + 1
+ val = (val >> lo) & ((1 << size) - 1)
+ return (val, size)
diff --git a/format.py b/format.py
index 0fed092..e97fac2 100755
--- a/format.py
+++ b/format.py
@@ -1,5 +1,14 @@
#!/usr/bin/env python3
-"""
+""" format.py
+Verifies instruction formats, orders and pre-bit-packs arguments.
+Every instruction-line in the code segment needs to start with the opcode,
+which has to be correctly tagged. Arguments have to be provided in the correct
+order currently, and their first tag is verified also.
+
+Labels can be referenced in the immXX/offXX fields of most instructions. If no
+bitslice is given for the labels, the default slice is picked based on the
+instruction.
+
>>> from io import StringIO
>>> # doctest: +REPORT_NDIFF
... print(subv.join_all(format(StringIO('''
@@ -57,18 +66,6 @@ main:
import subv
import bits
-instr_map = {
- 'opr': ('r', 0x33),
- 'load': ('i', 0x03),
- 'opi': ('i', 0x13),
- 'jalr': ('i', 0x67),
- 'store': ('s', 0x23),
- 'branch': ('b', 0x63),
- 'lui': ('u', 0x37),
- 'auipc': ('u', 0x17),
- 'jal': ('j', 0x6f),
-}
-
def ref_slice(ref, hi, lo):
if hi < lo:
raise ValueError("cant slice reverse range")
@@ -83,7 +80,8 @@ def ref_slice(ref, hi, lo):
}
def default_slice(val, hi, lo):
- """ ...
+ """ add a default slice spec to labels if missing,
+
>>> default_slice(('label', 'imm12'), 12, 1)
('label[12:1]', 'imm12')
>>> default_slice(('label[14:3]', 'imm12'), 12, 1)
@@ -93,7 +91,7 @@ def default_slice(val, hi, lo):
>>> default_slice(('label[31:0]', 'imm12'), 11, 0)
Traceback (most recent call last):
...
- AssertionError: slice doesnt match metadata
+ AssertionError: expected 12 bit slice
"""
parsed = subv.parse_reference(val)
if 'hi' not in parsed:
@@ -101,13 +99,14 @@ def default_slice(val, hi, lo):
parsed['lo'] = lo
p_size = parsed['hi'] - parsed['lo'] + 1
- assert parsed['size'] == p_size, "slice doesnt match metadata"
+ assert parsed['size'] == p_size, "expected {} bit slice".format(parsed['size'])
ref = '{label}[{hi}:{lo}]'.format(**parsed)
return (ref, *val[1:])
def slice_bit_or_ref(val, hi, lo):
- """ ...
+ """ bit.slice but for bitfields and label references.
+
>>> slice_bit_or_ref(('label[31:0]', 'imm32'), 12, 1)
('label[12:1]', 'imm12')
>>> slice_bit_or_ref(('label[31:0]', 'off32'), 11, 0)
@@ -156,12 +155,12 @@ def pack_u(instr):
>>> pack_u([(0x37, 'lui'), (5, 'rd', 't0'), ('pos[31:0]', 'imm20')])
Traceback (most recent call last):
...
- AssertionError: slice doesnt match metadata
+ AssertionError: expected 20 bit slice
"""
(op, rd, imm) = instr
op = bits.u(subv.untag(op), 7)
rd = bits.u(subv.untag(rd, 'rd'), 5)
- if subv.is_lref(imm):
+ if subv.is_reference(imm):
imm = default_slice(imm, 31, 12)
else:
imm = bits.i(subv.untag(imm, 'imm20'), 20)
@@ -170,6 +169,7 @@ def pack_u(instr):
def pack_i(instr):
""" verify & pack I-type instructions.
+
>>> pack_i([(0x13, 'opi'), (0, 'subop', 'add'), (6, 'rd', 't1'), (0, 'rs', 'x0'), (101, 'imm12')])
[(19, 7), (6, 5), (0, 3), (0, 5), (101, 12)]
@@ -181,7 +181,7 @@ def pack_i(instr):
sub = bits.u(subv.untag(sub, 'subop'), 3)
rd = bits.u(subv.untag(rd, 'rd'), 5)
rs = bits.u(subv.untag(rs, 'rs'), 5)
- if subv.is_lref(imm):
+ if subv.is_reference(imm):
imm = default_slice(imm, 11, 0)
else:
imm = bits.i(subv.untag(imm, 'imm12'), 12)
@@ -189,7 +189,8 @@ def pack_i(instr):
return [op, rd, sub, rs, imm]
def pack_s(instr):
- """
+ """ verify & pack S-type instructions.
+
>>> pack_s([(0x23, 'store'), (2, 'subop', 'word'), (5, 'rs', 't0'), (6, 'rs', 't1'), (0, 'off12')])
[(35, 7), (0, 5), (2, 3), (5, 5), (6, 5), (0, 7)]
@@ -202,7 +203,7 @@ def pack_s(instr):
rs1 = bits.u(subv.untag(rs1, 'rs'), 5)
rs2 = bits.u(subv.untag(rs2, 'rs'), 5)
- if subv.is_lref(imm):
+ if subv.is_reference(imm):
imm = default_slice(imm, 11, 0)
else:
imm = bits.i(subv.untag(imm, 'off12'), 12)
@@ -212,7 +213,8 @@ def pack_s(instr):
return [op, imm_lo, sub, rs1, rs2, imm_hi]
def pack_j(instr):
- """
+ """ verify & pack J-type instructions.
+
>>> pack_j([(0x6f, 'jal'), (0, 'rd', 'x0'), (0, 'off20')])
[(111, 7), (0, 5), (0, 8), (0, 1), (0, 10), (0, 1)]
@@ -226,7 +228,7 @@ def pack_j(instr):
op = bits.u(subv.untag(op), 7)
rd = bits.u(subv.untag(rd, 'rd'), 5)
- if subv.is_lref(imm):
+ if subv.is_reference(imm):
imm = default_slice(imm, 20, 1)
else:
imm = bits.i(subv.untag(imm, 'off20'), 20)
@@ -239,7 +241,8 @@ def pack_j(instr):
return [op, rd, imm_hi, imm_11, imm_lo, imm_20]
def pack_b(instr):
- """
+ """ verify & pack B-type instructions.
+
>>> pack_b([(0x63, 'branch'), (0, 'subop', '=='), (6, 'rs'), (0, 'rs'), (0, 'off12')])
[(99, 7), (0, 1), (0, 4), (0, 3), (6, 5), (0, 5), (0, 6), (0, 1)]
@@ -255,7 +258,7 @@ def pack_b(instr):
rs1 = bits.u(subv.untag(rs1, 'rs'), 5)
rs2 = bits.u(subv.untag(rs2, 'rs'), 5)
- if subv.is_lref(imm):
+ if subv.is_reference(imm):
imm = default_slice(imm, 12, 1)
else:
imm = bits.i(subv.untag(imm, 'off12'), 12)
@@ -266,38 +269,37 @@ def pack_b(instr):
imm_12 = slice_bit_or_ref(imm, 11, 11) # + ('off[12]',)
return [op, imm_11, imm_lo, sub, rs1, rs2, imm_md, imm_12]
+instr_map = {
+ # 'opr': (pack_r, 0x33),
+ 'load': (pack_i, 0x03),
+ 'opi': (pack_i, 0x13),
+ 'jalr': (pack_i, 0x67),
+ 'store': (pack_s, 0x23),
+ 'branch': (pack_b, 0x63),
+ 'lui': (pack_u, 0x37),
+ 'auipc': (pack_u, 0x17),
+ 'jal': (pack_j, 0x6f),
+}
+
def format(iter):
segment = None
for line in iter:
line = subv.parse(line)
if segment == 'code' and line['type'] == 'instr':
- instr = line['instr']
- op = instr[0]
- assert len(op) == 2, 'instruction without op label: {}'.format(instr)
+ op = line['instr'][0]
+ assert len(op) == 2, 'instruction without op label: {}'.format(op)
(op, label) = op
if label not in instr_map:
raise ValueError("unknown instruction label: {}".format(label))
- (format, expected) = instr_map[label]
+ (formatter, expected) = instr_map[label]
if op != expected:
raise ValueError("opcode {} doesn't match label {} (expected {})"
.format(op, label, expected))
- if format == 'u':
- out = pack_u(instr)
- elif format == 'i':
- out = pack_i(instr)
- elif format == 's':
- out = pack_s(instr)
- elif format == 'j':
- out = pack_j(instr)
- elif format == 'b':
- out = pack_b(instr)
- else:
- raise NotImplementedError()
-
- yield subv.format_instr(out)
+ line['instr'] = formatter(line['instr'])
+ yield subv.format(line)
else:
if line['type'] == 'segment':
segment = line['segment'][0]
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')
diff --git a/survey.py b/survey.py
index 3ac7e1c..e3d7a3e 100755
--- a/survey.py
+++ b/survey.py
@@ -15,7 +15,7 @@ def scan(iter):
return map
def observe(part, rel_addr, map):
- if subv.is_lref(part):
+ if subv.is_reference(part):
label, trim, tag = part
trim = int(trim)