Make funcparser able to handle non-string returns

This commit is contained in:
Griatch 2021-03-18 23:27:00 +01:00
parent 18fea15097
commit 4ae3f57ddf
2 changed files with 73 additions and 25 deletions

View file

@ -72,6 +72,7 @@ class ParsedFunc:
open_lparens: int = 0
open_lsquate: int = 0
open_lcurly: int = 0
exec_return = ""
def get(self):
return self.funcname, self.args, self.kwargs
@ -214,7 +215,7 @@ class FuncParser:
kwargs = {**self.default_kwargs, **kwargs, **reserved_kwargs}
try:
return str(func(*args, **kwargs))
return func(*args, **kwargs)
except Exception:
logger.log_trace()
if raise_errors:
@ -267,6 +268,7 @@ class FuncParser:
open_lcurly = 0 # open {
escaped = False
current_kwarg = ""
exec_return = ""
curr_func = None
fullstr = '' # final string
@ -316,6 +318,7 @@ class FuncParser:
open_lparens = 0
open_lsquare = 0
open_lcurly = 0
exec_return = ""
callstack.append(curr_func)
# start a new func
@ -329,6 +332,13 @@ class FuncParser:
# in a function def (can be nested)
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
# it must immediately merge as a string
infuncstr += str(exec_return)
exec_return = ''
if char == "'": # note that this is the same as "\'"
# a single quote - flip status
single_quoted = not single_quoted
@ -342,7 +352,7 @@ class FuncParser:
continue
if double_quoted or single_quoted:
# inside a string escape - this escapes everything else
# inside a string definition - this escapes everything else
infuncstr += char
continue
@ -374,6 +384,8 @@ class FuncParser:
if char == '=':
# beginning of a keyword argument
if exec_return != '':
infuncstr = exec_return
current_kwarg = infuncstr.strip()
curr_func.kwargs[current_kwarg] = ""
curr_func.fullstr += infuncstr + char
@ -396,16 +408,28 @@ class FuncParser:
infuncstr += char
continue
# end current arg/kwarg one way or another
if current_kwarg:
curr_func.kwargs[current_kwarg] = infuncstr.strip()
current_kwarg = ""
elif infuncstr.strip():
curr_func.args.append(infuncstr.strip())
if exec_return != '':
# store the execution return as-received
if current_kwarg:
curr_func.kwargs[current_kwarg] = exec_return
else:
curr_func.args.append(exec_return)
else:
# store a string instead
if current_kwarg:
curr_func.kwargs[current_kwarg] = infuncstr.strip()
elif infuncstr.strip():
# don't store the empty string
curr_func.args.append(infuncstr.strip())
# we need to store the full string so we can print it 'raw' in
# case this funcdef turns out to e.g. lack an ending paranthesis
curr_func.fullstr += infuncstr + char
# note that at this point either exec_return or infuncstr will
# be empty. We need to store the full string so we can print
# it 'raw' in case this funcdef turns out to e.g. lack an
# ending paranthesis
curr_func.fullstr += str(exec_return) + infuncstr + char
current_kwarg = ""
exec_return = ''
infuncstr = ''
if char == ')':
@ -415,13 +439,14 @@ class FuncParser:
if strip:
# remove function as if it returned empty
infuncstr = ''
exec_return = ''
elif escape:
# get function and set it as escaped
infuncstr = escape_char + curr_func.fullstr
exec_return = escape_char + curr_func.fullstr
else:
# execute the function
infuncstr = self.execute(
# execute the function - the result may be a string or
# something else
exec_return = self.execute(
curr_func, raise_errors=raise_errors, **reserved_kwargs)
if callstack:
@ -429,7 +454,11 @@ class FuncParser:
# and continue where we were
curr_func = callstack.pop()
current_kwarg = curr_func.current_kwarg
infuncstr = curr_func.infuncstr + infuncstr
if curr_func.infuncstr:
# if we have an ongoing string, we must merge the
# exec into this as a part of that string
infuncstr = curr_func.infuncstr + str(exec_return)
exec_return = ''
curr_func.infuncstr = ''
single_quoted = curr_func.single_quoted
double_quoted = curr_func.double_quoted
@ -437,13 +466,14 @@ class FuncParser:
open_lsquare = curr_func.open_lsquare
open_lcurly = curr_func.open_lcurly
else:
# back to the top-level string
# back to the top-level string - this means the
# exec_return should always be converted to a string.
curr_func = None
fullstr += infuncstr
fullstr += str(exec_return)
exec_return = ''
infuncstr = ''
continue
# no special char
infuncstr += char
if curr_func:

View file

@ -44,15 +44,27 @@ def _clr_callable(*args, **kwargs):
return f"|{clr}{string}|n"
def _typ_callable(*args, **kwargs):
if args:
return type(literal_eval(args[0]))
return ''
try:
if isinstance(args[0], str):
return type(literal_eval(args[0]))
else:
return type(args[0])
except (SyntaxError, ValueError):
return type("")
def _add_callable(*args, **kwargs):
if len(args) > 1:
return literal_eval(args[0]) + literal_eval(args[1])
return ''
def _lit_callable(*args, **kwargs):
return literal_eval(args[0])
def _lsum_callable(*args, **kwargs):
if isinstance(args[0], (list, tuple)):
return sum(val for val in args[0])
return ''
_test_callables = {
"foo": _test_callable,
"bar": _test_callable,
@ -63,6 +75,8 @@ _test_callables = {
"clr": _clr_callable,
"typ": _typ_callable,
"add": _add_callable,
"lit": _lit_callable,
"sum": _lsum_callable,
}
class TestFuncParser(TestCase):
@ -147,17 +161,21 @@ class TestFuncParser(TestCase):
("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'>"),
])
def test_parse(self, string, expected):
"""
Test parsing of string.
"""
t0 = time.time()
# t0 = time.time()
# from evennia import set_trace;set_trace()
ret = self.parser.parse(string, raise_errors=True)
t1 = time.time()
print(f"time: {(t1-t0)*1000} ms")
# t1 = time.time()
# print(f"time: {(t1-t0)*1000} ms")
self.assertEqual(expected, ret)
def test_parse_raise(self):