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 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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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="]")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
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 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)
|
||||||
|
|
|
||||||
|
|
@ -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])}]"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
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 = 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):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue