refactored, array working
This commit is contained in:
parent
7483a1315b
commit
20ad7b4f7e
10 changed files with 142 additions and 78 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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="]")
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
36
src/ytdl_sub/script/types/array.py
Normal file
36
src/ytdl_sub/script/types/array.py
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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])}]"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
46
src/ytdl_sub/script/types/variable_dependency.py
Normal file
46
src/ytdl_sub/script/types/variable_dependency.py
Normal 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()))
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Reference in a new issue