Skip to content
Snippets Groups Projects
Commit 06b88571 authored by Andrea Franchini's avatar Andrea Franchini
Browse files

WIP: updating parser

parent a75a3e73
Branches
Tags
No related merge requests found
Showing
with 107 additions and 70 deletions
...@@ -3,8 +3,8 @@ import argparse ...@@ -3,8 +3,8 @@ import argparse
from mc_openapi.app_config import app from mc_openapi.app_config import app
from mc_openapi.doml_mc import DOMLVersion from mc_openapi.doml_mc import DOMLVersion
from mc_openapi.doml_mc.dsl_parser.exceptions import RequirementException from mc_openapi.doml_mc.domlr_parser.exceptions import RequirementException
from mc_openapi.doml_mc.dsl_parser.parser import Parser from mc_openapi.doml_mc.domlr_parser.parser import Parser
from mc_openapi.doml_mc.imc import RequirementStore from mc_openapi.doml_mc.imc import RequirementStore
from mc_openapi.doml_mc.intermediate_model.metamodel import MetaModelDocs from mc_openapi.doml_mc.intermediate_model.metamodel import MetaModelDocs
from mc_openapi.doml_mc.mc import ModelChecker from mc_openapi.doml_mc.mc import ModelChecker
......
...@@ -38,6 +38,6 @@ TOKENS: ...@@ -38,6 +38,6 @@ TOKENS:
FORALL: a 'forall' quantifier. FORALL: a 'forall' quantifier.
CLASS_PREFIX: a 'class' followed by a class name CLASS_PREFIX: a 'class' followed by a class name
ERR_DESC_SYMBOL: a '---' (three hyphens) to separate the logic expression from the error message. ERR_DESC_SYMBOL: a 'error:' to separate the logic expression from the error message.
HINTS: HINTS:
DOT: If you're trying to use a class, did you put 'class' before the class name? DOT: If you're trying to use a class, did you put 'class' before the class name?
\ No newline at end of file
requirements : requirement (requirement)* requirements : requirement (requirement)*
requirement : FLIP_EXPR req_name expression ERR_DESC_SYMBOL error_desc requirement : FLIP_EXPR req_name expression "error:" error_desc
req_name : ESCAPED_STRING req_name : ESCAPED_STRING
error_desc : ESCAPED_STRING error_desc : ESCAPED_STRING
...@@ -23,10 +23,12 @@ error_desc : ESCAPED_STRING ...@@ -23,10 +23,12 @@ error_desc : ESCAPED_STRING
| "(" expression ")" | "(" expression ")"
| property | property
?property : CONST "has" RELATIONSHIP (CONST|value) -> relationship_expr ?property : CONST "has" RELATIONSHIP (CONST|attr_rhs) -> relationship_expr
| const_or_class "is" const_or_class -> equality | const_or_class "is" const_or_class -> equality
| const_or_class "is not" const_or_class -> inequality | const_or_class "is not" const_or_class -> inequality
| value COMPARISON_OP value -> comparison
?attr_rhs : COMPARISON_OP value -> attribute_value
| COMPARISON_OP CONST RELATIONSHIP -> rhs_const_attribute
bound_consts : [CONST ("," CONST)*] bound_consts : [CONST ("," CONST)*]
...@@ -36,7 +38,6 @@ const_or_class : "class" CLASS ...@@ -36,7 +38,6 @@ const_or_class : "class" CLASS
value : ESCAPED_STRING value : ESCAPED_STRING
| NUMBER | NUMBER
| BOOL | BOOL
| VALUE
FLIP_EXPR : "+" FLIP_EXPR : "+"
| "-" | "-"
...@@ -45,22 +46,16 @@ COMPARISON_OP : ">" | ">=" ...@@ -45,22 +46,16 @@ COMPARISON_OP : ">" | ">="
| "<" | "<=" | "<" | "<="
| "==" | "!=" | "==" | "!="
BOOL : "!True" BOOL : ":true"
| "!False" | ":false"
// regex: /[a-zA-Z]+_[a-zA-Z]+::[a-zA-Z]+/ // regex: /[a-zA-Z]+_[a-zA-Z]+::[a-zA-Z]+/
//RELATIONSHIP: (LCASE_LETTER) ("_"|"::"|LETTER|DIGIT)*
RELATIONSHIP: /[^\W\d_]+\.[^\W\d_]+\.([^\W\d]|_)+/ RELATIONSHIP: /[^\W\d_]+\.[^\W\d_]+\.([^\W\d]|_)+/
//CLASS: (LCASE_LETTER) ("_"|LETTER|DIGIT)*
CLASS: /[^\W\d_]+\.[^\W\d_]+/ CLASS: /[^\W\d_]+\.[^\W\d_]+/
// Const must start with lowercase letter // Const must start with lowercase letter
CONST: (LCASE_LETTER) ("_"|LETTER|DIGIT)* CONST: (LCASE_LETTER) ("_"|LETTER|DIGIT)*
// Value must start with uppercase letter
VALUE: (UCASE_LETTER) ("_"|LETTER|DIGIT)*
ERR_DESC_SYMBOL: "---"
// Comment: python/sh style // Comment: python/sh style
COMMENT: /#[^\n]*/ COMMENT: /#[^\n]*/
......
...@@ -4,8 +4,8 @@ from typing import Callable ...@@ -4,8 +4,8 @@ from typing import Callable
import yaml import yaml
from lark import Lark, Transformer, UnexpectedCharacters from lark import Lark, Transformer, UnexpectedCharacters
from mc_openapi.doml_mc.dsl_parser.exceptions import RequirementBadSyntaxException from mc_openapi.doml_mc.domlr_parser.exceptions import RequirementBadSyntaxException
from mc_openapi.doml_mc.dsl_parser.utils import (RefHandler, StringValuesCache, from mc_openapi.doml_mc.domlr_parser.utils import (RefHandler, StringValuesCache,
VarStore) VarStore)
from mc_openapi.doml_mc.error_desc_helper import get_user_friendly_name from mc_openapi.doml_mc.error_desc_helper import get_user_friendly_name
from mc_openapi.doml_mc.imc import (Requirement, RequirementStore, SMTEncoding, from mc_openapi.doml_mc.imc import (Requirement, RequirementStore, SMTEncoding,
...@@ -16,6 +16,7 @@ from z3 import And, Exists, ExprRef, ForAll, Implies, Not, Or, Solver, Xor, simp ...@@ -16,6 +16,7 @@ from z3 import And, Exists, ExprRef, ForAll, Implies, Not, Or, Solver, Xor, simp
class ParserData: class ParserData:
def __init__(self) -> None: def __init__(self) -> None:
# TODO: Replace with files api?
grammar_path = os.path.join(os.path.dirname(__file__), "grammar.lark") grammar_path = os.path.join(os.path.dirname(__file__), "grammar.lark")
exceptions_path = os.path.join(os.path.dirname(__file__), "exceptions.yaml") exceptions_path = os.path.join(os.path.dirname(__file__), "exceptions.yaml")
with open(grammar_path, "r") as grammar: with open(grammar_path, "r") as grammar:
...@@ -30,19 +31,25 @@ class Parser: ...@@ -30,19 +31,25 @@ class Parser:
self.parser = Lark(grammar, start="requirements") self.parser = Lark(grammar, start="requirements")
def parse(self, input: str): def parse(self, input: str):
"""Parse the input string containing the DOMLR requirements and
returns a tuple with:
- RequirementStore with the parsed requirements inside
- A list of strings to be added to the string constant EnumSort
"""
try: try:
self.tree = self.parser.parse(input) self.tree = self.parser.parse(input)
const_store = VarStore() const_store = VarStore()
user_values_cache = StringValuesCache() user_values_cache = StringValuesCache()
transformer = DSLTransformer(const_store, user_values_cache) transformer = DOMLRTransformer(const_store, user_values_cache)
return RequirementStore(transformer.transform(self.tree)), user_values_cache.get_list() return RequirementStore(transformer.transform(self.tree)), user_values_cache.get_list()
except UnexpectedCharacters as e: except UnexpectedCharacters as e:
ctx = e.get_context(input) ctx = e.get_context(input)
msg = _get_error_desc_for_unexpected_characters(e, input) msg = _get_error_desc_for_unexpected_characters(e, input)
# TODO: Replace before production
print(msg) print(msg)
exit() exit()
...@@ -50,7 +57,7 @@ class Parser: ...@@ -50,7 +57,7 @@ class Parser:
# print() # print()
# raise RequirementBadSyntaxException(e.line, e.column, msg) # raise RequirementBadSyntaxException(e.line, e.column, msg)
class DSLTransformer(Transformer): class DOMLRTransformer(Transformer):
# These callbacks will be called when a rule with the same name # These callbacks will be called when a rule with the same name
# is matched. It starts from the leaves. # is matched. It starts from the leaves.
def __init__(self, def __init__(self,
...@@ -73,7 +80,7 @@ class DSLTransformer(Transformer): ...@@ -73,7 +80,7 @@ class DSLTransformer(Transformer):
flip_expr: bool = args[0].value == "-" flip_expr: bool = args[0].value == "-"
name: str = args[1] name: str = args[1]
expr: Callable[[SMTEncoding, SMTSorts], ExprRef] = args[2] expr: Callable[[SMTEncoding, SMTSorts], ExprRef] = args[2]
errd: Callable[[Solver, SMTSorts, IntermediateModel, int], str] = args[4] errd: Callable[[Solver, SMTSorts, IntermediateModel, int], str] = args[3]
index = self.const_store.get_index_and_push() index = self.const_store.get_index_and_push()
return Requirement( return Requirement(
expr, expr,
...@@ -90,6 +97,8 @@ class DSLTransformer(Transformer): ...@@ -90,6 +97,8 @@ class DSLTransformer(Transformer):
def req_name(self, args) -> str: def req_name(self, args) -> str:
return str(args[0].value.replace('"', '')) return str(args[0].value.replace('"', ''))
# Requirement requirement expression
def bound_consts(self, args): def bound_consts(self, args):
const_names = list(map(lambda arg: arg.value, args)) const_names = list(map(lambda arg: arg.value, args))
for name in const_names: for name in const_names:
...@@ -119,6 +128,7 @@ class DSLTransformer(Transformer): ...@@ -119,6 +128,7 @@ class DSLTransformer(Transformer):
return lambda enc, sorts: ForAll(args[0](enc, sorts), args[1](enc, sorts)) return lambda enc, sorts: ForAll(args[0](enc, sorts), args[1](enc, sorts))
def relationship_expr(self, args): def relationship_expr(self, args):
print(args)
rel_name = args[1].value rel_name = args[1].value
def _gen_rel_expr(enc: SMTEncoding, sorts: SMTSorts): def _gen_rel_expr(enc: SMTEncoding, sorts: SMTSorts):
...@@ -148,6 +158,25 @@ class DSLTransformer(Transformer): ...@@ -148,6 +158,25 @@ class DSLTransformer(Transformer):
return _gen_rel_expr return _gen_rel_expr
def attribute_value(self, args):
"""Parse rhs of expression containing:
<, <=, >, >=, ==, != can be followed by a NUMBER
==, != can be followed by a BOOL or STRING
should generate attr(e1, rel, attr_data_sort(VALUE)) for ==, !=
or attr(e1, rel, val) and get_int(val) >= NUMBER
"""
print(args)
return args
def rhs_const_attribute(self, args):
"""Parse rhs of expression
==, != followed by CONST RELATIONSHIP
should generate attr(e1, rel, val) and attr(e2, rel, val) and e1 COMP_OP e2
"""
print(args)
return args
def _get_equality_sides(self, arg1, arg2): def _get_equality_sides(self, arg1, arg2):
# We track use of const in const_or_class # We track use of const in const_or_class
if arg1.type == "CONST" and arg2.type == "CONST": if arg1.type == "CONST" and arg2.type == "CONST":
...@@ -181,35 +210,35 @@ class DSLTransformer(Transformer): ...@@ -181,35 +210,35 @@ class DSLTransformer(Transformer):
self.const_store.use(args[0].value) self.const_store.use(args[0].value)
return args[0] return args[0]
def comparison(self, args): # def comparison(self, args):
def _gen_comparison(enc: SMTEncoding, sorts: SMTSorts): # def _gen_comparison(enc: SMTEncoding, sorts: SMTSorts):
a = args[0](enc, sorts) # a = args[0](enc, sorts)
b = args[2](enc, sorts) # b = args[2](enc, sorts)
op = args[1].value # op = args[1].value
# To extract the `int` contained in the attr_data_sort, # # To extract the `int` contained in the attr_data_sort,
# we need to call its `get_int` method on the `DatatypeRef` # # we need to call its `get_int` method on the `DatatypeRef`
get_int = sorts.attr_data_sort.get_int # get_int = sorts.attr_data_sort.get_int
# TODO: Find a way to check if we're actually comparing Integers? # # TODO: Find a way to check if we're actually comparing Integers?
a = get_int(a) # a = get_int(a)
b = get_int(b) # b = get_int(b)
if op == ">": # if op == ">":
return a > b # return a > b
if op == ">=": # if op == ">=":
return a >= b # return a >= b
if op == "<": # if op == "<":
return a < b # return a < b
if op == "<=": # if op == "<=":
return a <= b # return a <= b
if op == "==": # if op == "==":
return a == b # return a == b
if op == "!=": # if op == "!=":
return a != b # return a != b
return _gen_comparison # return _gen_comparison
def value(self, args): def value(self, args):
type = args[0].type type = args[0].type
......
from difflib import get_close_matches from difflib import get_close_matches
from mc_openapi.doml_mc.dsl_parser.exceptions import \ from mc_openapi.doml_mc.domlr_parser.exceptions import \
RequirementMissingKeyException RequirementMissingKeyException
from mc_openapi.doml_mc.imc import SMTEncoding, SMTSorts from mc_openapi.doml_mc.imc import SMTEncoding, SMTSorts
from z3 import Const, DatatypeRef, ExprRef, FuncDeclRef, SortRef, Ints from z3 import Const, DatatypeRef, ExprRef, FuncDeclRef, SortRef, Ints
......
...@@ -52,6 +52,7 @@ class DOMLAssociation: ...@@ -52,6 +52,7 @@ class DOMLAssociation:
MetaModel = dict[str, DOMLClass] MetaModel = dict[str, DOMLClass]
InverseAssociation = list[tuple[str, str]] InverseAssociation = list[tuple[str, str]]
MetaModelDocs: dict[DOMLVersion, dict] = {}
MetaModels: dict[DOMLVersion, MetaModel] = {} MetaModels: dict[DOMLVersion, MetaModel] = {}
InverseAssociations: dict[DOMLVersion, InverseAssociation] = {} InverseAssociations: dict[DOMLVersion, InverseAssociation] = {}
...@@ -151,9 +152,12 @@ def parse_inverse_associations(doc: dict) -> list[tuple[str, str]]: ...@@ -151,9 +152,12 @@ def parse_inverse_associations(doc: dict) -> list[tuple[str, str]]:
def init_metamodels(): def init_metamodels():
global MetaModels, InverseAssociations global MetaModelDocs, MetaModels, InverseAssociations
for ver in DOMLVersion: for ver in DOMLVersion:
mmdoc = yaml.load(ilres.read_text(assets, f"doml_meta_{ver.value}.yaml"), yaml.Loader) source = ilres.files(assets).joinpath(f"doml_meta_{ver.value}.yaml")
mmdoc = yaml.load(source.read_text() , yaml.Loader)
MetaModelDocs[ver] = mmdoc
MetaModels[ver] = parse_metamodel(mmdoc) MetaModels[ver] = parse_metamodel(mmdoc)
InverseAssociations[ver] = parse_inverse_associations(mmdoc) InverseAssociations[ver] = parse_inverse_associations(mmdoc)
......
...@@ -9,8 +9,7 @@ from .consistency_reqs import (get_association_multiplicity_reqs, ...@@ -9,8 +9,7 @@ from .consistency_reqs import (get_association_multiplicity_reqs,
get_attribute_multiplicity_reqs, get_attribute_multiplicity_reqs,
get_attribute_type_reqs, get_attribute_type_reqs,
get_inverse_association_reqs) get_inverse_association_reqs)
from .dsl_parser.parser import Parser from .imc import IntermediateModelChecker, RequirementStore
from .imc import IntermediateModelChecker, Requirement, RequirementStore
from .intermediate_model.metamodel import (DOMLVersion, InverseAssociations, from .intermediate_model.metamodel import (DOMLVersion, InverseAssociations,
MetaModels) MetaModels)
from .mc_result import MCResult, MCResults from .mc_result import MCResult, MCResults
......
import warnings
warnings.warn("the synthesis module is deprecated", DeprecationWarning,
stacklevel=2)
\ No newline at end of file
...@@ -6,7 +6,7 @@ from pyecore.ecore import EClass, EObject ...@@ -6,7 +6,7 @@ from pyecore.ecore import EClass, EObject
from mc_openapi.doml_mc.intermediate_model.doml_element import \ from mc_openapi.doml_mc.intermediate_model.doml_element import \
IntermediateModel IntermediateModel
from mc_openapi.doml_mc.intermediate_model.metamodel import DOMLVersion from mc_openapi.doml_mc.intermediate_model.metamodel import DOMLVersion
from mc_openapi.doml_mc.synthesis.synthesis import AssocAndElems from mc_openapi.doml_mc.synthesis_old.synthesis import AssocAndElems
from mc_openapi.doml_mc.xmi_parser.doml_model import get_rset from mc_openapi.doml_mc.xmi_parser.doml_model import get_rset
import secrets import secrets
......
...@@ -21,8 +21,9 @@ def init_doml_rsets(): # noqa: E302 ...@@ -21,8 +21,9 @@ def init_doml_rsets(): # noqa: E302
global doml_rsets global doml_rsets
for ver in DOMLVersion: for ver in DOMLVersion:
rset = ResourceSet() rset = ResourceSet()
source = ilres.files(assets).joinpath(f"doml_{ver.value}.ecore")
resource = rset.get_resource(BytesURI( resource = rset.get_resource(BytesURI(
"doml", bytes=ilres.read_binary(assets, f"doml_{ver.value}.ecore") "doml", bytes=source.read_bytes()
)) ))
doml_metamodel = resource.contents[0] doml_metamodel = resource.contents[0]
......
...@@ -2,27 +2,33 @@ ...@@ -2,27 +2,33 @@
vm is class infrastructure.VirtualMachine vm is class infrastructure.VirtualMachine
and and
not exists iface ( not exists iface (
vm has infrastructure.ComputingNode->ifaces iface vm has infrastructure.ComputingNode.ifaces iface
) )
---
"VM {vm} must have iface {iface}" error: "VM {vm} must have iface {iface}"
+ "VM must have iface" + "VM must have iface"
forall vm ( forall vm (
vm is class infrastructure.VirtualMachine vm is class infrastructure.VirtualMachine
implies implies
exists iface ( exists iface (
vm has infrastructure.ComputingNode->ifaces iface vm has infrastructure.ComputingNode.ifaces iface
) )
) )
---
"VM {vm} must have iface {iface}" error: "VM {vm} must have iface {iface}"
# - "Iface must be unique"
# ni1 has infrastructure.NetworkInterface.endPoint Value
# and
# ni1 is not ni2
# and
# ni2 has infrastructure.NetworkInterface.endPoint Value
- "Iface must be unique" - "Iface must be unique"
ni1 has infrastructure.NetworkInterface->endPoint Value ni1 has infrastructure.NetworkInterface.endPoint == ni2 infrastructure.NetworkInterface.endPoint
and and
ni1 is not ni2 ni1 is not ni2
and
ni2 has infrastructure.NetworkInterface->endPoint Value
--- error: "Iface {ni1} and {ni2} must have different values"
"Iface {ni1} and {ni2} must have different values"
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
vm is class infrastructure.VirtualMachine vm is class infrastructure.VirtualMachine
and and
not exists iface ( not exists iface (
vm has infrastructure.ComputingNode->ifaces iface vm has infrastructure.ComputingNode.ifaces iface
) )
--- ---
"VM {vm} has no associated interface." "VM {vm} has no associated interface."
...@@ -13,9 +13,9 @@ ...@@ -13,9 +13,9 @@
vm is class infrastructure.VirtualMachine vm is class infrastructure.VirtualMachine
and and
not exists iface ( not exists iface (
vm has infrastructure.ComputingNode->ifaces iface vm has infrastructure.ComputingNode.ifaces iface
and and
vm has infrastructure.ComputingNode->ifaces X vm has infrastructure.ComputingNode.ifaces X
) )
--- ---
"VM {vm} has no associated interface." "VM {vm} has no associated interface."
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment