Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
jvanstraten committed Jul 29, 2019
1 parent 9bb298a commit bcb024c
Show file tree
Hide file tree
Showing 8 changed files with 558 additions and 5 deletions.
93 changes: 93 additions & 0 deletions tests/test_core_addressing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Tests for the classes defined in `vhdmmio.core.addressing`."""

from collections import OrderedDict
from unittest import TestCase
from vhdmmio.core.addressing import AddressSignal, AddressSignalMap, MaskedAddress, AddressManager

class TestAddressing(TestCase):
"""Tests for the classes defined in `vhdmmio.core.addressing`."""

def test_16450(self):
"""test an AddressManager with 16450's DLAB madness"""
self.maxDiff = None #pylint: disable=C0103
dlab = AddressSignal('dlab')
mgr = AddressManager()
mgr.signals.append(dlab)
mgr.add_mapping('RBR', MaskedAddress(0, 0xFFFFFFFF), 1, 0, {dlab: MaskedAddress(0, 1)})
mgr.add_mapping('THR', MaskedAddress(0, 0xFFFFFFFF), 0, 1, {dlab: MaskedAddress(0, 1)})
mgr.add_mapping('IER', MaskedAddress(1, 0xFFFFFFFF), 1, 1, {dlab: MaskedAddress(0, 1)})
mgr.add_mapping('IIR', MaskedAddress(2, 0xFFFFFFFF), 1, 0)
mgr.add_mapping('LCR', MaskedAddress(3, 0xFFFFFFFF), 1, 1)
mgr.add_mapping('MCR', MaskedAddress(4, 0xFFFFFFFF), 1, 1)
mgr.add_mapping('LSR', MaskedAddress(5, 0xFFFFFFFF), 1, 1)
mgr.add_mapping('MSR', MaskedAddress(6, 0xFFFFFFFE), 1, 1)
mgr.add_mapping('DLL', MaskedAddress(0, 0xFFFFFFFF), 1, 1, {dlab: MaskedAddress(1, 1)})
mgr.add_mapping('DLH', MaskedAddress(1, 0xFFFFFFFF), 1, 1, {dlab: MaskedAddress(1, 1)})
self.assertEqual(list(mgr.doc_iter()), [
(
OrderedDict((
(AddressSignalMap.BUS, MaskedAddress(0, 0xFFFFFFFF)),
(dlab, MaskedAddress(0, 1)))),
'0x00000000, `dlab`=0',
'RBR', 'THR'
),
(
OrderedDict((
(AddressSignalMap.BUS, MaskedAddress(0, 0xFFFFFFFF)),
(dlab, MaskedAddress(1, 1)))),
'0x00000000, `dlab`=1',
'DLL', 'DLL'
),
(
OrderedDict((
(AddressSignalMap.BUS, MaskedAddress(1, 0xFFFFFFFF)),
(dlab, MaskedAddress(0, 1)))),
'0x00000001, `dlab`=0',
'IER', 'IER'
),
(
OrderedDict((
(AddressSignalMap.BUS, MaskedAddress(1, 0xFFFFFFFF)),
(dlab, MaskedAddress(1, 1)))),
'0x00000001, `dlab`=1',
'DLH', 'DLH'
),
(
OrderedDict((
(AddressSignalMap.BUS, MaskedAddress(2, 0xFFFFFFFF)),
(dlab, MaskedAddress(0, 0)))),
'0x00000002',
'IIR', None
),
(
OrderedDict((
(AddressSignalMap.BUS, MaskedAddress(3, 0xFFFFFFFF)),
(dlab, MaskedAddress(0, 0)))),
'0x00000003',
'LCR', 'LCR'
),
(
OrderedDict((
(AddressSignalMap.BUS, MaskedAddress(4, 0xFFFFFFFF)),
(dlab, MaskedAddress(0, 0)))),
'0x00000004',
'MCR', 'MCR'
),
(
OrderedDict((
(AddressSignalMap.BUS, MaskedAddress(5, 0xFFFFFFFF)),
(dlab, MaskedAddress(0, 0)))),
'0x00000005',
'LSR', 'LSR'
),
(
OrderedDict((
(AddressSignalMap.BUS, MaskedAddress(6, 0xFFFFFFFE)),
(dlab, MaskedAddress(0, 0)))),
'0x00000006/1',
'MSR', 'MSR'
)])
with self.assertRaisesRegex(
ValueError, r'address conflict between SCRP \(0x00000007\) and '
r'MSR \(0x00000006/1\) at 0x00000007, `dlab`=0 in read mode'):
mgr.add_mapping('SCRP', MaskedAddress(7, 0xFFFFFFFF), 1, 1)
17 changes: 17 additions & 0 deletions vhdmmio/configurable/configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class Configurable:
output_directory = None

def __init__(self, parent=None, dictionary=None, **kwargs):
super().__init__()
self._frozen = False

# Save the parent.
self._parent = parent

Expand Down Expand Up @@ -131,6 +134,18 @@ def save(self, obj=None):

raise TypeError('unsupported input for save() API')

# Configurables can be frozen to prevent further mutation.
@property
def frozen(self):
"""Returns whether this configurable has been frozen."""
return self._frozen

def freeze(self):
"""Freezes this configurable, shielding it against further mutation."""
self._frozen = True
for loader in self.loaders:
loader.freeze(getattr(self, '_' + loader.key))

# A key aspect of `Configurable`s is that they can automatically generate
# markdown documentation for their configuration dictionary. These
# parameters are set by the `@configurable()` annotation.
Expand Down Expand Up @@ -230,6 +245,8 @@ def getter(self, loader=loader):
# function). define a setter as well.
if loader.mutable():
def setter(self, value, loader=loader):
if self.frozen:
raise ValueError('cannot modify frozen configurable')
loader.validate(value)
setattr(self, '_' + loader.key, value)
else:
Expand Down
41 changes: 37 additions & 4 deletions vhdmmio/configurable/listconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ def serialize(self, dictionary, value):
"""`ListConfig` serializer. See `Loader.serialize()` for more info."""
dictionary[self.key] = [item.serialize() for item in value]

@staticmethod
def freeze(value):
"""Prevents the value managed by this loader (passed to the method)
from being mutated further."""
value.freeze()


class ProtectedList:
"""Wrapper for Python list that ensures that the type of objects added to
Expand All @@ -140,10 +146,27 @@ def __init__(self, configurable, initial=None):
super().__init__()
self._configurable = configurable
self._list = []
self._frozen = False
if initial is not None:
self.extend(initial)

def validate(self, value):
@property
def frozen(self):
"""Returns whether this list has been frozen."""
return self._frozen

def freeze(self):
"""Freezes the contents of this list."""
self._frozen = True
for item in self._list:
item.freeze()

def _mutable_check(self):
"""Raises an exception if we're frozen."""
if self.frozen:
raise ValueError('cannot mutate frozen protected list')

def _validate(self, value):
"""Checks the type of the given value to make sure it can go into the
list."""

Expand All @@ -161,10 +184,12 @@ def __getitem__(self, index):
return self._list[index]

def __setitem__(self, index, value):
self.validate(value)
self._mutable_check()
self._validate(value)
self._list[index] = value

def __delitem__(self, index):
self._mutable_check()
del self._list[index]

def __iter__(self):
Expand All @@ -178,29 +203,35 @@ def __contains__(self, value):

def append(self, value):
"""Appends to the internal list, ensuring value is acceptable."""
self.validate(value)
self._mutable_check()
self._validate(value)
self._list.append(value)

def extend(self, iterable):
"""Extends the internal list, ensuring value is acceptable."""
self._mutable_check()
for value in iterable:
self.append(value)

def insert(self, index, value):
"""Inserts into the internal list, ensuring value is acceptable."""
self.validate(value)
self._mutable_check()
self._validate(value)
self._list.insert(index, value)

def remove(self, value):
"""Removes a value from the list."""
self._mutable_check()
self._list.remove(value)

def pop(self, index=-1):
"""Pops a value from the list."""
self._mutable_check()
return self._list.pop(index)

def clear(self):
"""Clears the list."""
self._mutable_check()
self._list.clear()

def index(self, *args):
Expand All @@ -213,10 +244,12 @@ def count(self, value):

def sort(self, *args, **kwargs):
"""Chains to list.sort()."""
self._mutable_check()
return self._list.sort(*args, **kwargs)

def reverse(self):
"""Chains to list.reverse()."""
self._mutable_check()
return self._list.reverse()

def copy(self):
Expand Down
5 changes: 5 additions & 0 deletions vhdmmio/configurable/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ def validate(_):
appropriate ParseError if not. This function only needs to work if
`mutable()` returns `True`."""

@staticmethod
def freeze(_):
"""Prevents the value managed by this loader (passed to the method)
from being mutated further."""


class ScalarLoader(Loader):
"""Base class for scalar configuration key loaders, i.e. loaders that
Expand Down
6 changes: 6 additions & 0 deletions vhdmmio/configurable/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ def validate(self, value):
if value.parent is not self:
raise ValueError('value must have been initialized with us as the parent')

@staticmethod
def freeze(value):
"""Prevents the value managed by this loader (passed to the method)
from being mutated further."""
value.freeze()


def select(method):
"""Method decorator for configuring a `configurable`-annotated class
Expand Down
Loading

0 comments on commit bcb024c

Please # to comment.