parser prints line numbers AND gives an error if you use anonymous objects/links in LHS of a rule
This commit is contained in:
parent
c7288635f8
commit
6314506ac0
2 changed files with 102 additions and 46 deletions
|
|
@ -1,10 +1,10 @@
|
|||
# Parser for Object Diagrams textual concrete syntax
|
||||
|
||||
from lark import Lark, logger
|
||||
from lark import Lark, logger, Transformer
|
||||
from lark.indenter import Indenter
|
||||
from api.od import ODAPI
|
||||
from services.scd import SCD
|
||||
from concrete_syntax.common import _Code, TBase
|
||||
from concrete_syntax.common import _Code
|
||||
from uuid import UUID
|
||||
|
||||
grammar = r"""
|
||||
|
|
@ -41,11 +41,25 @@ rev_link_spec: "(" IDENTIFIER "<-" IDENTIFIER ")"
|
|||
slot: IDENTIFIER "=" literal ";"
|
||||
"""
|
||||
|
||||
parser = Lark(grammar, parser='lalr')
|
||||
parser = Lark(grammar, parser='lalr', propagate_positions=True)
|
||||
|
||||
class DefaultNameGenerator:
|
||||
def __init__(self):
|
||||
self.counter = 0
|
||||
|
||||
def __call__(self, type_name):
|
||||
name = f"__{type_name}_{self.counter}"
|
||||
self.counter += 1
|
||||
return name
|
||||
|
||||
# given a concrete syntax text string, and a meta-model, parses the CS
|
||||
# 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.
|
||||
def parse_od(state, m_text, mm, type_transform=lambda type_name: type_name):
|
||||
def parse_od(state,
|
||||
m_text, # text to parse
|
||||
mm, # meta-model of model that will be parsed. The meta-model must already have been parsed.
|
||||
type_transform=lambda type_name: type_name,
|
||||
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.
|
||||
):
|
||||
tree = parser.parse(m_text)
|
||||
|
||||
m = state.create_node()
|
||||
|
|
@ -56,62 +70,95 @@ def parse_od(state, m_text, mm, type_transform=lambda type_name: type_name):
|
|||
for type_name in ["Integer", "String", "Boolean", "ActionCode"]
|
||||
}
|
||||
|
||||
class T(TBase):
|
||||
class T(Transformer):
|
||||
def __init__(self, visit_tokens):
|
||||
super().__init__(visit_tokens)
|
||||
self.obj_counter = 0 # used for generating unique names for anonymous objects
|
||||
|
||||
def IDENTIFIER(self, token):
|
||||
return (str(token), token.line)
|
||||
|
||||
def INT(self, token):
|
||||
return (int(token), token.line)
|
||||
|
||||
def BOOL(self, token):
|
||||
return (token == "True", token.line)
|
||||
|
||||
def STR(self, token):
|
||||
return (str(token[1:-1]), token.line) # strip the "" or ''
|
||||
|
||||
def CODE(self, token):
|
||||
return (_Code(str(token[1:-1])), token.line) # 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)), token.line)
|
||||
|
||||
def literal(self, el):
|
||||
return el[0]
|
||||
|
||||
def link_spec(self, el):
|
||||
[src, tgt] = el
|
||||
return (src, tgt)
|
||||
[(src, src_line), (tgt, _)] = el
|
||||
return (src, tgt, src_line)
|
||||
|
||||
def rev_link_spec(self, el):
|
||||
[tgt, src] = el # <-- reversed :)
|
||||
return (src, tgt)
|
||||
[(tgt, tgt_line), (src, _)] = el # <-- reversed :)
|
||||
return (src, tgt, tgt_line)
|
||||
|
||||
def type_name(self, el):
|
||||
type_name = el[0]
|
||||
type_name, line = el[0]
|
||||
if type_name in primitive_types:
|
||||
return type_name
|
||||
return (type_name, line)
|
||||
else:
|
||||
return type_transform(el[0])
|
||||
return (type_transform(type_name), line)
|
||||
|
||||
def slot(self, el):
|
||||
[attr_name, value] = el
|
||||
return (attr_name, value)
|
||||
[(attr_name, line), (value, _)] = el
|
||||
return (attr_name, value, line)
|
||||
|
||||
def object(self, el):
|
||||
[obj_name, type_name, link] = el[0:3]
|
||||
[obj, (type_name, line), link] = el[0:3]
|
||||
slots = el[3:]
|
||||
if state.read_dict(m, obj_name) != None:
|
||||
msg = f"Element '{obj_name}:{type_name}': name '{obj_name}' already in use."
|
||||
# raise Exception(msg + " Names must be unique")
|
||||
print(msg + " Ignoring.")
|
||||
return
|
||||
if obj_name == None:
|
||||
# object/link names are optional
|
||||
# generate a unique name if no name given
|
||||
obj_name = f"__{type_name}_{self.obj_counter}"
|
||||
self.obj_counter += 1
|
||||
if link == None:
|
||||
obj_node = od.create_object(obj_name, type_name)
|
||||
else:
|
||||
src, tgt = link
|
||||
if tgt in primitive_types:
|
||||
if state.read_dict(m, tgt) == None:
|
||||
scd = SCD(m, state)
|
||||
scd.create_model_ref(tgt, primitive_types[tgt])
|
||||
src_obj = od.get(src)
|
||||
tgt_obj = od.get(tgt)
|
||||
obj_node = od.create_link(obj_name, type_name, src_obj, tgt_obj)
|
||||
# Create slots
|
||||
for attr_name, value in slots:
|
||||
if isinstance(value, _Code):
|
||||
od.set_slot_value(obj_node, attr_name, value.code, is_code=True)
|
||||
try:
|
||||
if obj != None:
|
||||
(obj_name, _) = obj
|
||||
else:
|
||||
od.set_slot_value(obj_node, attr_name, value)
|
||||
# anonymous object - auto-generate a name
|
||||
obj_name = name_generator(type_name)
|
||||
if state.read_dict(m, obj_name) != None:
|
||||
msg = f"Element '{obj_name}:{type_name}': name '{obj_name}' already in use."
|
||||
raise Exception(msg + " Names must be unique")
|
||||
# print(msg + " Ignoring.")
|
||||
return
|
||||
if link == None:
|
||||
obj_node = od.create_object(obj_name, type_name)
|
||||
else:
|
||||
(src, tgt, _) = link
|
||||
if tgt in primitive_types:
|
||||
if state.read_dict(m, tgt) == None:
|
||||
scd = SCD(m, state)
|
||||
scd.create_model_ref(tgt, primitive_types[tgt])
|
||||
src_obj = od.get(src)
|
||||
tgt_obj = od.get(tgt)
|
||||
obj_node = od.create_link(obj_name, type_name, src_obj, tgt_obj)
|
||||
# Create slots
|
||||
for attr_name, value, line in slots:
|
||||
if isinstance(value, _Code):
|
||||
od.set_slot_value(obj_node, attr_name, value.code, is_code=True)
|
||||
else:
|
||||
od.set_slot_value(obj_node, attr_name, value)
|
||||
|
||||
return obj_name
|
||||
return obj_name
|
||||
except Exception as e:
|
||||
# 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:
|
||||
raise Exception(f"at line {line}:\n " + m_text.split('\n')[line-1] + "\n"+ str(e)) from e
|
||||
|
||||
t = T(visit_tokens=True).transform(tree)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue