Merge branch 'funcparser-fix-2737' of https://github.com/ChrisLR/evennia into ChrisLR-funcparser-fix-2737

This commit is contained in:
Griatch 2022-06-12 00:01:13 +02:00
commit f5d0018c8e
2 changed files with 72 additions and 35 deletions

View file

@ -84,8 +84,8 @@ class _ParsedFunc:
# state storage
fullstr: str = ""
infuncstr: str = ""
single_quoted: bool = False
double_quoted: bool = False
single_quoted: int = -1
double_quoted: int = -1
current_kwarg: str = ""
open_lparens: int = 0
open_lsquate: int = 0
@ -318,8 +318,8 @@ class FuncParser:
# parsing state
callstack = []
single_quoted = False
double_quoted = False
single_quoted = -1
double_quoted = -1
open_lparens = 0 # open (
open_lsquare = 0 # open [
open_lcurly = 0 # open {
@ -330,6 +330,7 @@ class FuncParser:
curr_func = None
fullstr = "" # final string
infuncstr = "" # string parts inside the current level of $funcdef (including $)
literal_infuncstr = False
for char in string:
@ -373,12 +374,13 @@ class FuncParser:
curr_func.open_lcurly = open_lcurly
current_kwarg = ""
infuncstr = ""
single_quoted = False
double_quoted = False
single_quoted = -1
double_quoted = -1
open_lparens = 0
open_lsquare = 0
open_lcurly = 0
exec_return = ""
literal_infuncstr = False
callstack.append(curr_func)
# start a new func
@ -401,19 +403,41 @@ class FuncParser:
infuncstr += str(exec_return)
exec_return = ""
if char == "'": # note that this is the same as "\'"
if char == "'" and double_quoted < 0: # note that this is the same as "\'"
# a single quote - flip status
single_quoted = not single_quoted
infuncstr += char
if single_quoted == 0:
infuncstr = infuncstr[1:]
single_quoted = -1
elif single_quoted > 0:
prefix = infuncstr[0:single_quoted]
infuncstr = prefix + infuncstr[single_quoted+1:]
single_quoted = -1
else:
infuncstr += char
infuncstr = infuncstr.strip()
single_quoted = len(infuncstr) - 1
literal_infuncstr = True
continue
if char == '"': # note that this is the same as '\"'
if char == '"' and single_quoted < 0: # note that this is the same as '\"'
# a double quote = flip status
double_quoted = not double_quoted
infuncstr += char
if double_quoted == 0:
infuncstr = infuncstr[1:]
double_quoted = -1
elif double_quoted > 0:
prefix = infuncstr[0:double_quoted]
infuncstr = prefix + infuncstr[double_quoted + 1:]
double_quoted = -1
else:
infuncstr += char
infuncstr = infuncstr.strip()
double_quoted = len(infuncstr) - 1
literal_infuncstr = True
continue
if double_quoted or single_quoted:
if double_quoted >= 0 or single_quoted >= 0:
# inside a string definition - this escapes everything else
infuncstr += char
continue
@ -477,12 +501,15 @@ class FuncParser:
else:
curr_func.args.append(exec_return)
else:
if not literal_infuncstr:
infuncstr = infuncstr.strip()
# store a string instead
if current_kwarg:
curr_func.kwargs[current_kwarg] = infuncstr.strip()
elif infuncstr.strip():
curr_func.kwargs[current_kwarg] = infuncstr
elif literal_infuncstr or infuncstr.strip():
# don't store the empty string
curr_func.args.append(infuncstr.strip())
curr_func.args.append(infuncstr)
# note that at this point either exec_return or infuncstr will
# be empty. We need to store the full string so we can print
@ -493,6 +520,7 @@ class FuncParser:
current_kwarg = ""
exec_return = ""
infuncstr = ""
literal_infuncstr = False
if char == ")":
# closing the function list - this means we have a
@ -536,6 +564,7 @@ class FuncParser:
if return_str:
exec_return = ""
infuncstr = ""
literal_infuncstr = False
continue
infuncstr += char

View file

@ -44,6 +44,7 @@ def _double_callable(*args, **kwargs):
def _eval_callable(*args, **kwargs):
if args:
return simple_eval(args[0])
return ""
@ -113,25 +114,25 @@ class TestFuncParser(TestCase):
("$foo() Test noargs5", "_test() Test noargs5"),
("Test args1 $foo(a,b,c)", "Test args1 _test(a, b, c)"),
("Test args2 $bar(foo, bar, too)", "Test args2 _test(foo, bar, too)"),
("Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, ' too')"),
("Test args4 $foo('')", "Test args4 _test('')"),
('Test args4 $foo("")', 'Test args4 _test("")'),
(r"Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, too)"),
("Test args4 $foo('')", "Test args4 _test()"),
('Test args4 $foo("")', 'Test args4 _test()'),
("Test args5 $foo(\(\))", "Test args5 _test(())"),
("Test args6 $foo(\()", "Test args6 _test(()"),
("Test args7 $foo(())", "Test args7 _test(())"),
("Test args8 $foo())", "Test args8 _test())"),
("Test args9 $foo(=)", "Test args9 _test(=)"),
("Test args10 $foo(\,)", "Test args10 _test(,)"),
("Test args10 $foo(',')", "Test args10 _test(',')"),
("Test args10 $foo(',')", "Test args10 _test(,)"),
("Test args11 $foo(()", "Test args11 $foo(()"), # invalid syntax
(
"Test kwarg1 $bar(foo=1, bar='foo', too=ere)",
"Test kwarg1 _test(foo=1, bar='foo', too=ere)",
"Test kwarg1 _test(foo=1, bar=foo, too=ere)",
),
("Test kwarg2 $bar(foo,bar,too=ere)", "Test kwarg2 _test(foo, bar, too=ere)"),
("test kwarg3 $foo(foo = bar, bar = ere )", "test kwarg3 _test(foo=bar, bar=ere)"),
(
"test kwarg4 $foo(foo =' bar ',\" bar \"= ere )",
r"test kwarg4 $foo(foo =\' bar \',\" bar \"= ere )",
"test kwarg4 _test(foo=' bar ', \" bar \"=ere)",
),
(
@ -180,22 +181,24 @@ class TestFuncParser(TestCase):
("Test clr $clr(r, This is a red string!)", "Test clr |rThis is a red string!|n"),
("Test eval1 $eval(21 + 21 - 10)", "Test eval1 32"),
("Test eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"),
("Test eval3 $eval('21' + 'foo' + 'bar')", "Test eval3 21foobar"),
("Test eval4 $eval('21' + '$repl()' + '' + str(10 // 2))", "Test eval4 21rr5"),
("Test eval5 $eval('21' + '\$repl()' + '' + str(10 // 2))", "Test eval5 21$repl()5"),
("Test eval6 $eval('$repl(a)' + '$repl(b)')", "Test eval6 rarrbr"),
("Test eval3 $eval(\"'21' + 'foo' + 'bar'\")", "Test eval3 21foobar"),
(r"Test eval4 $eval(\'21\' + \'$repl()\' + \"''\" + str(10 // 2))", "Test eval4 21rr5"),
(r"Test eval5 $eval(\'21\' + \'\$repl()\' + \'\' + str(10 // 2))", "Test eval5 21$repl()5"),
("Test eval6 $eval(\"'$repl(a)' + '$repl(b)'\")", "Test eval6 rarrbr"),
("Test type1 $typ([1,2,3,4])", "Test type1 <class 'list'>"),
("Test type2 $typ((1,2,3,4))", "Test type2 <class 'tuple'>"),
("Test type3 $typ({1,2,3,4})", "Test type3 <class 'set'>"),
("Test type4 $typ({1:2,3:4})", "Test type4 <class 'dict'>"),
("Test type5 $typ(1), $typ(1.0)", "Test type5 <class 'int'>, <class 'float'>"),
("Test type6 $typ('1'), $typ(\"1.0\")", "Test type6 <class 'str'>, <class 'str'>"),
("Test type6 $typ(\"'1'\"), $typ('\"1.0\"')", "Test type6 <class 'str'>, <class 'str'>"),
("Test add1 $add(1, 2)", "Test add1 3"),
("Test add2 $add([1,2,3,4], [5,6])", "Test add2 [1, 2, 3, 4, 5, 6]"),
("Test literal1 $sum($lit([1,2,3,4,5,6]))", "Test literal1 21"),
("Test literal2 $typ($lit(1))", "Test literal2 <class 'int'>"),
("Test literal3 $typ($lit(1)aaa)", "Test literal3 <class 'str'>"),
("Test literal4 $typ(aaa$lit(1))", "Test literal4 <class 'str'>"),
("Test spider's thread", "Test spider's thread"),
]
)
def test_parse(self, string, expected):
@ -258,7 +261,11 @@ class TestFuncParser(TestCase):
self.assertEqual([1, 2, 3, 4], ret)
self.assertTrue(isinstance(ret, list))
ret = self.parser.parse_to_any("$lit('')")
ret = self.parser.parse_to_any("$lit(\"''\")")
self.assertEqual("", ret)
self.assertTrue(isinstance(ret, str))
ret = self.parser.parse_to_any(r"$lit(\'\')")
self.assertEqual("", ret)
self.assertTrue(isinstance(ret, str))
@ -390,7 +397,8 @@ class TestDefaultCallables(TestCase):
("Some $rjust(Hello, 30)", "Some Hello"),
("Some $rjust(Hello, width=30)", "Some Hello"),
("Some $cjust(Hello, 30)", "Some Hello "),
("Some $eval('-'*20)Hello", "Some --------------------Hello"),
("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"),
("$crop(\"spider's silk\", 5)", "spide"),
]
)
def test_other_callables(self, string, expected):
@ -455,18 +463,18 @@ class TestDefaultCallables(TestCase):
self.parser.parse(
"this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5) text., 80)"
),
"this should be '''escaped,''' and '''instead,''' cropped with text. ",
"this should be escaped, and instead, cropped with text. ",
)
def test_escaped2(self):
raw_str = 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)'
expected = 'this should be escaped, and instead, cropped with text. '
result = self.parser.parse(raw_str)
self.assertEqual(
self.parser.parse(
'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)'
),
'this should be """escaped,""" and """instead,""" cropped with text. ',
result,
expected,
)
class TestCallableSearch(test_resources.BaseEvenniaTest):
"""
Test the $search(query) callable