Skip to content

Commit

Permalink
Implement $FUNCNAME (bash-only, used by Nix.)
Browse files Browse the repository at this point in the history
Also make note of a var-ref issue where $? is not dynamic.

Addresses issue #26.
  • Loading branch information
Andy Chu committed Aug 11, 2017
1 parent 439a44a commit 0b86961
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 19 deletions.
4 changes: 2 additions & 2 deletions core/cmd_exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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
1 change: 1 addition & 0 deletions core/completion_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
23 changes: 21 additions & 2 deletions core/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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:
Expand Down
14 changes: 7 additions & 7 deletions core/state_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand Down
11 changes: 4 additions & 7 deletions spec/introspect.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
11 changes: 11 additions & 0 deletions spec/var-ref.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -66,3 +76,4 @@ caller
# BUG mksh stdout-json: ""
# BUG mksh status: 1


2 changes: 1 addition & 1 deletion test/spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 "$@"
}

Expand Down

0 comments on commit 0b86961

Please sign in to comment.