parser.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. # Parser for Object Diagrams textual concrete syntax
  2. from lark import Lark, logger, Transformer
  3. from lark.indenter import Indenter
  4. from api.od import ODAPI
  5. from services.scd import SCD
  6. from concrete_syntax.common import _Code
  7. from uuid import UUID
  8. grammar = r"""
  9. %import common.WS
  10. %ignore WS
  11. %ignore COMMENT
  12. ?start: object*
  13. IDENTIFIER: /[A-Za-z_][A-Za-z_0-9]*/
  14. COMMENT: /#[^\n]*/
  15. literal: INT
  16. | STR
  17. | BOOL
  18. | CODE
  19. | BYTES
  20. | INDENTED_CODE
  21. INT: /[0-9]+/
  22. STR: /"[^"]*"/
  23. | /'[^']*'/
  24. BOOL: "True" | "False"
  25. CODE: /`[^`]*`/
  26. BYTES: /b"[^"]*"/
  27. | /b'[^']*'/
  28. INDENTED_CODE: /```[^`]*```/
  29. type_name: IDENTIFIER
  30. # name (optional) type
  31. object: [IDENTIFIER] ":" type_name [link_spec | rev_link_spec] ["{" slot* "}"]
  32. link_spec: "(" IDENTIFIER "->" IDENTIFIER ")"
  33. rev_link_spec: "(" IDENTIFIER "<-" IDENTIFIER ")"
  34. slot: IDENTIFIER "=" literal ";"
  35. """
  36. parser = Lark(grammar, parser='lalr', propagate_positions=True)
  37. class DefaultNameGenerator:
  38. def __init__(self):
  39. self.counter = 0
  40. def __call__(self, type_name):
  41. name = f"__{type_name}_{self.counter}"
  42. self.counter += 1
  43. return name
  44. # given a concrete syntax text string, and a meta-model, parses the CS
  45. # Parameter 'type_transform' is useful for adding prefixes to the type names, when parsing a model and pretending it is an instance of a prefixed meta-model.
  46. def parse_od(state,
  47. m_text, # text to parse
  48. mm, # meta-model of model that will be parsed. The meta-model must already have been parsed.
  49. type_transform=lambda type_name: type_name,
  50. name_generator=DefaultNameGenerator(), # exception to raise if anonymous (nameless) object/link occurs in the model. Main reason for this is to forbid them in LHS of transformation rules.
  51. ):
  52. tree = parser.parse(m_text)
  53. m = state.create_node()
  54. od = ODAPI(state, m, mm)
  55. primitive_types = {
  56. type_name : UUID(state.read_value(state.read_dict(state.read_root(), type_name)))
  57. for type_name in ["Integer", "String", "Boolean", "ActionCode", "Bytes"]
  58. }
  59. class T(Transformer):
  60. def __init__(self, visit_tokens):
  61. super().__init__(visit_tokens)
  62. def IDENTIFIER(self, token):
  63. return (str(token), token.line)
  64. def INT(self, token):
  65. return (int(token), token.line)
  66. def BOOL(self, token):
  67. return (token == "True", token.line)
  68. def STR(self, token):
  69. return (str(token[1:-1]), token.line) # strip the "" or ''
  70. def CODE(self, token):
  71. return (_Code(str(token[1:-1])), token.line) # strip the ``
  72. def BYTES(self, token):
  73. # Strip b"" or b'', and make \\ back to \ (happens when reading the file as a string)
  74. return (token[2:-1].encode().decode('unicode_escape').encode('raw_unicode_escape'), token.line) # Strip b"" or b''
  75. def INDENTED_CODE(self, token):
  76. skip = 4 # strip the ``` and the following newline character
  77. space_count = 0
  78. while token[skip+space_count] == " ":
  79. space_count += 1
  80. lines = token.split('\n')[1:-1]
  81. for line in lines:
  82. if len(line) >= space_count and line[0:space_count] != ' '*space_count:
  83. raise Exception("wrong indentation of INDENTED_CODE")
  84. unindented_lines = [l[space_count:] for l in lines]
  85. return (_Code('\n'.join(unindented_lines)), token.line)
  86. def literal(self, el):
  87. return el[0]
  88. def link_spec(self, el):
  89. [(src, src_line), (tgt, _)] = el
  90. return (src, tgt, src_line)
  91. def rev_link_spec(self, el):
  92. [(tgt, tgt_line), (src, _)] = el # <-- reversed :)
  93. return (src, tgt, tgt_line)
  94. def type_name(self, el):
  95. type_name, line = el[0]
  96. if type_name in primitive_types:
  97. return (type_name, line)
  98. else:
  99. return (type_transform(type_name), line)
  100. def slot(self, el):
  101. [(attr_name, line), (value, _)] = el
  102. return (attr_name, value, line)
  103. def object(self, el):
  104. [obj, (type_name, line), link] = el[0:3]
  105. slots = el[3:]
  106. try:
  107. if obj != None:
  108. (obj_name, _) = obj
  109. else:
  110. # anonymous object - auto-generate a name
  111. obj_name = name_generator(type_name)
  112. if state.read_dict(m, obj_name) != None:
  113. msg = f"Element '{obj_name}:{type_name}': name '{obj_name}' already in use."
  114. raise Exception(msg + " Names must be unique")
  115. # print(msg + " Ignoring.")
  116. return
  117. if link == None:
  118. obj_node = od.create_object(obj_name, type_name)
  119. else:
  120. (src, tgt, _) = link
  121. if tgt in primitive_types:
  122. if state.read_dict(m, tgt) == None:
  123. scd = SCD(m, state)
  124. scd.create_model_ref(tgt, primitive_types[tgt])
  125. src_obj = od.get(src)
  126. tgt_obj = od.get(tgt)
  127. obj_node = od.create_link(obj_name, type_name, src_obj, tgt_obj)
  128. # Create slots
  129. for attr_name, value, line in slots:
  130. if isinstance(value, _Code):
  131. od.set_slot_value(obj_node, attr_name, value.code, is_code=True)
  132. else:
  133. od.set_slot_value(obj_node, attr_name, value)
  134. return obj_name
  135. except Exception as e:
  136. # raising a *new* exception (instead of adding a note to the existing exception) because Lark will also raise a new exception, and ignore our note:
  137. raise Exception(f"at line {line}:\n " + m_text.split('\n')[line-1] + "\n"+ str(e)) from e
  138. t = T(visit_tokens=True).transform(tree)
  139. return m