From 70876dc12346454f17ba7a8122f3c38f9e8d7e2b Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Sun, 10 Dec 2023 22:19:55 -0800 Subject: [PATCH] no more escaping strings, triple quote --- src/ytdl_sub/script/parser.py | 47 +++++++++++-------- .../script/functions/test_string_functions.py | 1 + tests/unit/script/types/test_string.py | 7 +-- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/ytdl_sub/script/parser.py b/src/ytdl_sub/script/parser.py index 1028bf2b..4bf0cdda 100644 --- a/src/ytdl_sub/script/parser.py +++ b/src/ytdl_sub/script/parser.py @@ -104,10 +104,18 @@ def _is_numeric_start(char: str) -> bool: return char.isnumeric() or char == "-" -def _is_string_start(char: str) -> bool: +def _is_string_start_single_char(char: Optional[str]) -> bool: return char in ["'", '"'] +def _is_string_start_multi_char(string: Optional[str]) -> bool: + return string in ["'''", '"""'] + + +def _is_null(string: Optional[str]) -> bool: + return string and string.lower() == "null" + + def _is_breakable(char: str) -> bool: return char in ["}", ",", ")", "]"] or char.isspace() @@ -120,10 +128,6 @@ def _is_boolean_false(string: Optional[str]) -> bool: return string and string.lower() == "false" -def _is_null(string: Optional[str]) -> bool: - return string and string.lower() == "null" - - def _is_custom_function_argument_start(char: str) -> bool: return char == "$" @@ -273,28 +277,25 @@ class _Parser: return Float(value=numeric_float) - def _parse_string(self) -> String: + def _parse_string(self, str_open_token: str) -> String: """ Begin parsing a string, including the quotation value """ self._set_highlight_position() string_value = "" - open_quotation_char = self._read() - if not _is_string_start(open_quotation_char): + if not _is_string_start_single_char(str_open_token) and not _is_string_start_multi_char( + str_open_token + ): raise UNREACHABLE - is_escaped = False - while ch := self._read(): - if ch == open_quotation_char and not is_escaped: + while ch := self._read(increment_pos=False): + if self._read(increment_pos=False, length=len(str_open_token)) == str_open_token: + self._pos += len(str_open_token) return String(value=string_value) - if ch == "\\" and not is_escaped: - is_escaped = True - continue - + self._pos += 1 string_value += ch - is_escaped = False raise STRINGS_NOT_CLOSED @@ -313,8 +314,12 @@ class _Parser: if _is_null(self._read(increment_pos=False, length=4)): self._pos += 4 return String(value="") - if _is_string_start(self._read(increment_pos=False)): - return self._parse_string() + if _is_string_start_multi_char(str_open_token := self._read(increment_pos=False, length=3)): + self._pos += 3 + return self._parse_string(str_open_token) + if _is_string_start_single_char(str_open_token := self._read(increment_pos=False)): + self._pos += 1 + return self._parse_string(str_open_token) if self._read(increment_pos=False) == "[": self._pos += 1 return self._parse_array() @@ -535,7 +540,11 @@ class _Parser: self._ast.append(self._parse_variable()) elif _is_numeric_start(ch1): raise NUMERICS_ONLY_ARGS - elif _is_string_start(ch1) or _is_null(self._read(increment_pos=False, length=4)): + elif ( + _is_string_start_single_char(ch1) + or _is_string_start_multi_char(self._read(increment_pos=False, length=3)) + or _is_null(self._read(increment_pos=False, length=4)) + ): raise STRINGS_ONLY_ARGS elif _is_boolean_true( self._read(increment_pos=False, length=4) diff --git a/tests/unit/script/functions/test_string_functions.py b/tests/unit/script/functions/test_string_functions.py index ded9b66e..4533d5bf 100644 --- a/tests/unit/script/functions/test_string_functions.py +++ b/tests/unit/script/functions/test_string_functions.py @@ -1,6 +1,7 @@ import pytest from unit.script.conftest import single_variable_output + class TestNumericFunctions: @pytest.mark.parametrize( "values, expected_output", diff --git a/tests/unit/script/types/test_string.py b/tests/unit/script/types/test_string.py index 1e5b5d1b..697b59fe 100644 --- a/tests/unit/script/types/test_string.py +++ b/tests/unit/script/types/test_string.py @@ -31,6 +31,7 @@ class TestString: [ ("{%string('')}", ""), ('{%string("")}', ""), + ('{%string("\\")}', "\\"), ("{%string('323')}", "323"), ('{%string( "4253" )}', "4253"), ('{%string("hi")}', "hi"), @@ -41,9 +42,9 @@ class TestString: ("{%string('in function')} out of function", "in function out of function"), ("{%string('supports \" in string')}", 'supports " in string'), ('{%string("supports \' in string")}', "supports ' in string"), - ('{%string("\\" in string with open \\"")}', '" in string with open "'), - ("{%string('\\' in string with open \\'')}", "' in string with open '"), - ("{%string('backslash \\\\')}", "backslash \\"), + ("{%string('backslash \\\\')}", "backslash \\\\"), + ("{%string('''triple quote with \" ' \\''')}", "triple quote with \" ' \\"), + ('{%string("""triple quote with " \' \\""")}', "triple quote with \" ' \\"), ], ) def test_string(self, string: str, expected_string: str):