refactored, array working

This commit is contained in:
Jesse Bannon 2023-11-07 21:34:43 -08:00
parent 7483a1315b
commit 20ad7b4f7e
10 changed files with 142 additions and 78 deletions

View file

@ -1,6 +1,7 @@
from typing import List
from ytdl_sub.script.types.resolvable import Array
from ytdl_sub.script.types.array import Array
from ytdl_sub.script.types.resolvable import Integer
from ytdl_sub.script.types.resolvable import Resolvable
@ -12,3 +13,18 @@ class ArrayFunctions:
output.extend(array.value)
return Array(output)
@staticmethod
def at(array: Array, idx: Integer) -> Resolvable:
return array.value[idx.value]
@staticmethod
def flatten_array(array: Array) -> Array:
output: List[Resolvable] = []
for elem in array.value:
if isinstance(elem, Array):
output.extend(ArrayFunctions.flatten_array(elem).value)
else:
output.append(elem)
return Array(output)

View file

@ -1,6 +1,8 @@
from typing import List, Dict
from typing import Dict
from typing import List
from ytdl_sub.script.types.resolvable import Array, Map
from ytdl_sub.script.types.array import Array
from ytdl_sub.script.types.resolvable import Map
from ytdl_sub.script.types.resolvable import Resolvable
from ytdl_sub.utils.exceptions import StringFormattingException

View file

@ -2,9 +2,10 @@ from typing import List
from typing import Optional
from ytdl_sub.script.syntax_tree import SyntaxTree
from ytdl_sub.script.types.array import Array
from ytdl_sub.script.types.array import UnresolvedArray
from ytdl_sub.script.types.function import ArgumentType
from ytdl_sub.script.types.function import Function
from ytdl_sub.script.types.resolvable import Array
from ytdl_sub.script.types.resolvable import Boolean
from ytdl_sub.script.types.resolvable import Float
from ytdl_sub.script.types.resolvable import Integer
@ -20,7 +21,7 @@ class _Parser:
def __init__(self, text: str):
self._text = text
self._pos = 0
self._ast: List[String | Variable | Function] = []
self._ast: List[ArgumentType] = []
self._syntax_tree = self._parse()
@ -157,7 +158,7 @@ class _Parser:
Begin parsing a function after reading the first ``%``
"""
function_name: str = ""
function_args: List[String | Variable | "Function"] = []
function_args: List[ArgumentType] = []
while ch := self._read():
if ch == ")":
@ -170,16 +171,16 @@ class _Parser:
raise StringFormattingException("Invalid function")
def _parse_array(self) -> Array:
def _parse_array(self) -> UnresolvedArray:
"""
Begin parsing an array after reading the first ``[``
"""
function_args: List[String | Variable | "Function"] = []
function_args: List[ArgumentType] = []
while ch := self._read(increment_pos=False):
if ch == "]":
self._pos += 1
return Array(value=function_args)
return UnresolvedArray(value=function_args)
else:
function_args = self._parse_args(breaking_char="]")

View file

@ -1,20 +1,20 @@
from dataclasses import dataclass
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from ytdl_sub.script.types.function import Function
from ytdl_sub.script.types.function import VariableDependency
from ytdl_sub.script.types.resolvable import ArgumentType
from ytdl_sub.script.types.resolvable import Resolvable
from ytdl_sub.script.types.resolvable import String
from ytdl_sub.script.types.variable import Variable
from ytdl_sub.script.types.variable_dependency import VariableDependency
from ytdl_sub.utils.exceptions import StringFormattingException
@dataclass(frozen=True)
class SyntaxTree(VariableDependency):
ast: List[String | Variable | Function]
ast: List[ArgumentType]
@property
def variables(self) -> Set[Variable]:
@ -35,14 +35,9 @@ class SyntaxTree(VariableDependency):
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable:
resolved: List[Resolvable] = []
for token in self.ast:
if isinstance(token, Resolvable):
resolved.append(token)
elif isinstance(token, Variable):
resolved.append(resolved_variables[token])
elif isinstance(token, Function):
resolved.append(token.resolve(resolved_variables=resolved_variables))
else:
assert False, "should never reach"
resolved.append(
self._resolve_argument_type(resolved_variables=resolved_variables, arg=token)
)
# If only one resolvable resides in the AST, return as that
if len(resolved) == 1:

View file

@ -0,0 +1,36 @@
from dataclasses import dataclass
from typing import Dict
from typing import List
from typing import Set
from ytdl_sub.script.types.resolvable import ArgumentType
from ytdl_sub.script.types.resolvable import Resolvable
from ytdl_sub.script.types.variable import Variable
from ytdl_sub.script.types.variable_dependency import VariableDependency
@dataclass(frozen=True)
class Array:
value: List[Resolvable]
@dataclass(frozen=True)
class UnresolvedArray(Array, VariableDependency, ArgumentType):
value: List[ArgumentType]
@property
def variables(self) -> Set[Variable]:
return {value for value in self.value if isinstance(value, Variable)}
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable:
return ResolvedArray(
[
self._resolve_argument_type(resolved_variables=resolved_variables, arg=arg)
for arg in self.value
]
)
@dataclass(frozen=True)
class ResolvedArray(Array, Resolvable):
pass

View file

@ -1,7 +1,5 @@
import functools
import inspect
from abc import ABC
from abc import abstractmethod
from dataclasses import dataclass
from inspect import FullArgSpec
from typing import Callable
@ -11,45 +9,18 @@ from typing import Optional
from typing import Set
from typing import Type
from typing import Union
from typing import final
from typing import get_origin
from ytdl_sub.script.functions import Functions
from ytdl_sub.script.types.resolvable import Array
from ytdl_sub.script.types.resolvable import Boolean
from ytdl_sub.script.types.resolvable import Float
from ytdl_sub.script.types.resolvable import Integer
from ytdl_sub.script.types.resolvable import ArgumentType
from ytdl_sub.script.types.resolvable import Resolvable
from ytdl_sub.script.types.resolvable import Resolvable_0
from ytdl_sub.script.types.resolvable import Resolvable_1
from ytdl_sub.script.types.resolvable import Resolvable_2
from ytdl_sub.script.types.resolvable import String
from ytdl_sub.script.types.variable import Variable
from ytdl_sub.script.types.variable_dependency import VariableDependency
from ytdl_sub.utils.exceptions import StringFormattingException
ArgumentType = Union[Integer, Float, String, Boolean, Variable, "Function", Array]
@dataclass(frozen=True)
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 not self.variables.issubset(set(resolved_variables.keys()))
def is_union(arg_type: Type) -> bool:
return get_origin(arg_type) is Union
@ -151,7 +122,7 @@ class FunctionInputSpec:
@dataclass(frozen=True)
class Function(VariableDependency):
class Function(VariableDependency, ArgumentType):
name: str
args: List[ArgumentType]
@ -227,13 +198,9 @@ class Function(VariableDependency):
return variables
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable:
resolved_args: List[Resolvable] = []
for arg in self.args:
if arg in resolved_variables:
resolved_args.append(resolved_variables[arg])
elif isinstance(arg, Function):
resolved_args.append(arg.resolve(resolved_variables))
else:
resolved_args.append(arg)
resolved_args = [
self._resolve_argument_type(resolved_variables=resolved_variables, arg=arg)
for arg in self.args
]
return self.callable(*resolved_args)

View file

@ -1,15 +1,18 @@
from abc import ABC
from abc import abstractmethod
from dataclasses import dataclass
from typing import Any, Dict
from typing import Any
from typing import Dict
from typing import Generic
from typing import List
from typing import TypeVar
T = TypeVar("T")
NumericT = TypeVar("NumericT", bound=int | float)
class ArgumentType(ABC):
pass
class Resolvable_0(ABC):
pass
@ -41,35 +44,28 @@ class Numeric(ResolvableT[NumericT], ABC, Generic[NumericT]):
@dataclass(frozen=True)
class Integer(Numeric[int]):
class Integer(Numeric[int], ArgumentType):
pass
@dataclass(frozen=True)
class Float(Numeric[float]):
class Float(Numeric[float], ArgumentType):
pass
@dataclass(frozen=True)
class Boolean(ResolvableT[bool]):
class Boolean(ResolvableT[bool], ArgumentType):
pass
@dataclass(frozen=True)
class String(ResolvableT[str]):
class String(ResolvableT[str], ArgumentType):
pass
@dataclass(frozen=True)
class Array(Resolvable):
value: List[Resolvable]
def __str__(self) -> str:
return f"[{', '.join([val.value for val in self.value])}]"
@dataclass(frozen=True)
class Map(Resolvable):
class Map(Resolvable, ArgumentType):
value: Dict[Resolvable, Resolvable]
def __str__(self) -> str:
return f"[{', '.join([val.value for val in self.value])}]"
return f"[{', '.join([val.value for val in self.value])}]"

View file

@ -1,6 +1,8 @@
from dataclasses import dataclass
from ytdl_sub.script.types.resolvable import ArgumentType
@dataclass(frozen=True)
class Variable:
class Variable(ArgumentType):
name: str

View file

@ -0,0 +1,46 @@
from abc import ABC
from abc import abstractmethod
from dataclasses import dataclass
from typing import Dict
from typing import Set
from typing import final
from ytdl_sub.script.types.resolvable import ArgumentType
from ytdl_sub.script.types.resolvable import Resolvable
from ytdl_sub.script.types.variable import Variable
from ytdl_sub.utils.exceptions import StringFormattingException
@dataclass(frozen=True)
class VariableDependency(ABC):
@property
@abstractmethod
def variables(self) -> Set[Variable]:
raise NotImplemented()
@abstractmethod
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable:
raise NotImplemented()
def _resolve_argument_type(
self, resolved_variables: Dict[Variable, Resolvable], arg: ArgumentType
) -> Resolvable:
if isinstance(arg, Resolvable):
return arg
if isinstance(arg, Variable):
if arg not in resolved_variables:
raise StringFormattingException("should never reach@")
return resolved_variables[arg]
if isinstance(arg, VariableDependency):
return arg.resolve(resolved_variables)
assert False, "never reach here"
@final
def has_variable_dependency(self, resolved_variables: Dict[Variable, Resolvable]) -> bool:
"""
Returns
-------
True if variable dependency. False otherwise.
"""
return not self.variables.issubset(set(resolved_variables.keys()))

View file

@ -32,7 +32,10 @@ class TestParser:
parsed = parse("hello {['elem1', 'elem2']}")
parsed_empty = parse("hello {[]}")
parsed_with_var = parse("hello {['elem1', variable_name]}")
parsed_extend = parse("hi {%extend(['elem1', 'elem2'], ['elem3'], [], ['elem4'])}")
parsed_extend = parse(
"hi {%at(%flatten_array(%extend(['elem1', 'elem2'], ['elem3'], [['elem4'], ['elem5', 'elem6']], ['elem7'])), 1)}"
)
parsed_extend.resolve({})
assert False
def test_map(self):