Make funcparser able to handle non-string returns

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

View file

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

View file

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