Source code for fbrelation.syntax.box
'''
Defines classes for parsing and compiling box declarations, which consist of a
name followed by an attribute list in square brackets. Box declarations must
occupy a single line and must not contain arrows (`->`)::
<name> [<attributelist>]
'''
import re
from fbrelation.utility import find
from fbrelation.exceptions import ParsingError, CompilationError
from fbrelation.syntax.attributelist import AttributeListSyntax
from fbrelation.declarations.box import FunctionBoxDeclaration, \
MacroInputBoxDeclaration, \
MacroOutputBoxDeclaration, \
MacroBoxDeclaration, \
SenderBoxDeclaration, \
ReceiverBoxDeclaration
[docs]class BoxSyntax(object):
'''
Represents the abstract syntax of a box declaration, which has a name and
an attribute list.
'''
[docs] def __init__(self, name, attributeList):
'''
Initializes a new box syntax object with the given name and attribute
list.
'''
self.name = name
self.attributes = attributeList
[docs] def __getitem__(self, key):
'''
Allows the use of square-bracket notation to retrieve the value of
one of this box's attributes.
'''
return self.attributes[key]
[docs] def __str__(self):
'''
Converts the syntax object into its raw string representation.
'''
return '%s [%s]' % (self.name, str(self.attributes))
[docs] def compile(self, boxes, relations):
'''
Checks and compiles the abstract syntax structure to create a new
:class:`.BoxDeclaration` object of the appropriate subclass.
:param boxes: The list of box declarations compiled so far.
:param relations: The list of relation declarations compiled so far.
:returns: the newly created box declaration.
:raises: a :class:`.CompilationError` if any static checks fail.
'''
# Ensure that the box name is not a duplicate
if find(lambda b: b.name == self.name, boxes):
raise CompilationError(
'"%s": A box by the name of "%s" already exists.' %
(str(self), self.name))
# Declare a helper function to determine whether an attribute has
# been included
includes = lambda s: s in self.attributes and self.attributes[s]
# For a plain function box, ensure that both group name and box typ
# name are included
if ((includes('group') and not includes('type')) or
(includes('type') and not includes('group'))):
raise CompilationError(
'"%s": Function boxes must have both group and type specified '
'as attributes.' % str(self))
# Check the attributes to determine which type of box this might be
isFunction = includes('group') and includes('type')
isMacroInput = includes('input')
isMacroOutput = includes('output')
isMacro = includes('macro')
isSender = includes('sender')
isReceiver = includes('receiver')
# Declare helpers to check how many of these conditions is True
count_true = lambda xs: reduce(
lambda acc, x: acc + 1 if x else acc, xs, 0)
exactly_one = lambda xs: count_true(xs) == 1
# Ensure that exactly one of these cases is true: no more, no less
if not exactly_one((isFunction, isMacroInput, isMacroOutput,
isMacro, isSender, isReceiver)):
raise CompilationError(
'"%s": Invalid combination of attributes for a box '
'declaration.' % str(self))
# Finally, create a box declaration of the appropriate class
if isFunction:
return FunctionBoxDeclaration(
self.name, self['group'], self['type'])
if isMacroInput:
return MacroInputBoxDeclaration(self.name, self['input'])
if isMacroOutput:
return MacroOutputBoxDeclaration(self.name, self['output'])
if isMacro:
# Require that the given macro name matches an existing relation
relation = find(lambda r: r.name == self['macro'], relations)
if not relation:
raise CompilationError(
'"%s": No relation constraint named "%s" yet exists.' %
(str(self), self['macro']))
return MacroBoxDeclaration(self.name, relation)
if isSender:
return SenderBoxDeclaration(self.name, self['sender'])
if isReceiver:
return ReceiverBoxDeclaration(self.name, self['receiver'])
# We should never reach this point
assert False
@classmethod
[docs] def parse(cls, text):
'''
Parses the given input text to produce a new BoxSyntax object.
:returns: the newly created box syntax object.
:raises: a :class:`.ParsingError` if syntax is invalid.
'''
# Ensure that there are no extraneous brackets to confuse the regex
if text.count('[') != 1 or text.count(']') != 1:
raise ParsingError(
'"%s": Invalid syntax for a box declaration. Expected a '
'single attribute block enclosed in square brackets.' % text)
# Match the line against a regular expression, capturing the box name
# and the contents of the attribute list within the square brackets
match = re.match(r'([^\[\]]*)\[([^\[\]]*)\]', text)
if not match:
raise ParsingError(
'"%s": Invalid syntax for a box declaration. Expected box '
'name followed by an attribute block.' % text)
name, attributeText = match.groups()
# Parse the attribute list and create a new BoxSyntax object
return cls(name.strip(), AttributeListSyntax.parse(attributeText))