109 lines
3.5 KiB
Python
109 lines
3.5 KiB
Python
|
"""
|
||
|
Parsers CAM languages for quick construction of CAMs.
|
||
|
|
||
|
For example, when considering CAMs of life, most follow the same formula; check
|
||
|
the total number of cells in a given neighborhood and if there are a certain
|
||
|
number around an off cell, turn it on, and vice versa. Thus the parser takes
|
||
|
in a generic language regarding this and constructs the necessary functions for
|
||
|
the user.
|
||
|
|
||
|
@date: June 4th, 2015
|
||
|
"""
|
||
|
import re
|
||
|
|
||
|
import ruleset as r
|
||
|
import configuration as c
|
||
|
|
||
|
|
||
|
class InvalidFormat(Exception):
|
||
|
"""
|
||
|
Called when parsing an invalid format.
|
||
|
|
||
|
For example, in MCell and RLE, numbers should be in ascending order.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, value):
|
||
|
self.value = value
|
||
|
|
||
|
def __str__(self):
|
||
|
return repr(self.value)
|
||
|
|
||
|
|
||
|
class CAMParser:
|
||
|
"""
|
||
|
The following builds rulesets based on the passed string.
|
||
|
|
||
|
Following notation is supported:
|
||
|
* MCell Notation (x/y)
|
||
|
* RLE Format (By/Sx)
|
||
|
|
||
|
For reference: http://en.wikipedia.org/wiki/Life-like_cellular_automaton
|
||
|
"""
|
||
|
|
||
|
RLE_FORMAT = r'B\d*/S\d*$'
|
||
|
MCELL_FORMAT = r'\d*/\d*$'
|
||
|
|
||
|
def __init__(self, notation, cam):
|
||
|
"""
|
||
|
Parses the passed notation and saves values into members.
|
||
|
|
||
|
@sfunc: Represents the function that returns the next given state.
|
||
|
@ruleset: A created ruleset that matches always
|
||
|
@offsets: Represents the Moore neighborhood corresponding to the given CAM
|
||
|
"""
|
||
|
self.sfunc = None
|
||
|
self.offsets = c.Configuration.moore(cam.master)
|
||
|
self.ruleset = r.Ruleset(r.Ruleset.Method.ALWAYS_PASS)
|
||
|
|
||
|
if re.match(CAMParser.MCELL_FORMAT, notation):
|
||
|
x, y = notation.split('/')
|
||
|
if all(map(self._numasc, [x, y])):
|
||
|
self.sfunc = self._mcell(x, y)
|
||
|
else:
|
||
|
raise InvalidFormat("Non-ascending values in MCELL format")
|
||
|
|
||
|
elif re.match(CAMParser.RLE_FORMAT, notation):
|
||
|
B, S = map(lambda x: x[1:], notation.split('/'))
|
||
|
if all(map(self._numasc, [B, S])):
|
||
|
self.sfunc = self._mcell(S, B)
|
||
|
else:
|
||
|
raise InvalidFormat("Non-ascending values in RLE format")
|
||
|
|
||
|
else:
|
||
|
raise InvalidFormat("No supported format passed to parser.")
|
||
|
|
||
|
# Add configuration to given CAM
|
||
|
config = c.Configuration(self.sfunc, plane=cam.master, offsets=self.offsets)
|
||
|
self.ruleset.configurations.append(config)
|
||
|
|
||
|
|
||
|
def _numasc(self, value):
|
||
|
"""
|
||
|
Check the given value is a string of ascending numbers.
|
||
|
"""
|
||
|
if all(map(str.isnumeric, value)):
|
||
|
return ''.join(sorted(value)) == value
|
||
|
else:
|
||
|
return False
|
||
|
|
||
|
|
||
|
def _mcell(self, x, y):
|
||
|
"""
|
||
|
MCell Notation
|
||
|
|
||
|
A rule is written as a string x/y where each of x and y is a sequence of distinct digits from 0 to 8, in
|
||
|
numerical order. The presence of a digit d in the x string means that a live cell with d live neighbors
|
||
|
survives into the next generation of the pattern, and the presence of d in the y string means that a dead
|
||
|
cell with d live neighbors becomes alive in the next generation. For instance, in this notation,
|
||
|
Conway's Game of Life is denoted 23/3
|
||
|
"""
|
||
|
x, y = list(map(int, x)), list(map(int, y))
|
||
|
def next_state(plane, neighborhood, *args):
|
||
|
if plane.grid.flat[neighborhood.flat_index]:
|
||
|
return int(neighborhood.total in x)
|
||
|
else:
|
||
|
return int(neighborhood.total in y)
|
||
|
|
||
|
return next_state
|
||
|
|