diff --git a/evennia/utils/funcparser.py b/evennia/utils/funcparser.py index 50071fc33a..4ea426ca8a 100644 --- a/evennia/utils/funcparser.py +++ b/evennia/utils/funcparser.py @@ -85,6 +85,7 @@ class _ParsedFunc: # state storage fullstr: str = "" infuncstr: str = "" + rawstr: str = "" double_quoted: int = -1 current_kwarg: str = "" open_lparens: int = 0 @@ -96,7 +97,7 @@ class _ParsedFunc: return self.funcname, self.args, self.kwargs def __str__(self): - return self.fullstr + self.infuncstr + return self.prefix + self.rawstr + self.infuncstr class ParsingError(RuntimeError): @@ -369,6 +370,8 @@ class FuncParser: curr_func.open_lparens = open_lparens curr_func.open_lsquare = open_lsquare curr_func.open_lcurly = open_lcurly + # we must strip the remaining funcstr so it's not counted twice + curr_func.rawstr = curr_func.rawstr[: -len(infuncstr)] current_kwarg = "" infuncstr = "" double_quoted = -1 @@ -392,6 +395,8 @@ class FuncParser: # in a function def (can be nested) + curr_func.rawstr += char + if exec_return != "" and char not in (",=)"): # if exec_return is followed by any other character # than one demarking an arg,kwarg or function-end @@ -552,8 +557,15 @@ class FuncParser: # these are malformed (no closing bracket) and we should get their # strings as-is. callstack.append(curr_func) - for _ in range(len(callstack)): - infuncstr = str(callstack.pop()) + infuncstr + for inum, _ in enumerate(range(len(callstack))): + funcstr = str(callstack.pop()) + if inum == 0 and funcstr.endswith(infuncstr): + # avoid double-echo of nested function calls. This should + # produce a good result most of the time, but it's not 100% + # guaranteed to, since it can ignore genuine duplicates + infuncstr = funcstr + else: + infuncstr = funcstr + infuncstr if not return_str and exec_return != "": # return explicit return diff --git a/evennia/utils/tests/test_funcparser.py b/evennia/utils/tests/test_funcparser.py index c1acf3ed73..386d5d21ec 100644 --- a/evennia/utils/tests/test_funcparser.py +++ b/evennia/utils/tests/test_funcparser.py @@ -82,6 +82,12 @@ def _raises_callable(*args, **kwargs): raise RuntimeError("Test exception raised by test callable") +def _pass_callable(*args, **kwargs): + kwargs.pop("funcparser", None) + kwargs.pop("raise_errors", None) + return str(args) + str(kwargs) + + _test_callables = { "foo": _test_callable, "bar": _test_callable, @@ -95,6 +101,7 @@ _test_callables = { "lit": _lit_callable, "sum": _lsum_callable, "raise": _raises_callable, + "pass": _pass_callable, } @@ -184,8 +191,8 @@ class TestFuncParser(TestCase): ("Test with color |r$foo(a,b)|n is ok", "Test with color |r_test(a, b)|n is ok"), ("Test malformed1 This is $foo( and $bar(", "Test malformed1 This is $foo( and $bar("), ( - "Test malformed2 This is $foo( and $bar()", - "Test malformed2 This is $foo( and _test()", + "Test malformed2 This is $foo( and $bar()", + "Test malformed2 This is $foo( and _test()", ), ("Test malformed3 $", "Test malformed3 $"), ( @@ -304,11 +311,14 @@ class TestFuncParser(TestCase): ret = self.parser.parse(string, strip=True) self.assertEqual("Test and things", ret) - @unittest.skip("broken due to https://github.com/evennia/evennia/issues/2927") def test_parse_whitespace_preserved(self): - string = "The answer is $add(1, x)" + string = "The answer is $foobar(1, x)" # not found, so should be preserved ret = self.parser.parse(string) - self.assertEqual("The answer is $add(1, x)", ret) + self.assertEqual("The answer is $foobar(1, x)", ret) + + string = 'The $pass(testing, bar= $dum(b = "test2" , a), ) $pass(' + ret = self.parser.parse(string) + self.assertEqual("The ('testing',){'bar': '$dum(b = \"test2\" , a)'} $pass(", ret) def test_parse_escape(self): """