OverridesResolver

This commit is contained in:
Jesse Bannon 2023-07-18 00:10:01 -07:00
parent fe095b151c
commit ebf7989681
4 changed files with 147 additions and 11 deletions

View file

@ -0,0 +1,65 @@
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from ytdl_sub.script.functions import Resolvable
from ytdl_sub.script.parser import parse
from ytdl_sub.script.syntax_tree import SyntaxTree
from ytdl_sub.script.syntax_tree import Variable
from ytdl_sub.utils.exceptions import StringFormattingException
class OverridesResolver:
def __init__(self, overrides: Dict[str, str]):
self.overrides: Dict[Variable, SyntaxTree] = {
Variable(name=name): parse(value) for name, value in overrides.items()
}
def _ensure_no_cycles(self) -> None:
variable_dependencies: Dict[Variable, Set[Variable]] = {
variable: ast.variables for variable, ast in self.overrides.items()
}
def _traverse(
to_variable: Variable, visited_variables: Optional[List[Variable]] = None
) -> None:
if visited_variables is None:
visited_variables = []
if to_variable in visited_variables:
raise StringFormattingException("Detected cycle in variables")
visited_variables.append(to_variable)
for dep in variable_dependencies[to_variable]:
_traverse(to_variable=dep, visited_variables=visited_variables)
for variable in variable_dependencies.keys():
_traverse(variable)
def resolve_overrides(self) -> Dict[str, str]:
self._ensure_no_cycles()
unresolved_variables: List[Variable] = list(self.overrides.keys())
resolved_variables: Dict[Variable, Resolvable] = {}
while unresolved_variables:
unresolved_count: int = len(unresolved_variables)
for variable in unresolved_variables:
if not self.overrides[variable].has_variable_dependency(
resolved_variables=resolved_variables
):
resolved_variables[variable] = self.overrides[variable].resolve(
resolved_variables=resolved_variables
)
unresolved_variables.remove(variable)
assert (
len(unresolved_variables) != unresolved_count
), "did not resolve any variables, cycle detected"
return {
variable.name: resolvable.resolve()
for variable, resolvable in resolved_variables.items()
}

View file

@ -5,10 +5,10 @@ from ytdl_sub.script.functions import Boolean
from ytdl_sub.script.functions import Float
from ytdl_sub.script.functions import Integer
from ytdl_sub.script.functions import String
from ytdl_sub.script.types import ArgumentType
from ytdl_sub.script.types import Function
from ytdl_sub.script.types import SyntaxTree
from ytdl_sub.script.types import Variable
from ytdl_sub.script.syntax_tree import ArgumentType
from ytdl_sub.script.syntax_tree import Function
from ytdl_sub.script.syntax_tree import SyntaxTree
from ytdl_sub.script.syntax_tree import Variable
from ytdl_sub.utils.exceptions import StringFormattingException
from ytdl_sub.validators.string_formatter_validators import is_valid_source_variable_name

View file

@ -1,14 +1,18 @@
from abc import ABC
from abc import abstractmethod
from dataclasses import dataclass
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from typing import Union
from typing import final
from ytdl_sub.script.functions import Boolean
from ytdl_sub.script.functions import Float
from ytdl_sub.script.functions import Functions
from ytdl_sub.script.functions import Integer
from ytdl_sub.script.functions import Resolvable
from ytdl_sub.script.functions import String
from ytdl_sub.utils.exceptions import StringFormattingException
@ -22,11 +26,33 @@ ArgumentType = Union[Integer, Float, String, Boolean, Variable, "Function"]
@dataclass(frozen=True)
class Function:
class VariableDependency(ABC):
@property
@abstractmethod
def variables(self) -> Set[Variable]:
raise NotImplemented()
@abstractmethod
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> str:
raise NotImplemented()
@final
def has_variable_dependency(self, resolved_variables: Dict[Variable, Resolvable]) -> bool:
"""
Returns
-------
True if variable dependency. False otherwise.
"""
return self.variables.issubset(set(resolved_variables.keys()))
@dataclass(frozen=True)
class Function(VariableDependency):
name: str
args: List[ArgumentType]
def __post_init__(self):
# TODO: Figure out resolution via introspecting args and outputs of function
try:
getattr(Functions, self.name)
except AttributeError:
@ -48,9 +74,12 @@ class Function:
return variables
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable:
raise NotImplemented()
@dataclass(frozen=True)
class SyntaxTree:
class SyntaxTree(VariableDependency):
ast: List[String | Variable | Function]
@property
@ -69,6 +98,20 @@ class SyntaxTree:
return variables
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable:
output: str = ""
for token in self.ast:
if isinstance(token, String):
output += token.resolve()
elif isinstance(token, Variable):
output += resolved_variables[token].resolve()
elif isinstance(token, Function):
output += token.resolve(resolved_variables=resolved_variables)
else:
assert False, "should never reach"
return String(output)
@classmethod
def detect_cycles(cls, parsed_overrides: Dict[str, "SyntaxTree"]) -> None:
"""
@ -98,5 +141,33 @@ class SyntaxTree:
_traverse(variable)
@classmethod
def resolve(cls, parsed_overrides: Dict[str, "SyntaxTree"]) -> Dict[str, str]:
raise NotImplemented()
def resolve_overrides(cls, parsed_overrides: Dict[str, "SyntaxTree"]) -> Dict[str, str]:
cls.detect_cycles(parsed_overrides=parsed_overrides)
overrides: Dict[Variable, "SyntaxTree"] = {
Variable(name): ast for name, ast in parsed_overrides.items()
}
unresolved_variables: List[Variable] = list(overrides.keys())
resolved_variables: Dict[Variable, Resolvable] = {}
while unresolved_variables:
unresolved_count: int = len(unresolved_variables)
for variable in unresolved_variables:
if not overrides[variable].has_variable_dependency(
resolved_variables=resolved_variables
):
resolved_variables[variable] = overrides[variable].resolve(
resolved_variables=resolved_variables
)
unresolved_variables.remove(variable)
assert (
len(unresolved_variables) != unresolved_count
), "did not resolve any variables, cycle detected"
return {
variable.name: resolvable.resolve()
for variable, resolvable in resolved_variables.items()
}

View file

@ -5,9 +5,9 @@ from ytdl_sub.script.functions import Float
from ytdl_sub.script.functions import Integer
from ytdl_sub.script.functions import String
from ytdl_sub.script.parser import parse
from ytdl_sub.script.types import Function
from ytdl_sub.script.types import SyntaxTree
from ytdl_sub.script.types import Variable
from ytdl_sub.script.syntax_tree import Function
from ytdl_sub.script.syntax_tree import SyntaxTree
from ytdl_sub.script.syntax_tree import Variable
from ytdl_sub.utils.exceptions import StringFormattingException