114 lines
4.0 KiB
Python
114 lines
4.0 KiB
Python
"""
|
|
Rulebook class is deisgned to work with strings.
|
|
this class applies markov algorightm to given string
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
from typing import ClassVar
|
|
from rule import Rule, EMPTY_SYMBOL
|
|
|
|
@dataclass
|
|
class Rulebook:
|
|
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 logged_transform(self, string: str) -> tuple[str | None, str]:
|
|
"""
|
|
Does exactly the same thing as transform, but logging the whole
|
|
process, so it can be used to create reports
|
|
|
|
NOTE: It's literally COPY + PASTE in it's worst, but I don't like
|
|
the idea of splitting anything apart, because of the specificity
|
|
of the task
|
|
|
|
Returns the result of transformations and full report
|
|
in str respectively
|
|
"""
|
|
CENTER_WIDTH: int = 30
|
|
log = "НАЧАЛО ЛИСТИНГА".center(CENTER_WIDTH, "=") + '\n'
|
|
log += self._log_rules()
|
|
log += f"Ввод: \"{string}\"\n"
|
|
for _ in range(self.MAX_DEPTH):
|
|
log += string + "\n"
|
|
rule = self.find_appropriate_rule(string)
|
|
if rule is None:
|
|
log += "NO_MORE_RULES".center(CENTER_WIDTH, "_") + '\n'
|
|
return string, log
|
|
log += " " * string.find(rule.operand) + self._log_rule(rule) + '\n'
|
|
string = self._apply_rule(rule, string)
|
|
if rule.is_blocking:
|
|
log += string + '\n'
|
|
log += "FINAL_RULE".center(CENTER_WIDTH, "_") + '\n'
|
|
return string, log
|
|
|
|
log += "OVERFLOW".center(CENTER_WIDTH, "_") + '\n'
|
|
return None, log
|
|
|
|
def _log_rule(self, rule) -> str:
|
|
arrow = "->|" if rule.is_blocking else "->"
|
|
return f"{rule.operand} {arrow} {rule.target}"
|
|
|
|
def _log_rules(self) -> str:
|
|
log = ""
|
|
for num, rule in enumerate(self.rules, start=1):
|
|
#log += f"\t{num} : {rule.operand} {arrow} {rule.target}\n"
|
|
log += " " * 4 + f"{num} : " + self._log_rule(rule) + "\n"
|
|
return log
|
|
|
|
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
|
|
|