Concrete syntax no longer indentation-based (nightmare to parse). Add indented multi-line code terminals.
This commit is contained in:
parent
59de61d0a3
commit
e875821e70
8 changed files with 119 additions and 73 deletions
16
concrete_syntax/common.py
Normal file
16
concrete_syntax/common.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
def indent(multiline_string, how_much):
|
||||
lines = multiline_string.split('\n')
|
||||
return '\n'.join([' '*how_much+l for l in lines])
|
||||
|
||||
def display_value(val: any, type_name: str, indentation=0):
|
||||
if type_name == "ActionCode":
|
||||
if '\n' in val:
|
||||
return '```\n'+indent(val, indentation+4)+'\n'+' '*indentation+'```'
|
||||
else:
|
||||
return '`'+val+'`'
|
||||
elif type_name == "String":
|
||||
return '"'+val+'"'
|
||||
elif type_name == "Integer" or type_name == "Boolean":
|
||||
return str(val)
|
||||
else:
|
||||
raise Exception("don't know how to display value" + type_name)
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
from services import scd, od
|
||||
from services.bottom.V0 import Bottom
|
||||
from transformation import ramify
|
||||
import json
|
||||
from concrete_syntax.common import display_value
|
||||
from uuid import UUID
|
||||
|
||||
def render_class_diagram(state, model, prefix_ids=""):
|
||||
|
|
@ -97,7 +97,8 @@ def render_object_diagram(state, m, mm, render_attributes=True, prefix_ids=""):
|
|||
for attr_name, attr_edge in attributes:
|
||||
slot = m_od.get_slot(obj_node, attr_name)
|
||||
if slot != None:
|
||||
output += f"\n{attr_name} => {json.dumps(od.read_primitive_value(bottom, slot, mm)[0])}"
|
||||
val, type_name = od.read_primitive_value(bottom, slot, mm)
|
||||
output += f"\n{attr_name} => {display_value(val, type_name)}"
|
||||
output += '\n}'
|
||||
|
||||
output += '\n'
|
||||
|
|
|
|||
|
|
@ -7,46 +7,34 @@ from services.scd import SCD
|
|||
from uuid import UUID
|
||||
|
||||
grammar = r"""
|
||||
%import common.WS_INLINE
|
||||
%ignore WS_INLINE
|
||||
%import common.WS
|
||||
%ignore WS
|
||||
%ignore COMMENT
|
||||
|
||||
%declare _INDENT _DEDENT
|
||||
|
||||
?start: (_NL | object )*
|
||||
?start: object*
|
||||
|
||||
IDENTIFIER: /[A-Za-z_][A-Za-z_0-9]*/
|
||||
COMMENT: /#.*/
|
||||
|
||||
# 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_spec] _NL [_INDENT slot+ _DEDENT]
|
||||
object: [IDENTIFIER] ":" IDENTIFIER [link_spec] ["{" slot* "}"]
|
||||
link_spec: "(" IDENTIFIER "->" IDENTIFIER ")"
|
||||
slot: IDENTIFIER "=" literal _NL
|
||||
slot: IDENTIFIER "=" literal ";"
|
||||
"""
|
||||
|
||||
|
||||
class TreeIndenter(Indenter):
|
||||
NL_type = '_NL'
|
||||
OPEN_PAREN_types = []
|
||||
CLOSE_PAREN_types = []
|
||||
INDENT_type = '_INDENT'
|
||||
DEDENT_type = '_DEDENT'
|
||||
tab_len = 4
|
||||
|
||||
parser = Lark(grammar, parser='lalr', postlex=TreeIndenter())
|
||||
parser = Lark(grammar, parser='lalr')
|
||||
|
||||
# internal use only
|
||||
# just a dumb wrapper to distinguish between code and string
|
||||
|
|
@ -83,6 +71,18 @@ def parse_od(state, cs_text, mm):
|
|||
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 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]
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,8 @@
|
|||
|
||||
from services import od
|
||||
from services.bottom.V0 import Bottom
|
||||
import json
|
||||
from concrete_syntax.common import display_value
|
||||
|
||||
def display_value(val: any, type_name: str):
|
||||
if type_name == "ActionCode":
|
||||
return '`'+val+'`'
|
||||
elif type_name == "String":
|
||||
return '"'+val+'"'
|
||||
elif type_name == "Integer" or type_name == "Boolean":
|
||||
return str(val)
|
||||
else:
|
||||
raise Exception("don't know how to display value" + type_name)
|
||||
|
||||
def render_od(state, m_id, mm_id, hide_names=True):
|
||||
bottom = Bottom(state)
|
||||
|
|
@ -26,19 +17,23 @@ def render_od(state, m_id, mm_id, hide_names=True):
|
|||
|
||||
def write_attributes(object_node):
|
||||
o = ""
|
||||
for attr_name, slot_node in m_od.get_slots(object_node):
|
||||
slots = m_od.get_slots(object_node)
|
||||
if len(slots) > 0:
|
||||
o += " {"
|
||||
for attr_name, slot_node in slots:
|
||||
value, type_name = m_od.read_slot(slot_node)
|
||||
o += f" {attr_name} = {display_value(value, type_name)}\n"
|
||||
o += f"\n {attr_name} = {display_value(value, type_name, indentation=4)};"
|
||||
o += "\n}"
|
||||
return o
|
||||
|
||||
for class_name, objects in m_od.get_all_objects().items():
|
||||
for object_name, object_node in objects.items():
|
||||
output += f"{display_name(object_name)}:{class_name}\n"
|
||||
output += f"\n{display_name(object_name)}:{class_name}"
|
||||
output += write_attributes(object_node)
|
||||
|
||||
for assoc_name, links in m_od.get_all_links().items():
|
||||
for link_name, (link_edge, src_name, tgt_name) in links.items():
|
||||
output += f"{display_name(link_name)}:{assoc_name} ({src_name} -> {tgt_name})\n"
|
||||
output += f"\n{display_name(link_name)}:{assoc_name} ({src_name} -> {tgt_name})"
|
||||
# links can also have slots:
|
||||
output += write_attributes(link_edge)
|
||||
|
||||
|
|
|
|||
|
|
@ -62,22 +62,37 @@ def main():
|
|||
dsl_mm_cs = """
|
||||
# Integer:ModelRef
|
||||
Bear:Class
|
||||
Animal:Class
|
||||
abstract = True
|
||||
Man:Class
|
||||
lower_cardinality = 1
|
||||
upper_cardinality = 2
|
||||
# constraint = `get_value(get_slot(element, "weight")) < 100`
|
||||
Man_weight:AttributeLink (Man -> Integer)
|
||||
name = "weight"
|
||||
optional = False
|
||||
constraint = `get_value(get_target(element)) < 100`
|
||||
afraidOf:Association (Man -> Animal)
|
||||
target_lower_cardinality = 1
|
||||
Animal:Class {
|
||||
abstract = True;
|
||||
}
|
||||
Man:Class {
|
||||
lower_cardinality = 1;
|
||||
upper_cardinality = 2;
|
||||
constraint = `get_value(get_slot(element, "weight")) < 20`;
|
||||
}
|
||||
Man_weight:AttributeLink (Man -> Integer) {
|
||||
name = "weight";
|
||||
optional = False;
|
||||
constraint = ```
|
||||
node = get_target(element)
|
||||
get_value(node) < 20
|
||||
```;
|
||||
}
|
||||
afraidOf:Association (Man -> Animal) {
|
||||
target_lower_cardinality = 1;
|
||||
}
|
||||
Man_inh_Animal:Inheritance (Man -> Animal)
|
||||
Bear_inh_Animal:Inheritance (Bear -> Animal)
|
||||
sum_of_weights:GlobalConstraint
|
||||
constraint = `len(get_all_instances("afraidOf")) <= 1`
|
||||
|
||||
not_too_fat:GlobalConstraint {
|
||||
constraint = ```
|
||||
# total weight of all men low enough
|
||||
total_weight = 0
|
||||
for man_name, man_id in get_all_instances("Man"):
|
||||
total_weight += get_value(get_slot(man_id, "weight"))
|
||||
total_weight < 50
|
||||
```;
|
||||
}
|
||||
"""
|
||||
dsl_mm_id = parser.parse_od(state, dsl_mm_cs, mm=scd_mm_id)
|
||||
return dsl_mm_id
|
||||
|
|
@ -98,8 +113,9 @@ sum_of_weights:GlobalConstraint
|
|||
def create_dsl_m_parser():
|
||||
# Create DSL M with parser
|
||||
dsl_m_cs = """
|
||||
george :Man
|
||||
weight = 80
|
||||
george :Man {
|
||||
weight = 80;
|
||||
}
|
||||
bear1:Bear
|
||||
bear2:Bear
|
||||
:afraidOf (george -> bear1)
|
||||
|
|
@ -139,7 +155,7 @@ bear2:Bear
|
|||
lhs_id = state.create_node()
|
||||
lhs_od = OD(ramified_mm_id, lhs_id, state)
|
||||
lhs_od.create_object("man", prefix+"Man")
|
||||
lhs_od.create_slot(prefix+"weight", "man", lhs_od.create_string_value(f"man.{prefix}weight", 'v < 99'))
|
||||
lhs_od.create_slot(prefix+"weight", "man", lhs_od.create_actioncode_value(f"man.{prefix}weight", 'v < 99'))
|
||||
lhs_od.create_object("scaryAnimal", prefix+"Animal")
|
||||
lhs_od.create_link("manAfraidOfAnimal", prefix+"afraidOf", "man", "scaryAnimal")
|
||||
|
||||
|
|
@ -150,9 +166,9 @@ bear2:Bear
|
|||
rhs_id = state.create_node()
|
||||
rhs_od = OD(ramified_mm_id, rhs_id, state)
|
||||
rhs_od.create_object("man", prefix+"Man")
|
||||
rhs_od.create_slot(prefix+"weight", "man", rhs_od.create_string_value(f"man.{prefix}weight", 'v + 5'))
|
||||
rhs_od.create_slot(prefix+"weight", "man", rhs_od.create_actioncode_value(f"man.{prefix}weight", 'v + 5'))
|
||||
rhs_od.create_object("bill", prefix+"Man")
|
||||
rhs_od.create_slot(prefix+"weight", "bill", rhs_od.create_string_value(f"bill.{prefix}weight", '100'))
|
||||
rhs_od.create_slot(prefix+"weight", "bill", rhs_od.create_actioncode_value(f"bill.{prefix}weight", '100'))
|
||||
|
||||
rhs_od.create_link("billAfraidOfMan", prefix+"afraidOf", "bill", "man")
|
||||
|
||||
|
|
@ -229,7 +245,7 @@ bear2:Bear
|
|||
# plantuml_str = render_rewrite()
|
||||
|
||||
# print()
|
||||
# print("==============================================")
|
||||
print("==============================================")
|
||||
|
||||
# print(plantuml_str)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,17 @@ from pprint import pprint
|
|||
import functools
|
||||
|
||||
|
||||
# based on https://stackoverflow.com/a/39381428
|
||||
# Parses and executes a block of Python code, and returns the eval result of the last statement
|
||||
import ast
|
||||
def exec_then_eval(code, _globals, _locals):
|
||||
block = ast.parse(code, mode='exec')
|
||||
# assumes last node is an expression
|
||||
last = ast.Expression(block.body.pop().value)
|
||||
exec(compile(block, '<string>', mode='exec'), _globals, _locals)
|
||||
return eval(compile(last, '<string>', mode='eval'), _globals, _locals)
|
||||
|
||||
|
||||
class Conformance:
|
||||
def __init__(self, state: State, model: UUID, type_model: UUID):
|
||||
self.state = state
|
||||
|
|
@ -368,12 +379,13 @@ class Conformance:
|
|||
'get_all_instances': self.get_all_instances
|
||||
}
|
||||
# print("evaluating constraint ...", code)
|
||||
result = eval(
|
||||
loc = {**kwargs, **funcs}
|
||||
result = exec_then_eval(
|
||||
code,
|
||||
{'__builtins__': {'isinstance': isinstance, 'print': print,
|
||||
'int': int, 'float': float, 'bool': bool, 'str': str, 'tuple': tuple, 'len': len}
|
||||
}, # globals
|
||||
{**kwargs, **funcs} # locals
|
||||
loc # locals
|
||||
)
|
||||
# print('result =', result)
|
||||
return result
|
||||
|
|
@ -384,7 +396,8 @@ class Conformance:
|
|||
for subtype_name in self.sub_types[type_name]:
|
||||
# print(subtype_name, 'is subtype of ')
|
||||
result += [e_name for e_name, t_name in self.type_mapping.items() if t_name == subtype_name]
|
||||
return result
|
||||
result_with_ids = [ (e_name, self.bottom.read_outgoing_elements(self.model, e_name)[0]) for e_name in result]
|
||||
return result_with_ids
|
||||
|
||||
def check_constraints(self):
|
||||
"""
|
||||
|
|
@ -399,12 +412,11 @@ class Conformance:
|
|||
code = ActionCode(UUID(self.bottom.read_value(constraint)), self.bottom.state).read()
|
||||
return code
|
||||
|
||||
def check_result(result, local_or_global, tm_name, el_name=None):
|
||||
suffix = f"in '{el_name}'" if local_or_global == "Local" else ""
|
||||
def check_result(result, description):
|
||||
if not isinstance(result, bool):
|
||||
errors.append(f"{local_or_global} constraint `{code}` of '{tm_name}'{suffix} did not return boolean, instead got {type(result)} (value = {str(result)}).")
|
||||
elif not result:
|
||||
errors.append(f"{local_or_global} constraint `{code}` of '{tm_name}'{suffix} not satisfied.")
|
||||
raise Exception(f"{description} evaluation result is not boolean! Instead got {result}")
|
||||
if not result:
|
||||
errors.append(f"{description} not satisfied.")
|
||||
|
||||
# local constraints
|
||||
for m_name, tm_name in self.type_mapping.items():
|
||||
|
|
@ -415,7 +427,8 @@ class Conformance:
|
|||
morphisms = [m for m in morphisms if m in self.model_names]
|
||||
for m_element in morphisms:
|
||||
result = self.evaluate_constraint(code, element=m_element, type_name=tm_name)
|
||||
check_result(result, "Local", tm_name, m_name)
|
||||
description = f"Local constraint of \"{tm_name}\" in \"{m_name}\""
|
||||
check_result(result, description)
|
||||
|
||||
# global constraints
|
||||
glob_constraints = []
|
||||
|
|
@ -432,9 +445,9 @@ class Conformance:
|
|||
for tm_name in glob_constraints:
|
||||
code = get_code(tm_name)
|
||||
if code != None:
|
||||
# print('glob constr:', code)
|
||||
result = self.evaluate_constraint(code, model=self.model)
|
||||
check_result(result, "Global", tm_name)
|
||||
description = f"Global constraint \"{tm_name}\""
|
||||
check_result(result, description)
|
||||
return errors
|
||||
|
||||
def precompute_structures(self):
|
||||
|
|
|
|||
|
|
@ -15,12 +15,16 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
|
|||
string_type_id = state.read_dict(state.read_root(), "String")
|
||||
string_type = UUID(state.read_value(string_type_id))
|
||||
|
||||
actioncode_type_id = state.read_dict(state.read_root(), "ActionCode")
|
||||
actioncode_type = UUID(state.read_value(actioncode_type_id))
|
||||
|
||||
m_scd = scd.SCD(model, state)
|
||||
|
||||
ramified = state.create_node()
|
||||
ramified_scd = scd.SCD(ramified, state)
|
||||
|
||||
string_modelref = ramified_scd.create_model_ref("String", string_type)
|
||||
actioncode_modelref = ramified_scd.create_model_ref("ActionCode", actioncode_type)
|
||||
|
||||
classes = m_scd.get_classes()
|
||||
for class_name, class_node in classes.items():
|
||||
|
|
@ -44,7 +48,7 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
|
|||
# print(' creating attribute', attr_name, "with type String")
|
||||
# Every attribute becomes 'string' type
|
||||
# The string will be a Python expression
|
||||
ramified_attr_link = ramified_scd._create_attribute_link(prefix+class_name, string_modelref, prefix+attr_name, optional=True)
|
||||
ramified_attr_link = ramified_scd._create_attribute_link(prefix+class_name, actioncode_modelref, prefix+attr_name, optional=True)
|
||||
# traceability link
|
||||
bottom.create_edge(ramified_attr_link, attr_edge, RAMIFIES_LABEL)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from services.bottom.V0 import Bottom
|
|||
from transformation import ramify
|
||||
from services import od
|
||||
from services.primitives.string_type import String
|
||||
from services.primitives.actioncode_type import ActionCode
|
||||
from services.primitives.integer_type import Integer
|
||||
|
||||
def process_rule(state, lhs: UUID, rhs: UUID):
|
||||
|
|
@ -90,9 +91,9 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
# assume the type of the object is already the original type
|
||||
# this is because primitive types (e.g., Integer) are not RAMified
|
||||
type_name = od.get_object_name(bottom, pattern_mm, rhs_type)
|
||||
if type_name == "String":
|
||||
if type_name == "ActionCode":
|
||||
# Assume the string is a Python expression to evaluate
|
||||
python_expr = String(UUID(bottom.read_value(rhs_el_to_create)), bottom.state).read()
|
||||
python_expr = ActionCode(UUID(bottom.read_value(rhs_el_to_create)), bottom.state).read()
|
||||
result = eval(python_expr, {}, {})
|
||||
# Write the result into the host model.
|
||||
# This will be the *value* of an attribute. The attribute-link (connecting an object to the attribute) will be created as an edge later.
|
||||
|
|
@ -102,7 +103,7 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
m_od.create_string_value(model_el_name_to_create, result)
|
||||
name_mapping[pattern_name_to_create] = model_el_name_to_create
|
||||
else:
|
||||
raise Exception(f"RHS element '{pattern_name_to_create}' needs to be created in host, but has no un-RAMified type, and I don't know what to do with it. It's type is", type_name)
|
||||
raise Exception(f"RHS element '{pattern_name_to_create}' needs to be created in host, but has no un-RAMified type, and I don't know what to do with it. It's type is '{type_name}'")
|
||||
|
||||
# print("create edges....")
|
||||
for pattern_name_to_create, rhs_el_to_create, original_type, original_type_name, model_el_name_to_create in edges_to_create:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue