diff --git a/src/ytdl_sub/script/parser.py b/src/ytdl_sub/script/parser.py index ac6260ff..3fa53a75 100644 --- a/src/ytdl_sub/script/parser.py +++ b/src/ytdl_sub/script/parser.py @@ -25,13 +25,12 @@ from ytdl_sub.script.utils.exceptions import UNREACHABLE from ytdl_sub.script.utils.exceptions import CycleDetected from ytdl_sub.script.utils.exceptions import FunctionDoesNotExist from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments +from ytdl_sub.script.utils.exceptions import InvalidCustomFunctionArgumentName from ytdl_sub.script.utils.exceptions import InvalidSyntaxException from ytdl_sub.script.utils.exceptions import InvalidVariableName from ytdl_sub.script.utils.exceptions import UserException from ytdl_sub.script.utils.exceptions import VariableDoesNotExist from ytdl_sub.script.utils.name_validation import validate_variable_name -from ytdl_sub.utils.exceptions import StringFormattingException -from ytdl_sub.validators.string_formatter_validators import is_valid_source_variable_name # pylint: disable=invalid-name # pylint: disable=too-many-branches @@ -197,6 +196,7 @@ class _Parser: Begin parsing function args after the first ``$``, i.e. ``$0`` """ var_name = "" + variable_start_pos = self._pos while ch := self._read(increment_pos=False): if ch.isspace() and not var_name: self._pos += 1 @@ -204,15 +204,14 @@ class _Parser: if _is_breakable(ch): break - is_numeric = ch.isnumeric() - if not is_numeric: - raise StringFormattingException("invalid function var name") - var_name += ch self._pos += 1 - if not var_name: - raise StringFormattingException("invalid var name") + if not var_name.isnumeric(): + self._set_highlight_position(variable_start_pos) + raise InvalidCustomFunctionArgumentName( + "Custom function arguments must be numeric and increment starting from zero." + ) return FunctionArgument.from_idx(idx=int(var_name), custom_function_name=self._name) diff --git a/src/ytdl_sub/script/utils/exceptions.py b/src/ytdl_sub/script/utils/exceptions.py index f24a382d..fa67e1f0 100644 --- a/src/ytdl_sub/script/utils/exceptions.py +++ b/src/ytdl_sub/script/utils/exceptions.py @@ -19,6 +19,10 @@ class InvalidFunctionName(UserException): """Custom function name is invalid""" +class InvalidCustomFunctionArgumentName(UserException): + """Custom function argument name (i.e. $0) is invalid""" + + class IncompatibleFunctionArguments(UserException): """Function has invalid arguments""" diff --git a/tests/unit/script/types/test_custom_function.py b/tests/unit/script/types/test_custom_function.py index 2c9d2692..01708f98 100644 --- a/tests/unit/script/types/test_custom_function.py +++ b/tests/unit/script/types/test_custom_function.py @@ -7,6 +7,7 @@ from ytdl_sub.script.types.array import ResolvedArray from ytdl_sub.script.types.resolvable import Integer from ytdl_sub.script.utils.exceptions import CycleDetected from ytdl_sub.script.utils.exceptions import FunctionDoesNotExist +from ytdl_sub.script.utils.exceptions import InvalidCustomFunctionArgumentName class TestCustomFunction: @@ -80,3 +81,26 @@ class TestCustomFunction: "output": "{%func(1)}", } ).resolve() + + @pytest.mark.parametrize( + "name", + [ + "$00invalid", + "$abc", + "$3.14", + ], + ) + def test_custom_function_invalid_function_arguments(self, name: str): + with pytest.raises( + InvalidCustomFunctionArgumentName, + match=re.escape( + "Custom function arguments must be numeric and increment starting from zero." + ), + ): + Script( + { + "%func1": f"{{%mul(1, {name})}}", + "%func0": "{%mul(%func1(1), $0)}", + "output": "{%func0(1)}", + } + ).resolve() diff --git a/tests/unit/script/types/test_float.py b/tests/unit/script/types/test_float.py index 104a38a8..1328c5b1 100644 --- a/tests/unit/script/types/test_float.py +++ b/tests/unit/script/types/test_float.py @@ -25,7 +25,7 @@ class TestFloat: ) def test_float_not_arg(self, integer: str): with pytest.raises(InvalidSyntaxException, match=re.escape(str(NUMERICS_ONLY_ARGS))): - Script({"float": integer}).resolve() + Script({"out": integer}).resolve() @pytest.mark.parametrize( "float_, expected_float", @@ -57,7 +57,7 @@ class TestFloat: ) def test_invalid_float(self, float_: str): with pytest.raises(InvalidSyntaxException, match=re.escape(str(NUMERICS_INVALID_CHAR))): - Script({"float": float_}).resolve() + Script({"out": float_}).resolve() @pytest.mark.parametrize( "to_cast, expected_float", diff --git a/tests/unit/script/types/test_string.py b/tests/unit/script/types/test_string.py index 1a40229b..6fc990ba 100644 --- a/tests/unit/script/types/test_string.py +++ b/tests/unit/script/types/test_string.py @@ -23,7 +23,7 @@ class TestString: ) def test_string_not_arg(self, string: str): with pytest.raises(InvalidSyntaxException, match=re.escape(str(STRINGS_ONLY_ARGS))): - Script({"string": string}).resolve() + Script({"out": string}).resolve() @pytest.mark.parametrize( "string, expected_string", @@ -59,4 +59,4 @@ class TestString: ) def test_string_not_closed_properly(self, string: str): with pytest.raises(InvalidSyntaxException, match=re.escape(str(STRINGS_NOT_CLOSED))): - Script({"string": string}).resolve() + Script({"out": string}).resolve()