Skip to content

Commit

Permalink
Merge pull request #97 from mopichalova/88-regex-labels
Browse files Browse the repository at this point in the history
Rule labels in regulations and regular regulations
  • Loading branch information
xtrojak authored Mar 20, 2024
2 parents 0b3ccae + 567edc2 commit 2e3b301
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Testing/models/regulation5.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#! regulation
type regular
(r1_Sr1_Tr2|r1_Tr1_Sr2)
(r1_S;r1_T;r2|r1_T;r1_S;r2)
2 changes: 1 addition & 1 deletion eBCSgen/Core/Formula.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from lark import Transformer, Tree

from eBCSgen.Errors.ComplexOutOfScope import ComplexOutOfScope
from eBCSgen.Core.Rate import tree_to_string
from eBCSgen.utils import tree_to_string


class Formula:
Expand Down
13 changes: 1 addition & 12 deletions eBCSgen/Core/Rate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from sortedcontainers import SortedList

from eBCSgen.TS.State import Vector
from eBCSgen.utils import tree_to_string

STATIC_MATH = """<kineticLaw><math xmlns="http://www.w3.org/1998/Math/MathML"><apply>{}</apply></math></kineticLaw>"""

Expand Down Expand Up @@ -252,15 +253,3 @@ def fix_operator(self, node, matches):
operator = self.operators[matches[1].type]
return Tree(node, [matches[0], Token(matches[1].type, operator), matches[2]])


def tree_to_string(tree):
"""
Recursively constructs a list form given lark tree.
:param tree: given lark tree
:return: list of components
"""
if type(tree) == Tree:
return sum(list(map(tree_to_string, tree.children)), [])
else:
return [str(tree)]
6 changes: 6 additions & 0 deletions eBCSgen/Errors/RegulationParsingError.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class RegulationParsingError(Exception):
def __init__(self, error):
self.error = error

def __str__(self):
return "Error while parsing the regulation:\n\n{}".format(self.error)
56 changes: 50 additions & 6 deletions eBCSgen/Parsing/ParseBCSL.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numpy as np
from numpy import inf
from copy import deepcopy
from lark import Lark, Transformer, Tree
from lark import Lark, Token, Transformer, Tree
from lark import UnexpectedCharacters, UnexpectedToken, UnexpectedEOF
from lark.load_grammar import _TERMINAL_NAMES
import regex
Expand All @@ -26,6 +26,8 @@
from eBCSgen.Core.Model import Model
from eBCSgen.Errors.ComplexParsingError import ComplexParsingError
from eBCSgen.Errors.UnspecifiedParsingError import UnspecifiedParsingError
from eBCSgen.Errors.RegulationParsingError import RegulationParsingError
from eBCSgen.utils import tree_to_string


def load_TS_from_json(json_file: str) -> TransitionSystem:
Expand Down Expand Up @@ -189,7 +191,7 @@ def to_side(self):
REGULATIONS_GRAMMAR = """
regulation_def: "type" ( regular | programmed | ordered | concurrent_free | conditional )
!regular: "regular" _NL+ (DIGIT|LETTER| "+" | "*" | "(" | ")" | "[" | "]" | "_" | "|" | "&")+ _NL*
!regular: "regular" _NL+ expression _NL*
programmed: "programmed" _NL+ (successors _NL+)* successors _NL*
successors: CNAME ":" "{" CNAME ("," CNAME)* "}"
Expand All @@ -203,6 +205,40 @@ def to_side(self):
context: CNAME ":" "{" rate_complex ("," rate_complex)* "}"
"""

REGEX_GRAMMAR = r"""
!?expression: term ("|" term)*
?term: factor+
?factor: primary quantifier?
!quantifier: "??"
| "*?"
| "+?"
| "*+"
| "++"
| "?+"
| "*"
| "+"
| "?"
| "{NUMBER,NUMBER}"
| "{NUMBER}"
!primary: "(" expression ")"
| "[" REGEX_CHAR "-" REGEX_CHAR "]"
| "[" REGEX_CHAR* "]"
| SPECIAL_CHAR
| ESCAPED_CHAR
| "."
| REGEX_CHAR
SPECIAL_CHAR: "^" | "$" | "&"
ESCAPED_CHAR: "\\" ("w"|"W"|"d"|"D"|"s"|"S"|"b"|"B"|"A"|"Z"|"G"|"."|"^"|"["|"]"|"("|")"|"{"|"}"|"?"|"*"|"+"|"|"|"\\")
REGEX_CHAR: /[^\\^$().*+?{}\[\]|]/
"""


class TransformRegulations(Transformer):
def regulation(self, matches):
Expand All @@ -212,9 +248,11 @@ def regulation_def(self, matches):
return matches[0]

def regular(self, matches):
re = "".join(matches[1:])
# might raise exception
regex.compile(re)
re = "".join(tree_to_string(matches[1]))
try:
regex.compile(re)
except regex.error as e:
raise RegulationParsingError(f"Invalid regular expression: {re}. Error: {e}")
return Regular(re)

def programmed(self, matches):
Expand Down Expand Up @@ -527,6 +565,7 @@ class TreeToObjects(Transformer):
def __init__(self):
super(TreeToObjects, self).__init__()
self.params = set()
self.labels = set()

"""
A transformer which is called on a tree in a bottom-up manner and transforms all subtrees/tokens it encounters.
Expand Down Expand Up @@ -584,6 +623,8 @@ def rule(self, matches):
lhs, arrow, rhs, rate1 = matches
else:
lhs, arrow, rhs = matches
if label:
self.labels.add(label)
agents = tuple(lhs.seq + rhs.seq)
mid = lhs.counter
compartments = lhs.comp + rhs.comp
Expand Down Expand Up @@ -678,7 +719,9 @@ def model(self, matches):
elif key == "regulation":
if regulation:
raise UnspecifiedParsingError("Multiple regulations")
regulation = value
# check if regulation is in label
if value.check_labels(self.labels):
regulation = value

params = self.params - set(definitions)
return Model(rules, inits, definitions, params, regulation)
Expand All @@ -693,6 +736,7 @@ def __init__(self, start):
+ COMPLEX_GRAMMAR
+ EXTENDED_GRAMMAR
+ REGULATIONS_GRAMMAR
+ REGEX_GRAMMAR
)
self.parser = Lark(
grammar, parser="earley", propagate_positions=False, maybe_placeholders=False
Expand Down
8 changes: 8 additions & 0 deletions eBCSgen/Regulations/ConcurrentFree.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from eBCSgen.Errors.RegulationParsingError import RegulationParsingError
from eBCSgen.Regulations.Base import BaseRegulation


Expand All @@ -22,3 +23,10 @@ def filter(self, current_state, candidates):
if p_rule and non_p_rule:
del candidates[non_p_rule.pop()]
return candidates

def check_labels(self, model_labels):
for tuple in self.regulation:
for label in tuple:
if label not in model_labels:
raise RegulationParsingError(f"Label {label} in concurrent-free regulation not present in model")
return True
7 changes: 7 additions & 0 deletions eBCSgen/Regulations/Conditional.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from eBCSgen.Errors.RegulationParsingError import RegulationParsingError
from eBCSgen.Regulations.Base import BaseRegulation


Expand All @@ -19,6 +20,12 @@ def __repr__(self):
def filter(self, current_state, candidates):
agents = set(current_state.content.value)
return {rule: values for rule, values in candidates.items() if not self.check_intersection(rule.label, agents)}

def check_labels(self, model_labels):
for label in self.regulation:
if label not in model_labels:
raise RegulationParsingError(f"Label {label} in conditional regulation not present in model")
return True

def check_intersection(self, label, agents):
if label not in self.regulation:
Expand Down
8 changes: 8 additions & 0 deletions eBCSgen/Regulations/Ordered.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from eBCSgen.Errors.RegulationParsingError import RegulationParsingError
from eBCSgen.Regulations.Base import BaseRegulation


Expand Down Expand Up @@ -38,3 +39,10 @@ def filter(self, current_state, candidates):
return candidates
last_rule = current_state.memory.history[-1]
return {rule: values for rule, values in candidates.items() if not (last_rule, rule.label) in self.regulation}

def check_labels(self, model_labels):
for tuple in self.regulation:
for label in tuple:
if label not in model_labels:
raise RegulationParsingError(f"Label {label} in programmed regulation not present in model")
return True
10 changes: 10 additions & 0 deletions eBCSgen/Regulations/Programmed.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from eBCSgen.Errors.RegulationParsingError import RegulationParsingError
from eBCSgen.Regulations.Base import BaseRegulation


Expand All @@ -24,3 +25,12 @@ def filter(self, current_state, candidates):
if last_rule in self.regulation:
return {rule: values for rule, values in candidates.items() if rule.label in self.regulation[last_rule]}
return candidates

def check_labels(self, model_labels):
for rule_label, successors_labels in self.regulation.items():
if rule_label not in model_labels:
raise RegulationParsingError(f"Label {rule_label} in programmed regulation not present in model")
if not successors_labels.issubset(model_labels):
missing_labels = successors_labels - model_labels
raise RegulationParsingError(f"Label(s) {missing_labels} in programmed regulation not present in model")
return True
16 changes: 16 additions & 0 deletions eBCSgen/Regulations/Regular.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import regex
from eBCSgen.Errors.RegulationParsingError import RegulationParsingError

from eBCSgen.Regulations.Base import BaseRegulation

Expand All @@ -23,3 +24,18 @@ def filter(self, current_state, candidates):
path = "".join(current_state.memory.history)
return {rule: values for rule, values in candidates.items()
if self.regulation.fullmatch(path + rule.label, partial=True) is not None}

def check_labels(self, model_labels):
patterns = self.regulation.pattern.replace("(", "").replace(")", "").split("|")
subpaterns_set = set()
for pattern in patterns:
subpaterns_set = subpaterns_set.union(set(pattern.split(";")))

for subpattern in subpaterns_set:
subregex = regex.compile(subpattern)
if any(subregex.search(label) for label in model_labels):
continue
raise RegulationParsingError(
f"Label in programmed regulation not present in model"
)
return True
14 changes: 14 additions & 0 deletions eBCSgen/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from lark import Tree


def tree_to_string(tree):
"""
Recursively constructs a list form given lark tree.
:param tree: given lark tree
:return: list of components
"""
if type(tree) == Tree:
return sum(list(map(tree_to_string, tree.children)), [])
else:
return [str(tree)]

0 comments on commit 2e3b301

Please sign in to comment.