root/gaphor/tags/gaphor-0.12.5/gaphor/UML/umllex.py

Revision 2062, 16.7 kB (checked in by arj..@yirdis.nl, 1 year ago)
  • Added contains to element factory
  • fixed some issues with the namespace model.
  • Added visibility field to Association property page.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 """
2 Lexical analizer for attributes and operations.
3
4 In this module some parse functions are added for attributes and operations.
5 The regular expressions are constructed based on a series of
6 "sub-patterns". This makes it easy to identify the autonomy of an
7 attribute/operation.
8 """
9
10 __all__ = [
11         'parse_property', 'parse_operation',
12         'render_property', 'render_operation'
13         ]
14
15 import re
16 from cStringIO import StringIO
17 import gaphor
18
19 # Visibility (optional) ::= '+' | '-' | '#'
20 vis_subpat = r'\s*(?P<vis>[-+#])?'
21
22 # Derived value (optional) ::= [/]
23 derived_subpat = r'\s*(?P<derived>/)?'
24
25 # name (required) ::= name
26 name_subpat = r'\s*(?P<name>[a-zA-Z_]\w*)'
27
28 # Multiplicity (added to type_subpat) ::= '[' [mult_l ..] mult_u ']'
29 mult_subpat = r'\s*(\[\s*((?P<mult_l>[0-9]+)\s*\.\.)?\s*(?P<mult_u>([0-9]+|\*))\s*\])?'
30 multa_subpat = r'\s*(\[?((?P<mult_l>[0-9]+)\s*\.\.)?\s*(?P<mult_u>([0-9]+|\*))\]?)?'
31
32 # Type and multiplicity (optional) ::= ':' type [mult]
33 type_subpat = r'\s*(:\s*(?P<type>\w+)\s*' + mult_subpat + r')?'
34
35 # default value (optional) ::= '=' default
36 default_subpat = r'\s*(=\s*(?P<default>\S+))?'
37
38 # tagged values (optional) ::= '{' tags '}'
39 tags_subpat = r'\s*(\{\s*(?P<tags>.*?)\s*\})?'
40
41 # Parameters (required) ::= '(' [params] ')'
42 params_subpat = r'\s*\(\s*(?P<params>[^)]+)?\)'
43
44 # Possible other parameters (optional) ::= ',' rest
45 rest_subpat = r'\s*(,\s*(?P<rest>.*))?'
46
47 # Direction of a parameter (optional, default in) ::= 'in' | 'out' | 'inout'
48 dir_subpat = r'\s*((?P<dir>in|out|inout)\s)?'
49
50 # Some trailing garbage => no valid syntax...
51 garbage_subpat = r'\s*(?P<garbage>.*)'
52
53 def compile(regex):
54     return re.compile(regex, re.MULTILINE | re.S)
55
56 # Attribute:
57 #   [+-#] [/] name [: type[\[mult\]]] [= default] [{ tagged values }]
58 attribute_pat = compile(r'^' + vis_subpat + derived_subpat + name_subpat + type_subpat + default_subpat + tags_subpat + garbage_subpat)
59
60 # Association end name:
61 #   [[+-#] [/] name [\[mult\]]] [{ tagged values }]
62 association_end_name_pat = compile(r'^' + '(' + vis_subpat + derived_subpat + name_subpat + mult_subpat + ')?' + tags_subpat + garbage_subpat)
63
64 # Association end multiplicity:
65 #   [mult] [{ tagged values }]
66 association_end_mult_pat = compile(r'^' + multa_subpat + tags_subpat + garbage_subpat)
67
68 # Operation:
69 #   [+|-|#] name ([parameters]) [: type[\[mult\]]] [{ tagged values }]
70 operation_pat = compile(r'^' + vis_subpat + name_subpat + params_subpat + type_subpat + tags_subpat + garbage_subpat)
71
72 # One parameter supplied with an operation:
73 #   [in|out|inout] name [: type[\[mult\]] [{ tagged values }]
74 parameter_pat = compile(r'^' + dir_subpat + name_subpat + type_subpat + default_subpat + tags_subpat + rest_subpat)
75
76 # Lifeline:
77 #  [name] [: type]
78 lifeline_pat = compile('^' + name_subpat + type_subpat + garbage_subpat)
79
80 def _set_visibility(self, vis):
81     if vis == '+':
82         self.visibility = 'public'
83     elif vis == '#':
84         self.visibility = 'protected'
85     elif vis == '~':
86         self.visibility = 'package'
87     elif vis == '-':
88         self.visibility = 'private'
89     else:
90         try:
91             del self.visibility
92         except AttributeError:
93             pass
94
95 def parse_attribute(self, s):
96     """
97     Parse string s in the property. Tagged values, multiplicity and stuff
98     like that is altered to reflect the data in the property string.
99     """
100     m = attribute_pat.match(s)
101     if not m or m.group('garbage'):
102         self.name = s
103         del self.visibility
104         del self.isDerived
105         if self.typeValue:
106             self.typeValue.value = None
107         if self.lowerValue:
108             self.lowerValue.value = None
109         if self.upperValue:
110             self.upperValue.value = None
111         if self.defaultValue:
112             self.defaultValue.value = None
113         #if self.taggedValue:
114         #    self.taggedValue.value = None
115         while self.taggedValue:
116             self.taggedValue[0].unlink()
117     else:
118         from uml2 import LiteralSpecification
119         g = m.group
120         create = self._factory.create
121         _set_visibility(self, g('vis'))
122         self.isDerived = g('derived') and True or False
123         self.name = g('name')
124         if not self.typeValue:
125             self.typeValue = create(LiteralSpecification)
126         self.typeValue.value = g('type')
127         if not self.lowerValue:
128             self.lowerValue = create(LiteralSpecification)
129         self.lowerValue.value = g('mult_l')
130         if not self.upperValue:
131             self.upperValue = create(LiteralSpecification)
132         self.upperValue.value = g('mult_u')
133         if not self.defaultValue:
134             self.defaultValue = create(LiteralSpecification)
135         self.defaultValue.value = g('default')
136         while self.taggedValue:
137             self.taggedValue[0].unlink()
138         tags = g('tags')
139         if tags:
140             for t in map(str.strip, tags.split(',')):
141                 tv = create(LiteralSpecification)
142                 tv.value = t
143                 self.taggedValue = tv
144
145
146 def parse_association_end(self, s):
147     """
148     Parse the text at one end of an association. The association end holds
149     two strings. It is automattically figured out which string is fed to the
150     parser.
151     """
152     from uml2 import LiteralSpecification
153     create = self._factory.create
154
155     # if no name, then clear as there could be some garbage
156     # due to previous parsing (i.e. '[1'
157     m = association_end_name_pat.match(s)
158     if m and not m.group('name'):
159         self.name = None
160
161     # clear also multiplicity if no characters in ``s``
162     m = association_end_mult_pat.match(s)
163     if m and not m.group('mult_u'):
164         if self.upperValue:
165             self.upperValue.value = None
166
167     if m and m.group('mult_u') or m.group('tags'):
168         g = m.group
169         if not self.lowerValue:
170             self.lowerValue = create(LiteralSpecification)
171         self.lowerValue.value = g('mult_l')
172         if not self.upperValue:
173             self.upperValue = create(LiteralSpecification)
174         self.upperValue.value = g('mult_u')
175         while self.taggedValue:
176             self.taggedValue[0].unlink()
177         tags = g('tags')
178         if tags:
179             for t in map(str.strip, tags.split(',')):
180                 tv = create(LiteralSpecification)
181                 tv.value = t
182                 self.taggedValue = tv
183     else:
184         m = association_end_name_pat.match(s)
185         g = m.group
186         if g('garbage'):
187             self.name = s
188             del self.visibility
189             del self.isDerived
190         else:
191             _set_visibility(self, g('vis'))
192             self.isDerived = g('derived') and True or False
193             self.name = g('name')
194             # Optionally, the multiplicity and tagged values may be defined:
195             if g('mult_l'):
196                 if not self.lowerValue:
197                     self.lowerValue = create(LiteralSpecification)
198                 self.lowerValue.value = g('mult_l')
199
200             if g('mult_u'):
201                 if not g('mult_l') and self.lowerValue:
202                     self.lowerValue.value = None
203                 if not self.upperValue:
204                     self.upperValue = create(LiteralSpecification)
205                 self.upperValue.value = g('mult_u')
206
207             tags = g('tags')
208             if tags:
209                 while self.taggedValue:
210                     self.taggedValue[0].unlink()
211                 for t in map(str.strip, tags.split(',')):
212                     tv = create(LiteralSpecification)
213                     tv.value = t
214                     self.taggedValue = tv
215
216 def parse_property(self, s):
217     if self.association:
218         parse_association_end(self, s)
219     else:
220         parse_attribute(self, s)
221
222 def parse_operation(self, s):
223     """
224     Parse string s in the operation. Tagged values, parameters and
225     visibility is altered to reflect the data in the operation string.
226     """
227     from uml2 import Parameter, LiteralSpecification
228     m = operation_pat.match(s)
229     if not m or m.group('garbage'):
230         self.name = s
231         del self.visibility
232         map(Parameter.unlink, list(self.returnResult))
233         map(Parameter.unlink, list(self.formalParameter))
234     else:
235         g = m.group
236         create = self._factory.create
237         _set_visibility(self, g('vis'))
238         self.name = g('name')
239         if not self.returnResult:
240             self.returnResult = create(Parameter)
241         p = self.returnResult[0]
242         p.direction = 'return'
243         if not p.typeValue:
244             p.typeValue = create(LiteralSpecification)
245         p.typeValue.value = g('type')
246         if not p.lowerValue:
247             p.lowerValue = create(LiteralSpecification)
248         p.lowerValue.value = g('mult_l')
249         if not p.upperValue:
250             p.upperValue = create(LiteralSpecification)
251         p.upperValue.value = g('mult_u')
252         # FIXME: Maybe add to Operation.ownedRule?
253         while self.taggedValue:
254             self.taggedValue[0].unlink()
255         tags = g('tags')
256         if tags:
257             for t in map(str.strip, tags.split(',')):
258                 tv = create(LiteralSpecification)
259                 tv.value = t
260                 p.taggedValue = tv
261        
262         pindex = 0
263         params = g('params')
264         while params:
265             m = parameter_pat.match(params)
266             if not m:
267                 break
268             g = m.group
269             try:
270                 p = self.formalParameter[pindex]
271             except IndexError:
272                 p = create(Parameter)
273             p.direction = g('dir') or 'in'
274             p.name = g('name')
275             if not p.typeValue:
276                 p.typeValue = create(LiteralSpecification)
277             p.typeValue.value = g('type')
278             if not p.lowerValue:
279                 p.lowerValue = create(LiteralSpecification)
280             p.lowerValue.value = g('mult_l')
281             if not p.upperValue:
282                 p.upperValue = create(LiteralSpecification)
283             p.upperValue.value = g('mult_u')
284             if not p.defaultValue:
285                 p.defaultValue = create(LiteralSpecification)
286             p.defaultValue.value = g('default')
287             while p.taggedValue:
288                 p.taggedValue[0].unlink()
289             tags = g('tags')
290             if tags:
291                 for t in map(str.strip, tags.split(',')):
292                     tv = create(LiteralSpecification)
293                     tv.value = t
294                     p.taggedValue = tv
295             self.formalParameter = p
296
297             # Do the next parameter:
298             params = g('rest')
299             pindex += 1
300
301         # Remove remaining parameters:
302         for fp in self.formalParameter[pindex:]:
303             fp.unlink()
304
305 def parse_lifeline(self, s):
306     """
307     Parse string s in a lifeline. If a class is defined and can be found
308     in the datamodel, then a class is connected to the lifelines 'represents'
309     property.
310     """
311     m = lifeline_pat.match(s)
312     g = m.group
313     if not m or g('garbage'):
314         self.name = s
315         if hasattr(self, 'represents'):
316             del self.represents
317     else:
318         from uml2 import LiteralSpecification
319         self.name = g('name') + ": "
320         t = g('type')
321         if t:
322             self.name += ': ' + t
323         # In the near future the data model should be extended with
324         # Lifeline.represents: ConnectableElement
325
326 # Do not render if the name still contains a visibility element
327 no_render_pat = compile(r'^\s*[+#-]')
328 vis_map = {
329     'public': '+',
330     'protected': '#',
331     'package': '~',
332     'private': '-'
333 }
334
335
336 def render_attribute(self, visibility=False, is_derived=False, type=False,
337                            multiplicity=False, default=False, tags=False):
338     """
339     Create a OCL representation of the attribute,
340     Returns the attribute as a string.
341     If one or more of the parameters (visibility, is_derived, type,
342     multiplicity, default and/or tags) is set, only that field is rendered.
343     Note that the name of the attribute is always rendered, so a parseable
344     string is returned.
345
346     Note that, when some of those parameters are set, parsing the string
347     will not give you the same result.
348     """
349     name = self.name
350     if not name:
351         name = ''
352
353     if no_render_pat.match(name):
354         name = ''
355
356     # Render all fields if they all are set to False
357     if not (visibility or is_derived or type or multiplicity or default or tags):
358        visibility = is_derived = type = multiplicity = default = tags = True
359
360     s = StringIO()
361
362     if visibility:
363         s.write(vis_map[self.visibility])
364         s.write(' ')
365
366     if is_derived:
367         if self.isDerived: s.write('/')
368
369     s.write(name)
370    
371     if type and self.typeValue and self.typeValue.value:
372         s.write(': %s' % self.typeValue.value)
373
374     if multiplicity and self.upperValue and self.upperValue.value: 
375         if self.lowerValue and self.lowerValue.value:
376             s.write('[%s..%s]' % (self.lowerValue.value, self.upperValue.value))
377         else:
378             s.write('[%s]' % self.upperValue.value)
379
380     if default and self.defaultValue and self.defaultValue.value:
381         s.write(' = %s' % self.defaultValue.value)
382
383     if self.taggedValue:
384         tvs = ', '.join(filter(None, map(getattr, self.taggedValue,
385                                           ['value'] * len(self.taggedValue))))
386         if tvs:
387             s.write(' { %s }' % tvs)
388     s.reset()
389     return s.read()
390
391
392 def render_association_end(self):
393     """
394     """
395     name = ''
396     n = StringIO()
397     if self.name:
398         n.write(vis_map[self.visibility])
399         n.write(' ')
400         if self.isDerived:
401             n.write('/')
402         if self.name:
403             n.write(self.name)
404         n.reset()
405         name = n.read()
406
407     m = StringIO()
408     if self.upperValue and self.upperValue.value: 
409         if self.lowerValue and self.lowerValue.value:
410             m.write('%s..%s' % (self.lowerValue.value, self.upperValue.value))
411         else:
412             m.write('%s' % self.upperValue.value)
413     if self.taggedValue:
414         tvs = ',\n '.join(filter(None, map(getattr, self.taggedValue,
415                                      ['value'] * len(self.taggedValue))))
416         if tvs:
417             m.write(' { %s }' % tvs)
418     m.reset()
419     mult = m.read()
420
421     return name, mult
422
423
424 def render_property(self, *args, **kwargs):
425     """
426     Render a gaphor.UML.Property either as an attribute or as a
427     (name, multiplicity) tuple for association ends.
428
429     See also: render_attribute, render_association_end
430     """
431     if self.association:
432         return render_association_end(self)
433     else:
434         return render_attribute(self, *args, **kwargs)
435
436
437 def render_operation(self, visibility=False, type=False, multiplicity=False,
438                            default=False, tags=False, direction=False):
439     """
440     Create a OCL representation of the operation,
441     Returns the operation as a string.
442     """
443     name = self.name
444     if not name:
445         return ''
446     if no_render_pat.match(name):
447         return name
448
449     # Render all fields if they all are set to False
450     if not (visibility or type or multiplicity or default or tags or direction):
451        visibility = type = multiplicity = default = tags = direction = True
452
453     s = StringIO()
454     if visibility:
455         s.write(vis_map[self.visibility])
456         s.write(' ')
457
458     s.write(name)
459     s.write('(')
460
461     for p in self.formalParameter:
462         if direction:
463             s.write(p.direction)
464             s.write(' ')
465         s.write(p.name)
466         if type and p.typeValue and p.typeValue.value:
467             s.write(': %s' % p.typeValue.value