# coding=utf-8
"""
This module allows to analyze a USE OCL ``.use`` source file:
* the file is load with ``use`` binary,
* then a ``info model`` command is issue,
* the canonical representation produced is finally parsed.
Either find some errors or create a model (see :class:`pyuseocl.model.Model`)
"""
import os
import re
import pyuseocl.utils.errors
import pyuseocl.utils.sources
import pyuseocl.useengine
import pyuseocl.model
#from pyuseocl.model import Model, Enumeration, Class, Attribute, \
# Operation, Invariant, Association, Role, AssociationClass, \
# PreCondition, PostCondition, BasicType
[docs]class UseOCLModelFile(pyuseocl.utils.sources.SourceFile):
"""
Abstraction of ``.use`` source file.
This source file can be valid or not. In this later case
a reference to the contained model will be available.
"""
def __init__(self, useModelSourceFile):
"""
Analyze the given source file and returns a UseOCLModelFile.
If valid, this object contains a model, otherwise it contains the
list of errors as well as the USE OCL command exit code.
:param useModelSourceFile: The path of the '.use' source file to analyze
:type useModelSourceFile: str
Examples:
see test.pyuseocl.test_analyzer
"""
super(UseOCLModelFile, self).__init__(useModelSourceFile)
#: (bool) Indicates if the model file is valid, that is
#: can be successfully parsed and compiled with use.
self.isValid = None # Don't know yet
self.canonicalLines = None # Nothing yet
self.canonicalLength = 0 # Nothing yet
#: (list[Error]) list of errors if any or empty list.
self.errors = [] # No errors yet
#: (int) exit code of use command.
self.commandExitCode = None # Nothing yet
#: (Model) Model representing the file in a conceptual way
#: or None if self.isValid is false
self.model = None # Nothing yet, created by parse/resolve
# Try to validate the model and fill
# self.isValid
# self.errors,
# self.commandExitCode,
# self.canonicalLines,
# self.canonicalLength
self.__createCanonicalForm()
if self.isValid:
# Create the model by parsing the canonical form
# fill self.model,
self.__parseCanonicalLinesAndCreateModel()
self.__resolveModel()
[docs] def saveCanonicalModelFile(self, fileName=None):
"""
Save the model in the canonical form (returned by "info model")
in a given file.
Args:
fileName (str|NoneType): The output file name or None.
If no file name is provided then the name of the source
is taken but the extension will be '.can.use' instead
of '.use'
Returns:
str: the name of the file generated.
"""
if fileName is None:
fileName = os.path.splitext(self.fileName)[0] + '.can.use'
f = open(fileName, 'w')
f.write('\n'.join(self.canonicalLines))
f.close()
return fileName
[docs] def printStatus(self, a, b, c):
"""
Print the status of the file:
* the list of errors if the file is invalid,
* a short summary of entities (classes, attributes, etc.) otherwise
"""
if self.isValid:
print self.model
else:
print '%s error(s) in the model' % len(self.errors)
for e in self.errors:
print e
#--------------------------------------------------------------------------
# Class implementation
#--------------------------------------------------------------------------
def __createCanonicalForm(self):
engine = pyuseocl.useengine.USEEngine
self.commandExitCode = engine.analyzeUSEModel(self.fileName)
if self.commandExitCode != 0:
self.isValid = False
self.errors = []
for line in engine.err.splitlines():
self.__addErrorFromLine(line)
else:
self.isValid = True
self.errors = []
# Remove 2 lines at the beginning (use intro + command)
# and two lines at the end (information + quit command)
self.canonicalLines = engine.out.splitlines()[2:-2]
self.canonicalLength = len(self.canonicalLines)
return self.isValid
def __addErrorFromLine(self, line):
# testcases/useerrors/bart.use:line 27:26 no viable alternative at input '='
# testcases/useerrors/empty.use:line 1:0 mismatched input '<EOF>' expecting 'model'
# testcases/useerrors/model.use:line 1:5 mismatched input '<EOF>' expecting an identifier
# testcases/useerrors/model.use:2:10: Undefined class `B'.
# testcases/useerrors/card.use:line 4:4 extraneous input 'role' expecting [
# testcases/useerrors/card1.use:4:5: Invalid multiplicity range `1..0'.
# testcases/useerrors/card1.use:8:6: Class `C' cannot be a superclass of itself.
p = r'^(?P<filename>.*)' \
r'(:|:line | line )(?P<line>\d+):(?P<column>\d+)(:)?' \
r'(?P<message>.+)$'
m = re.match(p, line)
if m:
try:
# sometimes the regexp fail.
# e.g. with "ERROR oct. 11, 2015 3:57:00 PM java.util.pref ..."
# print "MATCH",line,m.group('filename'),
pyuseocl.utils.errors.LocalizedError(
sourceFile=self,
message=m.group('message'),
line=int(m.group('line')),
column=int(m.group('column')),
fileName=m.group('filename') # FIXME if needed
)
except:
pyuseocl.utils.errors.SourceError(self, line)
else:
pyuseocl.utils.errors.SourceError(self, line)
def __parseCanonicalLinesAndCreateModel(self):
# self.__matches = None
# self.__i = 0
#
# def until(regexp, force=True):
# self.__matches = None
# while self.__i < self.canonicalLength:
# print self.__i, ':', self.canonicalLines[self.__i]
# self.__matches = re.match(regexp,
# self.canonicalLines[self.__i])
# if self.__matches is not None:
# break
# else:
# self.__i += 1
# if self.__matches is None and force:
# raise Exception(
# 'Error in parsing. Waiting for a line matching %s'
# % regexp)
current_enumeration = None
current_class = None
current_association = None
current_invariant = None
current_operation = None
current_operation_condition = None
for (canonical_line_index,line) in enumerate(self.canonicalLines):
# print '==',line
r = r'^(constraints' \
r'|attributes' \
r'|operations' \
r'| begin' \
r'| end' \
r'|' \
r'|( *@(Test|Monitor)\(.*\)))$'
m = re.match(r, line)
if m:
# these lines can be ignored
continue
#---- model --------------------------------------------------
r = r'^model (?P<name>\w+)$'
m = re.match(r, line)
if m:
self.model = pyuseocl.model.Model(
name=m.group('name'),
code=self.sourceLines)
continue
#---- enumeration on one line (use version 3) ---------------
r = r'^enum (?P<name>\w+) { (?P<literals>.*) };?$'
m = re.match(r, line)
if m:
pyuseocl.model.Enumeration(
name=m.group('name'),
model=self.model,
code=line,
literals=m.group('literals').split(', '))
continue
#---- enumeration header - line # 1 (use version 4) ------------
r = r'^enum (?P<name>\w+) {$'
m = re.match(r, line)
if m:
current_enumeration =\
pyuseocl.model.Enumeration(
name=m.group('name'),
model=self.model,
code=line)
continue
#---- enumeration literals - line #2 (use version 4) ------------
if current_enumeration is not None:
r = r'^ *(?P<literals>[\w_, ]*) *$'
m = re.match(r, line)
if m:
literals=m.group('literals').split(', ')
for literal in literals:
current_enumeration.addLiteral(literal)
continue
#---- enumeration literals - line #3 (use version 4) ------------
if current_enumeration is not None:
r = r' *};$'
m = re.match(r, line)
if m:
current_enumeration = None
continue
#---- class --------------------------------------------------
r = r'^((?P<abstract>abstract) )?class (?P<name>\w+)' \
r'( < (?P<superclasses>(\w+|,)+))?$'
m = re.match(r, line)
if m:
# parse superclasses series
if m.group('superclasses') is None:
superclasses = ()
else:
superclasses = m.group('superclasses').split(',')
current_class = \
pyuseocl.model.Class(
name=m.group('name'),
model=self.model,
isAbstract=m.group('abstract') == 'abstract',
superclasses=superclasses
)
continue
#---- associationclass ---------------------------------------
r = r'^((?P<abstract>abstract) )?associationclass (?P<name>\w+)' \
r'( < (?P<superclasses>(\w+|,)+))?' \
r'( between)?$'
m = re.match(r, line)
if m:
# parse superclasses series
if m.group('superclasses') is None:
superclasses = ()
else:
superclasses = m.group('superclasses').split(',')
ac = \
pyuseocl.model.AssociationClass(
name=m.group('name'),
model=self.model,
isAbstract=m.group('abstract') == 'abstract',
superclasses=superclasses
)
current_class = ac
current_association = ac
continue
#---- attribute ----------------------------------------------
r = r'^ (?P<name>\w+) : (?P<type>\w+)$'
m = re.match(r, line)
if m:
if current_class is not None:
# This could be an association class
pyuseocl.model.Attribute(
name=m.group('name'),
classe_=current_class,
code=line,
type=m.group('type'))
continue
#---- operation ----------------------------------------------
r = r'^ (?P<name>\w+)' \
r'(?P<params_and_result>[^=]*)' \
r'( = )?$'
# r = r'^ (?P<name>\w+)' \
# r'\((?P<parameters>.*)\)' \
# r'( : (?P<type>(\w|,|\))+))?' \
# r'( =)?'
m = re.match(r, line)
if m and '(' in line and ')' in line:
if current_class is not None:
# print line
# This could be an association class
operation = \
pyuseocl.model.Operation(
name=m.group('name'),
model=self.model,
classe_=current_class,
code=line,
signature=m.group('name')
+ m.group('params_and_result'))
if line.endswith(' = '):
current_operation = operation
else:
current_operation = None
#print '==',line
#print ' ', operation.signature
#print ' "%s"' % operation.full_signature
#print
continue
#---- operation expression -----------------------------------
r = r'^ (?P<expression>[^ ].*)$'
m = re.match(r, line)
if m:
if current_operation is not None:
# This could be an association class
current_operation.expression = m.group('expression')
continue
#---- association --------------------------------------------
r = r'^(?P<kind>association|composition|aggregation) ' \
r'(?P<name>\w+) between$'
m = re.match(r, line)
if m:
current_association = \
pyuseocl.model.Association(
name=m.group('name'),
model=self.model,
kind=m.group('kind'))
continue
#---- role ---------------------------------------------------
r = r'^ (?P<type>\w+)\[(?P<cardinality>[^\]]+)\] *' \
r'(role (?P<name>\w+))?' \
r'( qualifier \((?P<qualifiers>(\w| |:|,)*)\))?' \
r'(?P<subsets>( subsets \w+)*)' \
r'( (?P<union>union))?' \
r'( (?P<ordered>ordered))?' \
r'( (derived = (?P<expression>.*)))?$'
m = re.match(r, line)
if m:
if current_association is not None:
# This could be an association class
# Parse the cardinality string
c = m.group('cardinality').split('..')
if c[0] == '*':
min = 0
max = None
elif len(c) == 1:
min = int(c[0])
max = min
else:
min = int(c[0])
max = None if c[1] == '*' else int(c[1])
# Parse the 'subsets' series
if m.group('subsets') == '':
subsets = None
else:
#print '***************', line
#print '** ',m.group('subsets')
subsets = m.group('subsets').split('subsets ')[1:]
#print s
# Parse the 'qualifiers' series
if m.group('qualifiers') is None:
qualifiers = None
else:
qualifiers = \
[tuple(q.split(' : '))
for q in m.group('qualifiers').split(', ')]
pyuseocl.model.Role(
name=m.group('name'), # could be empty, but will get default
association=current_association,
type=m.group('type'),
cardMin=min,
cardMax=max,
isOrdered=m.group('ordered') == 'ordered',
qualifiers=qualifiers,
subsets=subsets,
isUnion=m.group('union') == 'union',
expression=m.group('expression')
)
continue
#---- invariant ----------------------------------------------
r = r'^context (?P<vars>(\w| |,)+) : (?P<class>\w+)' \
r'( (?P<existential>existential))? inv (?P<name>\w+):$'
m = re.match(r, line)
if m:
variables = m.group('vars').split(', ')
current_invariant = \
pyuseocl.model.Invariant(
name=m.group('name'),
model=self.model,
classe_=m.group('class'),
variable=variables[0],
additionalVariables=variables[1:],
isExistential=
m.group('existential') == 'existential',
)
continue
#---- invariant expression -----------------------------------
r = r'^ (?P<expression>[^ ].*)$'
m = re.match(r, line)
if m:
if current_invariant is not None:
current_invariant.expression = m.group('expression')
current_invariant = None
continue
#---- pre or post condition ----------------------------------
r = r'^context (?P<class>\w+)::' \
r'(?P<signature>\w+.*)$'
m = re.match(r, line)
if m:
full_signature = \
'%s::%s' % (m.group('class'), m.group('signature'))
operation = self.model.operations[full_signature]
current_operation_condition = {
'class': m.group('class'),
'full_signature': full_signature,
'operation': operation
}
continue
#---- body of pre or post condition --------------------------
r = r'^ (?P<kind>(pre|post)) (?P<name>\w+): ' \
r'(?P<expression>.*)$'
m = re.match(r, line)
if m:
if current_operation_condition is not None:
operation = current_operation_condition['operation']
v = m.groupdict()
if v['kind'] == 'pre':
pyuseocl.model.PreCondition(
v['name'], self.model, operation, v['expression'])
else:
pyuseocl.model.PostCondition(
v['name'], self.model, operation, v['expression'])
current_operation_condition = None
continue
#---- end of association, class, invariant or operation ------
r = r'^(end)$' # match empty line as well
m = re.match(r, line)
if m:
current_class = None
current_association = None
continue
#---- a line has not been processed.
self.isValid = False
error = 'Parser: cannot process cannonical line #%s: "%s"' % (canonical_line_index, line)
self.errors.append(error)
print error
def __resolveModel(self):
def __resolveSimpleType(name):
""" Search the name in enumeration of basic type or register it.
"""
if name in self.model.enumerations:
return self.model.enumerations[name]
elif name in self.model.basicTypes:
return self.model.basicTypes[name]
else:
self.model.basicTypes[name] = \
pyuseocl.model.BasicType(name)
return self.model.basicTypes[name]
def __resolveClassType(name):
""" Search in class names or association class names.
"""
if name in self.model.classes:
return self.model.classes[name]
else:
return self.model.associationClasses[name]
def __resolveAttribute(attribute):
# Resolve the attribute type
attribute.type = __resolveSimpleType(attribute.type)
def __resolveOperation(operation):
# TODO: implement parsing of parameters and result type
raise NotImplementedError('operation resolution not implemented')
def __resolveClass(class_):
# resolve superclasses
class_.superclasses = \
[__resolveClassType(name) for name in class_.superclasses]
# resolve class attributes
for a in class_.attributes.values():
__resolveAttribute(a)
# resolve class operations
for op in class_.operations:
pass # TODO
def __resolveSubsets(role):
# TODO: implement subsets role search
raise NotImplementedError('subset resolution not implemented')
def __resolveRole(role):
# resolve role type
role.type = __resolveClassType(role.type)
# resolve qualifier types
if role.qualifiers is not None:
qs = role.qualifiers
role.qualifiers = []
for (n, t) in qs:
role.qualifiers.append((n, __resolveSimpleType(t)))
if role.subsets is not None:
for s in role.subsets:
pass # TODO _resolveSubset(role)
if role.association.isBinary:
rs = role.association.roles.values()
role.opposite = rs[1] if role is rs[0] else rs[0]
def __resolveAssociation(association):
association.arity = len(association.roles)
association.isBinary = (association.arity == 2)
for role in association.roles.values():
__resolveRole(role)
def __resolveInvariant(invariant):
c = __resolveClassType(invariant.class_)
invariant.class_ = c
c.invariants[invariant.name] = invariant
# resolve class (and class part of class associations)
cs = self.model.classes.values() \
+ self.model.associationClasses.values()
for c in cs:
__resolveClass(c)
# resolve association (and association part of class association)
as_ = self.model.associations.values() \
+ self.model.associationClasses.values()
for a in as_:
__resolveAssociation(a)
# resolve invariant
for i in self.model.invariants:
__resolveInvariant(i)