From 0b86961e74a41e2c2cc7d33dcc84438b968033ce Mon Sep 17 00:00:00 2001 From: Andy Chu Date: Fri, 11 Aug 2017 08:57:47 -0700 Subject: [PATCH] Implement $FUNCNAME (bash-only, used by Nix.) Also make note of a var-ref issue where $? is not dynamic. Addresses issue #26. --- core/cmd_exec.py | 4 ++-- core/completion_test.py | 1 + core/state.py | 23 +++++++++++++++++++++-- core/state_test.py | 14 +++++++------- spec/introspect.test.sh | 11 ++++------- spec/var-ref.test.sh | 11 +++++++++++ test/spec.sh | 2 +- 7 files changed, 47 insertions(+), 19 deletions(-) diff --git a/core/cmd_exec.py b/core/cmd_exec.py index 329ecab61e..04c5f17f77 100755 --- a/core/cmd_exec.py +++ b/core/cmd_exec.py @@ -983,7 +983,7 @@ def RunFunc(self, func_node, argv): if not self.fd_state.Push(def_redirects, self.waiter): return 1 # error - self.mem.Push(argv[1:]) + self.mem.PushCall(func_node.name, argv[1:]) # Redirects still valid for functions. # Here doc causes a pipe and Process(SubProgramThunk). @@ -996,7 +996,7 @@ def RunFunc(self, func_node, argv): # break/continue used in the wrong place e_die('Unexpected %r (in function call)', e.token.val, token=e.token) finally: - self.mem.Pop() + self.mem.PopCall() self.fd_state.Pop() return status diff --git a/core/completion_test.py b/core/completion_test.py index 02993cd462..20eec22a16 100755 --- a/core/completion_test.py +++ b/core/completion_test.py @@ -83,6 +83,7 @@ def testShellFuncExecution(self): pairs = [ast.assign_pair(ast.LhsName('COMPREPLY'), assign_op.Equal, w)] body_node = ast.Assignment(Id.Assign_None, [], pairs) + func_node.name = 'myfunc' func_node.body = body_node a = completion.ShellFuncAction(ex, func_node) diff --git a/core/state.py b/core/state.py index 05674ed20c..889ff003d5 100644 --- a/core/state.py +++ b/core/state.py @@ -166,6 +166,10 @@ def __init__(self, argv0, argv, environ): self.var_stack = [top] self.argv0 = argv0 self.argv_stack = [_ArgFrame(argv)] + # NOTE: could use deque and appendleft/popleft, but + # 1. ASDL type checking of StrArray doesn't allow it (could be fixed) + # 2. We don't otherwise depend on the collections module + self.func_name_stack = [] self.last_status = 0 # Mutable public variable self.last_job_id = -1 # Uninitialized value mutable public variable @@ -205,12 +209,17 @@ def _InitEnviron(self, environ): # Stack # - def Push(self, argv): + def PushCall(self, func_name, argv): """For function calls.""" + # bash uses this order: top of stack first. + self.func_name_stack.append(func_name) + self.var_stack.append({}) self.argv_stack.append(_ArgFrame(argv)) - def Pop(self): + def PopCall(self): + self.func_name_stack.pop() + self.var_stack.pop() self.argv_stack.pop() @@ -425,6 +434,16 @@ def SetVar(self, lval, value, new_flags, lookup_mode): # NOTE: Have a default for convenience def GetVar(self, name, lookup_mode=scope.Dynamic): assert isinstance(name, str), name + + # Do lookup of system globals before looking at user variables. Note: we + # could optimize this at compile-time like $?. That would break + # ${!varref}, but it's already broken for $?. + if name == 'FUNCNAME': + # bash wants it in reverse order. This is a little inefficient but we're + # not depending on deque(). + strs = list(reversed(self.func_name_stack)) + return runtime.StrArray(strs) + cell, _ = self._FindCellAndNamespace(name, lookup_mode) if cell: diff --git a/core/state_test.py b/core/state_test.py index 420a74c93d..bc68e2aaa0 100755 --- a/core/state_test.py +++ b/core/state_test.py @@ -18,16 +18,16 @@ class MemTest(unittest.TestCase): def testGet(self): mem = state.Mem('', [], {}) - mem.Push(['a', 'b']) + mem.PushCall('my-func', ['a', 'b']) print(mem.GetVar('HOME')) - mem.Pop() + mem.PopCall() print(mem.GetVar('NONEXISTENT')) def testSetVarClearFlag(self): mem = state.Mem('', [], {}) print(mem) - mem.Push(['ONE']) + mem.PushCall('my-func', ['ONE']) self.assertEqual(2, len(mem.var_stack)) # internal details # local x=y @@ -36,7 +36,7 @@ def testSetVarClearFlag(self): self.assertEqual('y', mem.var_stack[-1]['x'].val.s) # New frame - mem.Push(['TWO']) + mem.PushCall('my-func', ['TWO']) self.assertEqual(3, len(mem.var_stack)) # internal details # x=y -- test out dynamic scope @@ -220,10 +220,10 @@ def testUnset(self): def testArgv(self): mem = state.Mem('', [], {}) - mem.Push(['a', 'b']) + mem.PushCall('my-func', ['a', 'b']) self.assertEqual(['a', 'b'], mem.GetArgv()) - mem.Push(['x', 'y']) + mem.PushCall('my-func', ['x', 'y']) self.assertEqual(['x', 'y'], mem.GetArgv()) status = mem.Shift(1) @@ -238,7 +238,7 @@ def testArgv(self): self.assertEqual([], mem.GetArgv()) self.assertEqual(1, status) # error - mem.Pop() + mem.PopCall() self.assertEqual(['a', 'b'], mem.GetArgv()) def testArgv2(self): diff --git a/spec/introspect.test.sh b/spec/introspect.test.sh index 93faff0596..ee40a5c1c3 100644 --- a/spec/introspect.test.sh +++ b/spec/introspect.test.sh @@ -5,15 +5,12 @@ ### ${FUNCNAME[@]} array f() { - echo "begin: ${FUNCNAME[@]}" + argv.py "${FUNCNAME[@]}" g - echo "end: ${FUNCNAME[@]}" + argv.py "${FUNCNAME[@]}" } g() { - echo "func: ${FUNCNAME[@]}" + argv.py "${FUNCNAME[@]}" } f -# stdout-json: "begin: f\nfunc: g f\nend: f\n" -# N-I mksh stdout-json: "begin: \nfunc: \nend: \n" -# N-I dash stdout-json: "" -# N-I dash status: 2 +# stdout-json: "['f']\n['g', 'f']\n['f']\n" diff --git a/spec/var-ref.test.sh b/spec/var-ref.test.sh index 079ba3dcb2..facd6dc525 100755 --- a/spec/var-ref.test.sh +++ b/spec/var-ref.test.sh @@ -13,6 +13,16 @@ echo ref ${!a} ${a} # BUG mksh stdout: ref a b # N-I dash/zsh stdout-json: "" +### var ref with special vars +myfunc() { + local ref=$1 + echo ${!ref} +} +myfunc FUNCNAME +myfunc '?' # osh doesn't do this dynamically +# stdout-json: "myfunc\n0\n" +# N-I mksh stdout-json: "ref\nref\n" + ### declare -n and ${!a} declare -n a a=b @@ -66,3 +76,4 @@ caller # BUG mksh stdout-json: "" # BUG mksh status: 1 + diff --git a/test/spec.sh b/test/spec.sh index 20b5c3ba28..84d16d0d39 100755 --- a/test/spec.sh +++ b/test/spec.sh @@ -457,7 +457,7 @@ extended-glob() { # ${!var} syntax -- oil should replace this with associative arrays. var-ref() { - sh-spec spec/var-ref.test.sh --osh-failures-allowed 4 \ + sh-spec spec/var-ref.test.sh --osh-failures-allowed 5 \ $BASH $MKSH $OSH "$@" }