pyshyacc.py 24 KB

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