Add concrete syntax for class diagrams + example (woods2.py)
This commit is contained in:
parent
c248fc9090
commit
52ded8af77
6 changed files with 311 additions and 58 deletions
|
|
@ -1,3 +1,5 @@
|
|||
from lark import Transformer
|
||||
|
||||
def indent(multiline_string, how_much):
|
||||
lines = multiline_string.split('\n')
|
||||
return '\n'.join([' '*how_much+l for l in lines])
|
||||
|
|
@ -14,3 +16,42 @@ def display_value(val: any, type_name: str, indentation=0):
|
|||
return str(val)
|
||||
else:
|
||||
raise Exception("don't know how to display value" + type_name)
|
||||
|
||||
|
||||
# internal use only
|
||||
# just a dumb wrapper to distinguish between code and string
|
||||
class _Code:
|
||||
def __init__(self, code):
|
||||
self.code = code
|
||||
|
||||
class TBase(Transformer):
|
||||
|
||||
def IDENTIFIER(self, token):
|
||||
return str(token)
|
||||
|
||||
def INT(self, token):
|
||||
return int(token)
|
||||
|
||||
def BOOL(self, token):
|
||||
return token == "True"
|
||||
|
||||
def STR(self, token):
|
||||
return str(token[1:-1]) # strip the "" or ''
|
||||
|
||||
def CODE(self, token):
|
||||
return _Code(str(token[1:-1])) # strip the ``
|
||||
|
||||
def INDENTED_CODE(self, token):
|
||||
skip = 4 # strip the ``` and the following newline character
|
||||
space_count = 0
|
||||
while token[skip+space_count] == " ":
|
||||
space_count += 1
|
||||
lines = token.split('\n')[1:-1]
|
||||
for line in lines:
|
||||
if len(line) >= space_count and line[0:space_count] != ' '*space_count:
|
||||
raise Exception("wrong indentation of INDENTED_CODE")
|
||||
unindented_lines = [l[space_count:] for l in lines]
|
||||
return _Code('\n'.join(unindented_lines))
|
||||
|
||||
def literal(self, el):
|
||||
return el[0]
|
||||
|
|
|
|||
|
|
@ -1,26 +1,142 @@
|
|||
from lark import Lark, logger
|
||||
from concrete_syntax.common import _Code, TBase
|
||||
from uuid import UUID
|
||||
from services.scd import SCD
|
||||
from services.od import OD
|
||||
|
||||
grammar = r"""
|
||||
%import common.WS
|
||||
%ignore WS
|
||||
%ignore COMMENT
|
||||
|
||||
?start: object*
|
||||
?start: (class_ | association | global_constraint)*
|
||||
|
||||
IDENTIFIER: /[A-Za-z_][A-Za-z_0-9]*/
|
||||
COMMENT: /#[^\n]*\n/
|
||||
|
||||
# newline
|
||||
_NL: /(\r?\n[\t ]*)+/
|
||||
COMMENT: /#[^\n]*\n/
|
||||
|
||||
literal: INT
|
||||
| STR
|
||||
| BOOL
|
||||
| CODE
|
||||
| INDENTED_CODE
|
||||
|
||||
INT: /[0-9]+/
|
||||
STR: /"[^"]*"/
|
||||
| /'[^']*'/
|
||||
BOOL: "True" | "False"
|
||||
CODE: /`[^`]*`/
|
||||
INDENTED_CODE: /```[^`]*```/
|
||||
|
||||
object: [IDENTIFIER] ":" IDENTIFIER [link] _NL [_INDENT slot+ _DEDENT]
|
||||
link: "(" IDENTIFIER "->" IDENTIFIER ")"
|
||||
slot: IDENTIFIER "=" literal _NL
|
||||
INT_OR_INF: INT | "*"
|
||||
|
||||
multiplicity: "[" INT ".." INT_OR_INF "]"
|
||||
|
||||
ABSTRACT: "abstract"
|
||||
|
||||
superclasses: IDENTIFIER ("," IDENTIFIER)*
|
||||
|
||||
attrs: attr*
|
||||
|
||||
constraint: CODE | INDENTED_CODE
|
||||
|
||||
class_: [ABSTRACT] "class" IDENTIFIER [multiplicity] ["(" superclasses ")"] ["{" attrs [constraint] "}"]
|
||||
|
||||
association: "association" IDENTIFIER [multiplicity] IDENTIFIER "->" IDENTIFIER [multiplicity] ["{" [constraint] "}"]
|
||||
|
||||
OPTIONAL: "optional"
|
||||
|
||||
attr: [OPTIONAL] IDENTIFIER IDENTIFIER [constraint] ";"
|
||||
|
||||
global_constraint: "global" IDENTIFIER constraint
|
||||
"""
|
||||
|
||||
parser = Lark(grammar, parser='lalr')
|
||||
|
||||
def _handle_missing_multiplicity(multiplicity):
|
||||
if multiplicity != None:
|
||||
return multiplicity
|
||||
else:
|
||||
return (None, None)
|
||||
|
||||
|
||||
def parse_cd(state, m_text):
|
||||
type_model_id = state.read_dict(state.read_root(), "SCD")
|
||||
scd_mmm = UUID(state.read_value(type_model_id))
|
||||
|
||||
m = state.create_node()
|
||||
cd = SCD(m, state)
|
||||
od = OD(scd_mmm, m, state)
|
||||
|
||||
def _add_constraint_to_obj(obj_name, constraint):
|
||||
c = od.create_actioncode_value(f"{obj_name}.constraint", constraint.code)
|
||||
od.create_slot("constraint", obj_name, c)
|
||||
|
||||
primitive_types = {
|
||||
type_name : UUID(state.read_value(state.read_dict(state.read_root(), type_name)))
|
||||
for type_name in ["Integer", "String", "Boolean"]
|
||||
}
|
||||
|
||||
class T(TBase):
|
||||
def __init__(self, visit_tokens):
|
||||
super().__init__(visit_tokens)
|
||||
self.obj_counter = 0
|
||||
|
||||
def ABSTRACT(self, el):
|
||||
return True
|
||||
|
||||
def INT_OR_INF(self, el):
|
||||
return float('inf') if el == "*" else int(el)
|
||||
|
||||
def multiplicity(self, el):
|
||||
[lower, upper] = el
|
||||
return (lower, upper)
|
||||
|
||||
def superclasses(self, el):
|
||||
return list(el)
|
||||
|
||||
def attrs(self, el):
|
||||
return list(el)
|
||||
|
||||
def constraint(self, el):
|
||||
return el[0]
|
||||
|
||||
def attr(self, el):
|
||||
[optional, attr_type, attr_name, constraint] = el
|
||||
return (optional == "optional", attr_type, attr_name, constraint)
|
||||
|
||||
def global_constraint(self, el):
|
||||
[name, constraint] = el
|
||||
od.create_object(name, "GlobalConstraint")
|
||||
_add_constraint_to_obj(name, constraint)
|
||||
|
||||
def class_(self, el):
|
||||
[abstract, class_name, multiplicity, super_classes, attrs, constraint] = el
|
||||
(lower, upper) = _handle_missing_multiplicity(multiplicity)
|
||||
cd.create_class(class_name, abstract, lower, upper)
|
||||
if super_classes != None:
|
||||
for super_class in super_classes:
|
||||
cd.create_inheritance(class_name, super_class)
|
||||
if constraint != None:
|
||||
_add_constraint_to_obj(class_name, constraint)
|
||||
if attrs != None:
|
||||
for attr in attrs:
|
||||
(optional, attr_type, attr_name, constraint) = attr
|
||||
# TODO: only create type ref if it doesn't exist yet
|
||||
cd.create_model_ref(attr_type, primitive_types[attr_type])
|
||||
cd.create_attribute_link(class_name, attr_type, attr_name, optional)
|
||||
if constraint != None:
|
||||
_add_constraint_to_obj(f"{class_name}_{attr_name}", constraint)
|
||||
|
||||
def association(self, el):
|
||||
[assoc_name, src_multiplicity, src_name, tgt_name, tgt_multiplicity, constraint] = el
|
||||
(src_lower, src_upper) = _handle_missing_multiplicity(src_multiplicity)
|
||||
(tgt_lower, tgt_upper) = _handle_missing_multiplicity(tgt_multiplicity)
|
||||
cd.create_association(assoc_name, src_name, tgt_name, src_lower, src_upper, tgt_lower, tgt_upper)
|
||||
if constraint != None:
|
||||
_add_constraint_to_obj(class_name, constraint)
|
||||
|
||||
tree = parser.parse(m_text)
|
||||
t = T(visit_tokens=True).transform(tree)
|
||||
|
||||
return m
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
# Parser for Object Diagrams textual concrete syntax
|
||||
|
||||
from lark import Lark, logger, Transformer
|
||||
from lark import Lark, logger
|
||||
from lark.indenter import Indenter
|
||||
from services.od import OD
|
||||
from services.scd import SCD
|
||||
from concrete_syntax.common import _Code, TBase
|
||||
from uuid import UUID
|
||||
|
||||
grammar = r"""
|
||||
|
|
@ -39,12 +40,6 @@ slot: IDENTIFIER "=" literal ";"
|
|||
|
||||
parser = Lark(grammar, parser='lalr')
|
||||
|
||||
# internal use only
|
||||
# just a dumb wrapper to distinguish between code and string
|
||||
class _Code:
|
||||
def __init__(self, code):
|
||||
self.code = code
|
||||
|
||||
# given a concrete syntax text string, and a meta-model, parses the CS
|
||||
def parse_od(state, m_text, mm):
|
||||
tree = parser.parse(m_text)
|
||||
|
|
@ -54,41 +49,11 @@ def parse_od(state, m_text, mm):
|
|||
|
||||
int_mm_id = UUID(state.read_value(state.read_dict(state.read_root(), "Integer")))
|
||||
|
||||
class T(Transformer):
|
||||
class T(TBase):
|
||||
def __init__(self, visit_tokens):
|
||||
super().__init__(visit_tokens)
|
||||
self.obj_counter = 0
|
||||
|
||||
def IDENTIFIER(self, token):
|
||||
return str(token)
|
||||
|
||||
def INT(self, token):
|
||||
return int(token)
|
||||
|
||||
def BOOL(self, token):
|
||||
return token == "True"
|
||||
|
||||
def STR(self, token):
|
||||
return str(token[1:-1]) # strip the "" or ''
|
||||
|
||||
def CODE(self, token):
|
||||
return _Code(str(token[1:-1])) # strip the ``
|
||||
|
||||
def INDENTED_CODE(self, token):
|
||||
skip = 4 # strip the ``` and the following newline character
|
||||
space_count = 0
|
||||
while token[skip+space_count] == " ":
|
||||
space_count += 1
|
||||
lines = token.split('\n')[1:-1]
|
||||
for line in lines:
|
||||
if len(line) >= space_count and line[0:space_count] != ' '*space_count:
|
||||
raise Exception("wrong indentation of INDENTED_CODE")
|
||||
unindented_lines = [l[space_count:] for l in lines]
|
||||
return _Code('\n'.join(unindented_lines))
|
||||
|
||||
def literal(self, el):
|
||||
return el[0]
|
||||
|
||||
def link_spec(self, el):
|
||||
[src, tgt] = el
|
||||
return (src, tgt)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue