1# pyshyacc.py - PLY grammar definition for pysh
2#
3# Copyright 2007 Patrick Mezard
4#
5# This software may be used and distributed according to the terms
6# of the GNU General Public License, incorporated herein by reference.
7
8"""PLY grammar file.
9"""
10import os.path
11import sys
12
13import bb.pysh.pyshlex as pyshlex
14tokens = pyshlex.tokens
15
16from ply import yacc
17import bb.pysh.sherrors as sherrors
18
19class IORedirect:
20    def __init__(self, op, filename, io_number=None):
21        self.op = op
22        self.filename = filename
23        self.io_number = io_number
24
25class HereDocument:
26    def __init__(self, op, name, content, io_number=None):
27        self.op = op
28        self.name = name
29        self.content = content
30        self.io_number = io_number
31
32def make_io_redirect(p):
33    """Make an IORedirect instance from the input 'io_redirect' production."""
34    name, io_number, io_target = p
35    assert name=='io_redirect'
36
37    if io_target[0]=='io_file':
38        io_type, io_op, io_file = io_target
39        return IORedirect(io_op, io_file, io_number)
40    elif io_target[0]=='io_here':
41        io_type, io_op, io_name, io_content = io_target
42        return HereDocument(io_op, io_name, io_content, io_number)
43    else:
44        assert False, "Invalid IO redirection token %s" % repr(io_type)
45
46class SimpleCommand:
47    """
48    assigns contains (name, value) pairs.
49    """
50    def __init__(self, words, redirs, assigns):
51        self.words = list(words)
52        self.redirs = list(redirs)
53        self.assigns = list(assigns)
54
55class Pipeline:
56    def __init__(self, commands, reverse_status=False):
57        self.commands = list(commands)
58        assert self.commands    #Grammar forbids this
59        self.reverse_status = reverse_status
60
61class AndOr:
62    def __init__(self, op, left, right):
63        self.op = str(op)
64        self.left = left
65        self.right = right
66
67class ForLoop:
68    def __init__(self, name, items, cmds):
69        self.name = str(name)
70        self.items = list(items)
71        self.cmds = list(cmds)
72
73class WhileLoop:
74    def __init__(self, condition, cmds):
75        self.condition = list(condition)
76        self.cmds = list(cmds)
77
78class UntilLoop:
79    def __init__(self, condition, cmds):
80        self.condition = list(condition)
81        self.cmds = list(cmds)
82
83class FunDef:
84    def __init__(self, name, body):
85        self.name = str(name)
86        self.body = body
87
88class BraceGroup:
89    def __init__(self, cmds):
90        self.cmds = list(cmds)
91
92class IfCond:
93    def __init__(self, cond, if_cmds, else_cmds):
94        self.cond = list(cond)
95        self.if_cmds = if_cmds
96        self.else_cmds = else_cmds
97
98class Case:
99    def __init__(self, name, items):
100        self.name = name
101        self.items = items
102
103class SubShell:
104    def __init__(self, cmds):
105        self.cmds = cmds
106
107class RedirectList:
108    def __init__(self, cmd, redirs):
109        self.cmd = cmd
110        self.redirs = list(redirs)
111
112def get_production(productions, ptype):
113    """productions must be a list of production tuples like (name, obj) where
114    name is the production string identifier.
115    Return the first production named 'ptype'. Raise KeyError if None can be
116    found.
117    """
118    for production in productions:
119        if production is not None and production[0]==ptype:
120            return production
121    raise KeyError(ptype)
122
123#-------------------------------------------------------------------------------
124# PLY grammar definition
125#-------------------------------------------------------------------------------
126
127def p_multiple_commands(p):
128    """multiple_commands : newline_sequence
129                         | complete_command
130                         | multiple_commands complete_command"""
131    if len(p)==2:
132        if p[1] is not None:
133            p[0] = [p[1]]
134        else:
135            p[0] = []
136    else:
137        p[0] = p[1] + [p[2]]
138
139def p_complete_command(p):
140    """complete_command : list separator
141                        | list"""
142    if len(p)==3 and p[2] and p[2][1] == '&':
143        p[0] = ('async', p[1])
144    else:
145        p[0] = p[1]
146
147def p_list(p):
148    """list : list separator_op and_or
149            |                   and_or"""
150    if len(p)==2:
151        p[0] = [p[1]]
152    else:
153        #if p[2]!=';':
154        #    raise NotImplementedError('AND-OR list asynchronous execution is not implemented')
155        p[0] = p[1] + [p[3]]
156
157def p_and_or(p):
158    """and_or : pipeline
159              | and_or AND_IF linebreak pipeline
160              | and_or OR_IF  linebreak pipeline"""
161    if len(p)==2:
162        p[0] = p[1]
163    else:
164        p[0] = ('and_or', AndOr(p[2], p[1], p[4]))
165
166def p_maybe_bang_word(p):
167    """maybe_bang_word : Bang"""
168    p[0] = ('maybe_bang_word', p[1])
169
170def p_pipeline(p):
171    """pipeline : pipe_sequence
172                | bang_word pipe_sequence"""
173    if len(p)==3:
174        p[0] = ('pipeline', Pipeline(p[2][1:], True))
175    else:
176        p[0] = ('pipeline', Pipeline(p[1][1:]))
177
178def p_pipe_sequence(p):
179    """pipe_sequence : command
180                     | pipe_sequence PIPE linebreak command"""
181    if len(p)==2:
182        p[0] = ['pipe_sequence', p[1]]
183    else:
184        p[0] = p[1] + [p[4]]
185
186def p_command(p):
187    """command : simple_command
188               | compound_command
189               | compound_command redirect_list
190               | function_definition"""
191
192    if p[1][0] in ( 'simple_command',
193                    'for_clause',
194                    'while_clause',
195                    'until_clause',
196                    'case_clause',
197                    'if_clause',
198                    'function_definition',
199                    'subshell',
200                    'brace_group',):
201        if len(p) == 2:
202            p[0] = p[1]
203        else:
204            p[0] = ('redirect_list', RedirectList(p[1], p[2][1:]))
205    else:
206        raise NotImplementedError('%s command is not implemented' % repr(p[1][0]))
207
208def p_compound_command(p):
209    """compound_command : brace_group
210                        | subshell
211                        | for_clause
212                        | case_clause
213                        | if_clause
214                        | while_clause
215                        | until_clause"""
216    p[0] = p[1]
217
218def p_subshell(p):
219    """subshell : LPARENS compound_list RPARENS"""
220    p[0] = ('subshell', SubShell(p[2][1:]))
221
222def p_compound_list(p):
223    """compound_list : term
224                     | newline_list term
225                     |              term separator
226                     | newline_list term separator"""
227    productions = p[1:]
228    try:
229        sep = get_production(productions, 'separator')
230        if sep[1]!=';':
231            raise NotImplementedError()
232    except KeyError:
233        pass
234    term = get_production(productions, 'term')
235    p[0] = ['compound_list'] + term[1:]
236
237def p_term(p):
238    """term : term separator and_or
239            |                and_or"""
240    if len(p)==2:
241        p[0] = ['term', p[1]]
242    else:
243        if p[2] is not None and p[2][1] == '&':
244            p[0] = ['term', ('async', p[1][1:])] + [p[3]]
245        else:
246            p[0] = p[1] + [p[3]]
247
248def p_maybe_for_word(p):
249    # Rearrange 'For' priority wrt TOKEN. See p_for_word
250    """maybe_for_word : For"""
251    p[0] = ('maybe_for_word', p[1])
252
253def p_for_clause(p):
254    """for_clause : for_word name linebreak                            do_group
255                  | for_word name linebreak in          sequential_sep do_group
256                  | for_word name linebreak in wordlist sequential_sep do_group"""
257    productions = p[1:]
258    do_group = get_production(productions, 'do_group')
259    try:
260        items = get_production(productions, 'in')[1:]
261    except KeyError:
262        raise NotImplementedError('"in" omission is not implemented')
263
264    try:
265        items = get_production(productions, 'wordlist')[1:]
266    except KeyError:
267        items = []
268
269    name = p[2]
270    p[0] = ('for_clause', ForLoop(name, items, do_group[1:]))
271
272def p_name(p):
273    """name : token""" #Was NAME instead of token
274    p[0] = p[1]
275
276def p_in(p):
277    """in : In"""
278    p[0] = ('in', p[1])
279
280def p_wordlist(p):
281    """wordlist : wordlist token
282                |          token"""
283    if len(p)==2:
284        p[0] = ['wordlist', ('TOKEN', p[1])]
285    else:
286        p[0] = p[1] + [('TOKEN', p[2])]
287
288def p_case_clause(p):
289    """case_clause : Case token linebreak in linebreak case_list    Esac
290                   | Case token linebreak in linebreak case_list_ns Esac
291                   | Case token linebreak in linebreak              Esac"""
292    if len(p) < 8:
293        items = []
294    else:
295        items = p[6][1:]
296    name = p[2]
297    p[0] = ('case_clause', Case(name, [c[1] for c in items]))
298
299def p_case_list_ns(p):
300    """case_list_ns : case_list case_item_ns
301                    |           case_item_ns"""
302    p_case_list(p)
303
304def p_case_list(p):
305    """case_list : case_list case_item
306                 |           case_item"""
307    if len(p)==2:
308        p[0] = ['case_list', p[1]]
309    else:
310        p[0] = p[1] + [p[2]]
311
312def p_case_item_ns(p):
313    """case_item_ns :         pattern RPARENS               linebreak
314                    |         pattern RPARENS compound_list linebreak
315                    | LPARENS pattern RPARENS               linebreak
316                    | LPARENS pattern RPARENS compound_list linebreak"""
317    p_case_item(p)
318
319def p_case_item(p):
320    """case_item :         pattern RPARENS linebreak     DSEMI linebreak
321                 |         pattern RPARENS compound_list DSEMI linebreak
322                 | LPARENS pattern RPARENS linebreak     DSEMI linebreak
323                 | LPARENS pattern RPARENS compound_list DSEMI linebreak"""
324    if len(p) < 7:
325        name = p[1][1:]
326    else:
327        name = p[2][1:]
328
329    try:
330        cmds = get_production(p[1:], "compound_list")[1:]
331    except KeyError:
332        cmds = []
333
334    p[0] = ('case_item', (name, cmds))
335
336def p_pattern(p):
337    """pattern :              token
338               | pattern PIPE token"""
339    if len(p)==2:
340        p[0] = ['pattern', ('TOKEN', p[1])]
341    else:
342        p[0] = p[1] + [('TOKEN', p[2])]
343
344def p_maybe_if_word(p):
345    # Rearrange 'If' priority wrt TOKEN. See p_if_word
346    """maybe_if_word : If"""
347    p[0] = ('maybe_if_word', p[1])
348
349def p_maybe_then_word(p):
350    # Rearrange 'Then' priority wrt TOKEN. See p_then_word
351    """maybe_then_word : Then"""
352    p[0] = ('maybe_then_word', p[1])
353
354def p_if_clause(p):
355    """if_clause : if_word compound_list then_word compound_list else_part Fi
356                 | if_word compound_list then_word compound_list           Fi"""
357    else_part = []
358    if len(p)==7:
359        else_part = p[5]
360    p[0] = ('if_clause', IfCond(p[2][1:], p[4][1:], else_part))
361
362def p_else_part(p):
363    """else_part : Elif compound_list then_word compound_list else_part
364                 | Elif compound_list then_word compound_list
365                 | Else compound_list"""
366    if len(p)==3:
367        p[0] = p[2][1:]
368    else:
369        else_part = []
370        if len(p)==6:
371            else_part = p[5]
372        p[0] = ('elif', IfCond(p[2][1:], p[4][1:], else_part))
373
374def p_while_clause(p):
375    """while_clause : While compound_list do_group"""
376    p[0] = ('while_clause', WhileLoop(p[2][1:], p[3][1:]))
377
378def p_maybe_until_word(p):
379    # Rearrange 'Until' priority wrt TOKEN. See p_until_word
380    """maybe_until_word : Until"""
381    p[0] = ('maybe_until_word', p[1])
382
383def p_until_clause(p):
384    """until_clause : until_word compound_list do_group"""
385    p[0] = ('until_clause', UntilLoop(p[2][1:], p[3][1:]))
386
387def p_function_definition(p):
388    """function_definition : fname LPARENS RPARENS linebreak function_body"""
389    p[0] = ('function_definition', FunDef(p[1], p[5]))
390
391def p_function_body(p):
392    """function_body : compound_command
393                     | compound_command redirect_list"""
394    if len(p)!=2:
395        raise NotImplementedError('functions redirections lists are not implemented')
396    p[0] = p[1]
397
398def p_fname(p):
399    """fname : TOKEN""" #Was NAME instead of token
400    p[0] = p[1]
401
402def p_brace_group(p):
403    """brace_group : Lbrace compound_list Rbrace"""
404    p[0] = ('brace_group', BraceGroup(p[2][1:]))
405
406def p_maybe_done_word(p):
407    #See p_assignment_word for details.
408    """maybe_done_word : Done"""
409    p[0] = ('maybe_done_word', p[1])
410
411def p_maybe_do_word(p):
412    """maybe_do_word : Do"""
413    p[0] = ('maybe_do_word', p[1])
414
415def p_do_group(p):
416    """do_group : do_word compound_list done_word"""
417    #Do group contains a list of AndOr
418    p[0] = ['do_group'] + p[2][1:]
419
420def p_simple_command(p):
421    """simple_command : cmd_prefix cmd_word cmd_suffix
422                      | cmd_prefix cmd_word
423                      | cmd_prefix
424                      | cmd_name cmd_suffix
425                      | cmd_name"""
426    words, redirs, assigns = [], [], []
427    for e in p[1:]:
428        name = e[0]
429        if name in ('cmd_prefix', 'cmd_suffix'):
430            for sube in e[1:]:
431                subname = sube[0]
432                if subname=='io_redirect':
433                    redirs.append(make_io_redirect(sube))
434                elif subname=='ASSIGNMENT_WORD':
435                    assigns.append(sube)
436                else:
437                    words.append(sube)
438        elif name in ('cmd_word', 'cmd_name'):
439            words.append(e)
440
441    cmd = SimpleCommand(words, redirs, assigns)
442    p[0] = ('simple_command', cmd)
443
444def p_cmd_name(p):
445    """cmd_name : TOKEN"""
446    p[0] = ('cmd_name', p[1])
447
448def p_cmd_word(p):
449    """cmd_word : token"""
450    p[0] = ('cmd_word', p[1])
451
452def p_maybe_assignment_word(p):
453    #See p_assignment_word for details.
454    """maybe_assignment_word : ASSIGNMENT_WORD"""
455    p[0] = ('maybe_assignment_word', p[1])
456
457def p_cmd_prefix(p):
458    """cmd_prefix :            io_redirect
459                  | cmd_prefix io_redirect
460                  |            assignment_word
461                  | cmd_prefix assignment_word"""
462    try:
463        prefix = get_production(p[1:], 'cmd_prefix')
464    except KeyError:
465        prefix = ['cmd_prefix']
466
467    try:
468        value = get_production(p[1:], 'assignment_word')[1]
469        value = ('ASSIGNMENT_WORD', value.split('=', 1))
470    except KeyError:
471        value = get_production(p[1:], 'io_redirect')
472    p[0] = prefix + [value]
473
474def p_cmd_suffix(p):
475    """cmd_suffix   :            io_redirect
476                    | cmd_suffix io_redirect
477                    |            token
478                    | cmd_suffix token
479                    |            maybe_for_word
480                    | cmd_suffix maybe_for_word
481                    |            maybe_done_word
482                    | cmd_suffix maybe_done_word
483                    |            maybe_do_word
484                    | cmd_suffix maybe_do_word
485                    |            maybe_until_word
486                    | cmd_suffix maybe_until_word
487                    |            maybe_assignment_word
488                    | cmd_suffix maybe_assignment_word
489                    |            maybe_if_word
490                    | cmd_suffix maybe_if_word
491                    |            maybe_then_word
492                    | cmd_suffix maybe_then_word
493                    |            maybe_bang_word
494                    | cmd_suffix maybe_bang_word"""
495    try:
496        suffix = get_production(p[1:], 'cmd_suffix')
497        token = p[2]
498    except KeyError:
499        suffix = ['cmd_suffix']
500        token = p[1]
501
502    if isinstance(token, tuple):
503        if token[0]=='io_redirect':
504            p[0] = suffix + [token]
505        else:
506            #Convert maybe_*  to TOKEN if necessary
507            p[0] = suffix + [('TOKEN', token[1])]
508    else:
509        p[0] = suffix + [('TOKEN', token)]
510
511def p_redirect_list(p):
512    """redirect_list : io_redirect
513                     | redirect_list io_redirect"""
514    if len(p) == 2:
515        p[0] = ['redirect_list', make_io_redirect(p[1])]
516    else:
517        p[0] = p[1] + [make_io_redirect(p[2])]
518
519def p_io_redirect(p):
520    """io_redirect :           io_file
521                   | IO_NUMBER io_file
522                   |           io_here
523                   | IO_NUMBER io_here"""
524    if len(p)==3:
525        p[0] = ('io_redirect', p[1], p[2])
526    else:
527        p[0] = ('io_redirect', None, p[1])
528
529def p_io_file(p):
530    #Return the tuple (operator, filename)
531    """io_file : LESS      filename
532               | LESSAND   filename
533               | GREATER   filename
534               | GREATAND  filename
535               | DGREAT    filename
536               | LESSGREAT filename
537               | CLOBBER   filename"""
538    #Extract the filename from the file
539    p[0] = ('io_file', p[1], p[2][1])
540
541def p_filename(p):
542    #Return the filename
543    """filename : TOKEN"""
544    p[0] = ('filename', p[1])
545
546def p_io_here(p):
547    """io_here : DLESS here_end
548               | DLESSDASH here_end"""
549    p[0] = ('io_here', p[1], p[2][1], p[2][2])
550
551def p_here_end(p):
552    """here_end : HERENAME TOKEN"""
553    p[0] = ('here_document', p[1], p[2])
554
555def p_newline_sequence(p):
556    # Nothing in the grammar can handle leading NEWLINE productions, so add
557    # this one with the lowest possible priority relatively to newline_list.
558    """newline_sequence : newline_list"""
559    p[0] = None
560
561def p_newline_list(p):
562    """newline_list : NEWLINE
563                    | newline_list NEWLINE"""
564    p[0] = None
565
566def p_linebreak(p):
567    """linebreak : newline_list
568                 | empty"""
569    p[0] = None
570
571def p_separator_op(p):
572    """separator_op : COMMA
573                    | COMMA COMMA
574                    | AMP"""
575    p[0] = p[1]
576
577def p_separator(p):
578    """separator : separator_op linebreak
579                 | newline_list"""
580    if len(p)==2:
581        #Ignore newlines
582        p[0] = None
583    else:
584        #Keep the separator operator
585        p[0] = ('separator', p[1])
586
587def p_sequential_sep(p):
588    """sequential_sep : COMMA linebreak
589                      | newline_list"""
590    p[0] = None
591
592# Low priority TOKEN => for_word conversion.
593# Let maybe_for_word be used as a token when necessary in higher priority
594# rules.
595def p_for_word(p):
596    """for_word : maybe_for_word"""
597    p[0] = p[1]
598
599def p_if_word(p):
600    """if_word : maybe_if_word"""
601    p[0] = p[1]
602
603def p_then_word(p):
604    """then_word : maybe_then_word"""
605    p[0] = p[1]
606
607def p_done_word(p):
608    """done_word : maybe_done_word"""
609    p[0] = p[1]
610
611def p_do_word(p):
612    """do_word : maybe_do_word"""
613    p[0] = p[1]
614
615def p_until_word(p):
616    """until_word : maybe_until_word"""
617    p[0] = p[1]
618
619def p_assignment_word(p):
620    """assignment_word : maybe_assignment_word"""
621    p[0] = ('assignment_word', p[1][1])
622
623def p_bang_word(p):
624    """bang_word : maybe_bang_word"""
625    p[0] = ('bang_word', p[1][1])
626
627def p_token(p):
628    """token : TOKEN
629             | Fi"""
630    p[0] = p[1]
631
632def p_empty(p):
633    'empty :'
634    p[0] = None
635
636# Error rule for syntax errors
637def p_error(p):
638    msg = []
639    w = msg.append
640    if p:
641        w('%r\n' % p)
642        w('followed by:\n')
643        for i in range(5):
644            n = yacc.token()
645            if not n:
646                break
647            w('  %r\n' % n)
648    else:
649        w('Unexpected EOF')
650    raise sherrors.ShellSyntaxError(''.join(msg))
651
652# Build the parser
653try:
654    import pyshtables
655except ImportError:
656    outputdir = os.path.dirname(__file__)
657    if not os.access(outputdir, os.W_OK):
658        outputdir = ''
659    yacc.yacc(tabmodule = 'pyshtables', outputdir = outputdir, debug = 0)
660else:
661    yacc.yacc(tabmodule = 'pysh.pyshtables', write_tables = 0, debug = 0)
662
663
664def parse(input, eof=False, debug=False):
665    """Parse a whole script at once and return the generated AST and unconsumed
666    data in a tuple.
667
668    NOTE: eof is probably meaningless for now, the parser being unable to work
669    in pull mode. It should be set to True.
670    """
671    lexer = pyshlex.PLYLexer()
672    remaining = lexer.add(input, eof)
673    if lexer.is_empty():
674        return [], remaining
675    if debug:
676        debug = 2
677    return yacc.parse(lexer=lexer, debug=debug), remaining
678
679#-------------------------------------------------------------------------------
680# AST rendering helpers
681#-------------------------------------------------------------------------------
682
683def format_commands(v):
684    """Return a tree made of strings and lists. Make command trees easier to
685    display.
686    """
687    if isinstance(v, list):
688        return [format_commands(c) for c in v]
689    if isinstance(v, tuple):
690        if len(v)==2 and isinstance(v[0], str) and not isinstance(v[1], str):
691            if v[0] == 'async':
692                return ['AsyncList', map(format_commands, v[1])]
693            else:
694                #Avoid decomposing tuples like ('pipeline', Pipeline(...))
695                return format_commands(v[1])
696        return format_commands(list(v))
697    elif isinstance(v, IfCond):
698        name = ['IfCond']
699        name += ['if', map(format_commands, v.cond)]
700        name += ['then', map(format_commands, v.if_cmds)]
701        name += ['else', map(format_commands, v.else_cmds)]
702        return name
703    elif isinstance(v, ForLoop):
704        name = ['ForLoop']
705        name += [repr(v.name)+' in ', map(str, v.items)]
706        name += ['commands', map(format_commands, v.cmds)]
707        return name
708    elif isinstance(v, AndOr):
709        return [v.op, format_commands(v.left), format_commands(v.right)]
710    elif isinstance(v, Pipeline):
711        name = 'Pipeline'
712        if v.reverse_status:
713            name = '!' + name
714        return [name, format_commands(v.commands)]
715    elif isinstance(v, Case):
716        name = ['Case']
717        name += [v.name, format_commands(v.items)]
718    elif isinstance(v, SimpleCommand):
719        name = ['SimpleCommand']
720        if v.words:
721            name += ['words', map(str, v.words)]
722        if v.assigns:
723            assigns = [tuple(a[1]) for a in v.assigns]
724            name += ['assigns', map(str, assigns)]
725        if v.redirs:
726            name += ['redirs', map(format_commands, v.redirs)]
727        return name
728    elif isinstance(v, RedirectList):
729        name = ['RedirectList']
730        if v.redirs:
731            name += ['redirs', map(format_commands, v.redirs)]
732        name += ['command', format_commands(v.cmd)]
733        return name
734    elif isinstance(v, IORedirect):
735        return ' '.join(map(str, (v.io_number, v.op, v.filename)))
736    elif isinstance(v, HereDocument):
737        return ' '.join(map(str, (v.io_number, v.op, repr(v.name), repr(v.content))))
738    elif isinstance(v, SubShell):
739        return ['SubShell', map(format_commands, v.cmds)]
740    else:
741        return repr(v)
742
743def print_commands(cmds, output=sys.stdout):
744    """Pretty print a command tree."""
745    def print_tree(cmd, spaces, output):
746        if isinstance(cmd, list):
747            for c in cmd:
748                print_tree(c, spaces + 3, output)
749        else:
750            print >>output, ' '*spaces + str(cmd)
751
752    formatted = format_commands(cmds)
753    print_tree(formatted, 0, output)
754
755
756def stringify_commands(cmds):
757    """Serialize a command tree as a string.
758
759    Returned string is not pretty and is currently used for unit tests only.
760    """
761    def stringify(value):
762        output = []
763        if isinstance(value, list):
764            formatted = []
765            for v in value:
766                formatted.append(stringify(v))
767            formatted = ' '.join(formatted)
768            output.append(''.join(['<', formatted, '>']))
769        else:
770            output.append(value)
771        return ' '.join(output)
772
773    return stringify(format_commands(cmds))
774
775
776def visit_commands(cmds, callable):
777    """Visit the command tree and execute callable on every Pipeline and
778    SimpleCommand instances.
779    """
780    if isinstance(cmds, (tuple, list)):
781        map(lambda c: visit_commands(c,callable), cmds)
782    elif isinstance(cmds, (Pipeline, SimpleCommand)):
783        callable(cmds)
784