#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
pyFuckery - memory.py
Created on 2/12/17.
Memory object implementation. Provides memory bounds checking, as well as value enforcement.
"""
# Stdlib
import argparse
import hashlib
import json
import logging
import sys
# Third Party Code
import msgpack
# Custom Code
from fuckery.constants import DEFAULT_MEMORY_SIZE
from fuckery.constants import MEMORY_MAX_VALUE
from fuckery.constants import MEMORY_MIN_VALUE
from fuckery.exc import AddressError
from fuckery.exc import StorageError
log = logging.getLogger(__name__)
[docs]class Storage(object):
"""
Provides an interface for storing memory values for the Brainfuck VM.
This provides for type safety & memory access checking.
"""
def __init__(self, n=DEFAULT_MEMORY_SIZE):
"""
Init function for Storage.
:param n: Number of memory cells to create.
"""
self.n = n
self.min = MEMORY_MIN_VALUE
self.max = MEMORY_MAX_VALUE
self.mem = {i: 0x00 for i in range(self.n)}
@property
def mem_hash(self):
"""
Returns a hash of the state of the memory.
Note - Computing this frequently can be expensive to do as the memory section is
serialized via msgpack.dumps() prior to hashing.
:return:
"""
# We're abusing the python 3.6 ordereddict behavior which
# became a part of the cpython spec in 3.7 for this
# to work. It really only works since we pre-initialize
# the self.mem dictionary on startup.
ret = hashlib.md5(msgpack.dumps(self.mem)).hexdigest()
return ret
def __contains__(self, item):
return item in self.mem
def __len__(self):
return len(self.mem)
[docs] def get(self, addr):
"""
Get the value of the memory at a location.
:param addr: Memory address to retrieve.
:return:
"""
if addr not in self:
raise AddressError(f'Address is invalid: {addr}')
return self.mem.get(addr)
[docs] def set(self, addr, value):
"""
Set the value of the memory at a locaiton.
:param addr: Memory address to set.
:param value: Value to set.
:return:
"""
if addr not in self:
raise AddressError(f'Address is invalid: {addr}')
if not isinstance(value, int):
raise StorageError(f'Value is not an int: {type(value)}')
if value < self.min or value > self.max:
raise StorageError(f'Value is out of size bounds: {value}')
self.mem[addr] = value
# noinspection PyMissingOrEmptyDocstring
[docs]def main(options): # pragma: no cover
if not options.verbose:
logging.disable(logging.DEBUG)
m = Storage(n=25)
v = m.get(0)
log.info(f'm[0] is {v}')
m.set(24, 1)
v = m.get(0)
log.info(f'm[24] is {v}')
sys.exit(0)
# noinspection PyMissingOrEmptyDocstring
[docs]def makeargpaser(): # pragma: no cover
parser = argparse.ArgumentParser(description="Memory / Storage runner.")
parser.add_argument('-v', '--verbose', dest='verbose', default=False, action='store_true',
help='Enable verbose output')
return parser
def _main(): # pragma: no cover
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(funcName)s]')
p = makeargpaser()
opts = p.parse_args()
main(opts)
if __name__ == '__main__': # pragma: no cover
_main()