diff --git a/src/ytdl_sub/script/parser.py b/src/ytdl_sub/script/parser.py index 0573380a..7c098755 100644 --- a/src/ytdl_sub/script/parser.py +++ b/src/ytdl_sub/script/parser.py @@ -26,6 +26,7 @@ from ytdl_sub.validators.string_formatter_validators import is_valid_source_vari # pylint: disable=invalid-name # pylint: disable=too-many-branches # pylint: disable=too-many-return-statements +# pylint: disable=consider-using-ternary class ParsedArgType(Enum): @@ -56,6 +57,8 @@ BOOLEAN_ONLY_ARGS = InvalidSyntaxException( "Booleans can only be used as arguments to functions, maps, or arrays" ) +FUNCTION_INVALID_CHAR = InvalidSyntaxException("Invalid value when parsing a function") + def _UNEXPECTED_CHAR_ARGUMENT(arg_type: ParsedArgType): return InvalidSyntaxException(f"Unexpected character when parsing {arg_type.value} arguments") @@ -79,6 +82,10 @@ def _is_variable_start(char: str) -> bool: return char.isalpha() and char.islower() +def _is_function_name_char(char: str) -> bool: + return (char.isalpha() and char.islower()) or char.isnumeric() or char == "_" + + def _is_numeric_start(char: str) -> bool: return char.isnumeric() or char == "-" @@ -329,12 +336,15 @@ class _Parser: self._set_highlight_position(function_start_pos) raise - if ch != "(": + if _is_function_name_char(ch): function_name += ch - else: + elif ch == "(": function_args = self._parse_args(argument_parser=ParsedArgType.FUNCTION) + else: + break - raise StringFormattingException("Invalid function") + self._set_highlight_position(pos=self._pos - 1) + raise FUNCTION_INVALID_CHAR def _parse_array(self) -> UnresolvedArray: """ diff --git a/tests/unit/script/types/test_function.py b/tests/unit/script/types/test_function.py index 1e5f7b33..0f3e6997 100644 --- a/tests/unit/script/types/test_function.py +++ b/tests/unit/script/types/test_function.py @@ -2,12 +2,14 @@ import re import pytest +from ytdl_sub.script.parser import FUNCTION_INVALID_CHAR from ytdl_sub.script.script import Script from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.utils.exceptions import FunctionDoesNotExist from ytdl_sub.script.utils.exceptions import FunctionRuntimeException from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments +from ytdl_sub.script.utils.exceptions import InvalidSyntaxException from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError @@ -105,3 +107,10 @@ class TestFunction: match=re.escape("Function %lolnope does not exist as a built-in or custom function."), ): Script({"dne": "{%lolnope(False, 'test this error message')}"}).resolve() + + def test_function_does_not_close(self): + with pytest.raises( + InvalidSyntaxException, + match=re.escape(str(FUNCTION_INVALID_CHAR)), + ): + Script({"dne": "{%throw}"}).resolve()