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 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 from ytdl_sub.script.types.resolvable import Resolvable
@ -12,3 +13,18 @@ class ArrayFunctions:
output.extend(array.value) output.extend(array.value)
return Array(output) 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.script.types.resolvable import Resolvable
from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.exceptions import StringFormattingException

View file

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

View file

@ -1,20 +1,20 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict from typing import Dict
from typing import List from typing import List
from typing import Optional
from typing import Set from typing import Set
from ytdl_sub.script.types.function import Function 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 Resolvable
from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.types.resolvable import String
from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.types.variable import Variable
from ytdl_sub.script.types.variable_dependency import VariableDependency
from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.exceptions import StringFormattingException
@dataclass(frozen=True) @dataclass(frozen=True)
class SyntaxTree(VariableDependency): class SyntaxTree(VariableDependency):
ast: List[String | Variable | Function] ast: List[ArgumentType]
@property @property
def variables(self) -> Set[Variable]: def variables(self) -> Set[Variable]:
@ -35,14 +35,9 @@ class SyntaxTree(VariableDependency):
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable: def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable:
resolved: List[Resolvable] = [] resolved: List[Resolvable] = []
for token in self.ast: for token in self.ast:
if isinstance(token, Resolvable): resolved.append(
resolved.append(token) self._resolve_argument_type(resolved_variables=resolved_variables, arg=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"
# If only one resolvable resides in the AST, return as that # If only one resolvable resides in the AST, return as that
if len(resolved) == 1: 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 functools
import inspect import inspect
from abc import ABC
from abc import abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from inspect import FullArgSpec from inspect import FullArgSpec
from typing import Callable from typing import Callable
@ -11,45 +9,18 @@ from typing import Optional
from typing import Set from typing import Set
from typing import Type from typing import Type
from typing import Union from typing import Union
from typing import final
from typing import get_origin from typing import get_origin
from ytdl_sub.script.functions import Functions from ytdl_sub.script.functions import Functions
from ytdl_sub.script.types.resolvable import Array from ytdl_sub.script.types.resolvable import ArgumentType
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 Resolvable 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_0
from ytdl_sub.script.types.resolvable import Resolvable_1 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 Resolvable_2
from ytdl_sub.script.types.resolvable import String
from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.types.variable import Variable
from ytdl_sub.script.types.variable_dependency import VariableDependency
from ytdl_sub.utils.exceptions import StringFormattingException 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: def is_union(arg_type: Type) -> bool:
return get_origin(arg_type) is Union return get_origin(arg_type) is Union
@ -151,7 +122,7 @@ class FunctionInputSpec:
@dataclass(frozen=True) @dataclass(frozen=True)
class Function(VariableDependency): class Function(VariableDependency, ArgumentType):
name: str name: str
args: List[ArgumentType] args: List[ArgumentType]
@ -227,13 +198,9 @@ class Function(VariableDependency):
return variables return variables
def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable: def resolve(self, resolved_variables: Dict[Variable, Resolvable]) -> Resolvable:
resolved_args: List[Resolvable] = [] resolved_args = [
for arg in self.args: self._resolve_argument_type(resolved_variables=resolved_variables, arg=arg)
if arg in resolved_variables: for arg in self.args
resolved_args.append(resolved_variables[arg]) ]
elif isinstance(arg, Function):
resolved_args.append(arg.resolve(resolved_variables))
else:
resolved_args.append(arg)
return self.callable(*resolved_args) return self.callable(*resolved_args)

View file

@ -1,15 +1,18 @@
from abc import ABC from abc import ABC
from abc import abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Dict from typing import Any
from typing import Dict
from typing import Generic from typing import Generic
from typing import List
from typing import TypeVar from typing import TypeVar
T = TypeVar("T") T = TypeVar("T")
NumericT = TypeVar("NumericT", bound=int | float) NumericT = TypeVar("NumericT", bound=int | float)
class ArgumentType(ABC):
pass
class Resolvable_0(ABC): class Resolvable_0(ABC):
pass pass
@ -41,35 +44,28 @@ class Numeric(ResolvableT[NumericT], ABC, Generic[NumericT]):
@dataclass(frozen=True) @dataclass(frozen=True)
class Integer(Numeric[int]): class Integer(Numeric[int], ArgumentType):
pass pass
@dataclass(frozen=True) @dataclass(frozen=True)
class Float(Numeric[float]): class Float(Numeric[float], ArgumentType):
pass pass
@dataclass(frozen=True) @dataclass(frozen=True)
class Boolean(ResolvableT[bool]): class Boolean(ResolvableT[bool], ArgumentType):
pass pass
@dataclass(frozen=True) @dataclass(frozen=True)
class String(ResolvableT[str]): class String(ResolvableT[str], ArgumentType):
pass pass
@dataclass(frozen=True) @dataclass(frozen=True)
class Array(Resolvable): class Map(Resolvable, ArgumentType):
value: List[Resolvable]
def __str__(self) -> str:
return f"[{', '.join([val.value for val in self.value])}]"
@dataclass(frozen=True)
class Map(Resolvable):
value: Dict[Resolvable, Resolvable] value: Dict[Resolvable, Resolvable]
def __str__(self) -> str: 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 dataclasses import dataclass
from ytdl_sub.script.types.resolvable import ArgumentType
@dataclass(frozen=True) @dataclass(frozen=True)
class Variable: class Variable(ArgumentType):
name: str 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 = parse("hello {['elem1', 'elem2']}")
parsed_empty = parse("hello {[]}") parsed_empty = parse("hello {[]}")
parsed_with_var = parse("hello {['elem1', variable_name]}") 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 assert False
def test_map(self): def test_map(self):