diff --git a/src/rulebook.py b/src/rulebook.py index 90a52d0..8a202ee 100644 --- a/src/rulebook.py +++ b/src/rulebook.py @@ -4,13 +4,68 @@ this class applies markov algorightm to given string """ from dataclasses import dataclass -from collections import OrderedDict +from typing import ClassVar +from rule import Rule, EMPTY_SYMBOL @dataclass class Rulebook: - rules: OrderedDict[str, str] + MAX_DEPTH: ClassVar[int] = 100 + rules: list[Rule] + + def transform(self, string: str) -> str: + """ + Applies the next appropriate rule to given string. The + priority of rules is determined by their indexes in list + of rules. Lesser the index - higher the priority. + + Return transformed string + """ + + for _ in range(self.MAX_DEPTH): + rule = self.find_appropriate_rule(string) + if rule is None: + return string + string = self._apply_rule(rule, string) + if rule.is_blocking: + return string + raise ValueError("The amount of transformations exceeded " + f"{self.MAX_DEPTH}. You can change maximum " + "amount by setting class variable MAX_DEPTH," + " but may be something wrong with your input") + + + def find_appropriate_rule(self, string) -> Rule | None: + """ + Searches for appropriate rule in rules list and if + found one returns it, otherwise returns None + """ + for rule in self.rules: + if self._is_rule_appropriate(rule, string): + return rule + + def _apply_rule(self, rule: Rule, string: str) -> str: + """ + Tries to apply given rule to string. Doesn't check + wether the rule is appropriate or not. Inapplicability + leads to undefined behaviour + """ + if rule.operand == EMPTY_SYMBOL: + string = EMPTY_SYMBOL + string + + string = ( + string + .replace(rule.operand, rule.target, 1) + .replace(EMPTY_SYMBOL, '') + ) + return string + + def _is_rule_appropriate(self, rule: Rule, string: str) -> bool: + """ + Concrete realization of rule applicability check. Moved to separate + function mostly for extensibility purposes + """ + # Hardest part is always about empty set + if rule.operand == EMPTY_SYMBOL: + return True + return rule.operand in string - def __call__(self, string: str): - """aplies rule to the given string""" - raise NotImplementedError("Sorry, we still don't know how to apply" - "algorithm to your string") diff --git a/src/rulesparser.py b/src/rulesparser.py index b5bbb5c..2fa0af2 100644 --- a/src/rulesparser.py +++ b/src/rulesparser.py @@ -38,12 +38,31 @@ class RulesParser: raise ValueError(f"Can't recognize transform symbol. " f"\"{self.TRANSFORM}\" or \"{self.B_TRANSFORM}\"" f" expected, but \"{arrow}\" encountered") + + #optimising empty symbol return Rule( - operand=tokens[0], - target=tokens[2], + operand=self._optimise_empty(tokens[0]), + target=self._optimise_empty(tokens[2]), is_blocking=is_blocking ) + def _optimise_empty(self, string: str) -> str: + """ + Empty symbol has meaning only while it's the only + symbol in the string (I hope i'm not wrong right now), + so all empty symbols can be optimised + + Returns sting without EMPTY symbols if deleting them + is semantically possible, returns unchanges string if + nothing can be optimised + + NOTE: right now contains naive implementation + """ + string = re.sub(self.EMPTY+'+', EMPTY_SYMBOL, string) + if re.fullmatch(self.EMPTY, string): + return string + return re.sub(self.EMPTY, '', string) + def _get_lines(self, src: str) -> list[str]: """ Get cleaned lines only with rules to parse @@ -65,5 +84,4 @@ class RulesParser: Strips whitespaces at the end of lines """ result = re.sub(r' +$', '', src, flags=re.M) - # result = re.sub(r"\n+", r'\n', result) return result