Source code for fbrelation.syntax.relation
'''
Defines classes for parsing and compiling relation declarations, which consist
of a name, then a series of newline-separated box and connection declarations
inside a block of curly braces. Shell-style comments (prefixed with `#`) may be
included within the body::
name
{
<box|connection>
<box|connection>
...
<box|connection>
}
'''
from fbrelation.exceptions import ParsingError
from fbrelation.syntax.box import BoxSyntax
from fbrelation.syntax.connection import ConnectionSyntax
from fbrelation.declarations.relation import RelationDeclaration
[docs]class RelationSyntax(object):
'''
Represents the abstract syntax of a relation constraint declaration,
consisting of a name, a list of box syntax objects, and a list of
connection syntax objects. Note that order is preserved among boxes and
among connections respectively, but that the order of connections relative
to boxes is insignificant.
'''
[docs] def __init__(self, name, boxes, connections):
'''
Initializes a new relation syntax object with the given name and the
the provided box and connection structures.
'''
self.name = name
self.boxes = boxes
self.connections = connections
[docs] def __str__(self):
'''
Converts the syntax object into its raw string representation.
'''
boxStrings = [str(box) for box in self.boxes]
connectionStrings = [str(connection) for connection in self.connections]
return '%s\n{\n %s\n\n %s\n}' % (
self.name,
'\n '.join(boxStrings),
'\n '.join(connectionStrings))
[docs] def compile(self, relations):
'''
Checks and compiles this relation constraint from its abstract syntax
into a new :class:`.RelationDeclaration.` object.
:param relations: The list of relation declarations compiled so far.
Used to resolve macro references.
:returns: the newly created relation declaration.
:raises: a :class:`.CompilationError` if any static checks fail.
'''
# Compile each relation one-by-one, accumulating them into a list
boxDeclarations = []
for boxSyntax in self.boxes:
box = boxSyntax.compile(boxDeclarations, relations)
boxDeclarations.append(box)
# With all the boxes compiled, compile all of the connections and use
# both to construct and return a new relation declaration
return RelationDeclaration(
self.name,
boxDeclarations,
[c.compile(boxDeclarations) for c in self.connections])
@classmethod
[docs] def parse(cls, text):
'''
Parses the given input text to produce a new RelationSyntax object.
:returns: the newly created syntax object.
:raises: a :class:`.ParsingError` if syntax is invalid.
'''
# Ensure that there are no extraneous braces to confuse things
if text.count('{') != 1 or text.count('}') != 1:
raise ParsingError(
'Invalid syntax for a relation constraint declaration. '
'Expected a single block enclosed in curly braces.')
# Get the position of each curly brace so we can slice the input text
openingPos = text.find('{')
closingPos = text.find('}')
# Slice up to the opening brace to get the name of the constraint
name = text[:openingPos].strip()
if not name:
raise ParsingError(
'Invalid syntax for a relation constraint declaration. '
'Expected the block to be explicitly named.')
# Collect the individual declarations (either box or connection)
# line-by-line
boxDeclarationsText = []
connectionDeclarationsText = []
for line in cls.splitBodyLines(text[openingPos+1:closingPos]):
if '->' in line:
connectionDeclarationsText.append(line)
else:
boxDeclarationsText.append(line)
# Parse the collected input text and use the resulting data structures
# to construct a new RelationSyntax object
return RelationSyntax(
name,
[BoxSyntax.parse(t) for t in boxDeclarationsText],
[ConnectionSyntax.parse(t) for t in connectionDeclarationsText])
@classmethod
[docs] def splitBodyLines(cls, text):
'''
Given the input text for a relation constraint definition's body,
returns a list of the individual lines of that definition, omitting
whitespace and comments.
'''
def remove_comments(line):
'''
Returns the given line stripped of any comments.
'''
hashPos = line.find('#')
return line[:hashPos] if hashPos >= 0 else line
# Remove comments, strip whitespace, and return only non-blank lines
lines = map(str.strip, map(remove_comments, text.splitlines()))
return [l for l in lines if l]