instead is adding ".value" all over the place where it isn't necessary. I haven't actually tried this but it looks like dropping the Cell class entirely, putting that line in as the definition of registers, then s/.values//g ought to work, or very nearly work.
PC, SP, and O are already defined as variables containing the index for that register, a fine way to do it.
The reason for the Cell() is explained in a comment; I have to be able to pass around references to registers and memory locations distinct from their value. I'm open to alternatives but the above won't work as how do you pass in "register A" or "memory location 0x1000" as the arg to an instruction method in that case?
As a offset, probably, with appropriate changes. You're probably better off channeling C design here than Python. I'm running on the assumption that while speed may not be your overriding priority, you will want this to run with some speed. I haven't examined the opcodes, but in this case even if I had to distinguish between a number and a register reference I'd probably do something like let numbers be numbers and let register references be one-element tuples containing the register offset, then switch on the type when it came time to try to use them. That is most likely going to be significantly faster than going through the very powerful class/instance machinery on Python, and should you be inclined to play with PyPy it'll probably JIT a heck of a lot better too. (Although I'd also play with having a number with a very high bit set to indicate that it's a register reference, which would probably JIT even better, since there'd be no type check.)
By splitting the difference and playing with PyPy you should be able to use Python to dodge out on a lot of the C bookkeeping BS while potentially not paying very much on the speed penalty. Using a lot of Python constructs could result in a multiple orders of magnitude slowdown for only marginal gain in this case.
I did think about passing around a (type, identifier) tuple where type = REGISTER|MEMORY|LITERAL but I was put off by writing code conditional on type. The OO programmer in me dies a little when that is done rather than polymorphism.
Match the tools to the task. Organizational schemes suitable for multi-hundred-thousand line codebases aren't always needed for something like this, which just isn't going to get that large. Old-school bitbashing can be both fast and easy enough to read. OO can cost you a lot here for not very much gain.
Or whatever. Your program, of course. (No sarcasm.)
As soon as the spec popped up, I've been into implementing DCPU-16 in Python on my own too, and I quickly encountered the same issue. Currently, my Cell-equivalent class (which I called Pointer) defines __call__, so I can do a() to read and a(data) to set.
It arguably sucks less, but I'm looking for something better and currently trying various solutions involving __getitem__, closures, function attributes, decorators and a sprinkle of metaprogramming to keep things nicely separated and much less C-ish.
I've already defined opcodes this way (decorator+func) which makes it very descriptive and almost reduces the opcode dispatcher to a one-liner.
I haven't read the spec. or followed the topic in general, but from a quick glance at it: the size of the registers isn't given, and the registers aren't memory-mapped.
Why not use an integer instead of Cell? 42 is memory address 42, -3 is register R, including for SP, PC, etc. Or addresses >= 0x10000 are registers, then you can just have
def ife(self, a, b):
m = self.mem
self.skip = m[a] != m[b]
with Python's native indexing of the mem[] list, which could be an array().
only issues is there's a third type to consider which is a literal number.
So I need to distinguish Register, Memory Location and Literal. I considered passing around a (type, value) tuple but conditional code based on type really tells the OO programmer in me that polymorphism should be used instead.
Couldn't cells be split from "normal", and used as proxies only when needed (when the cell actually needs to be passed around)? And lazily instantiated (but memoized so they can be reused)?
Does not fix the issue jtauber pointed out: you can't pass around a section of an array "by reference" so that people can set stuff in e.g. register A.
PC, SP, and O are already defined as variables containing the index for that register, a fine way to do it.