Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 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 | """ |
| 10 | import os.path |
| 11 | import sys |
| 12 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 13 | import bb.pysh.pyshlex as pyshlex |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 14 | tokens = pyshlex.tokens |
| 15 | |
| 16 | from ply import yacc |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 17 | import bb.pysh.sherrors as sherrors |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 18 | |
| 19 | class 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 | |
| 25 | class 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 | |
| 32 | def 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 | |
| 46 | class 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 | |
| 55 | class 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 | |
| 61 | class AndOr: |
| 62 | def __init__(self, op, left, right): |
| 63 | self.op = str(op) |
| 64 | self.left = left |
| 65 | self.right = right |
| 66 | |
| 67 | class ForLoop: |
| 68 | def __init__(self, name, items, cmds): |
| 69 | self.name = str(name) |
| 70 | self.items = list(items) |
| 71 | self.cmds = list(cmds) |
| 72 | |
| 73 | class WhileLoop: |
| 74 | def __init__(self, condition, cmds): |
| 75 | self.condition = list(condition) |
| 76 | self.cmds = list(cmds) |
| 77 | |
| 78 | class UntilLoop: |
| 79 | def __init__(self, condition, cmds): |
| 80 | self.condition = list(condition) |
| 81 | self.cmds = list(cmds) |
| 82 | |
| 83 | class FunDef: |
| 84 | def __init__(self, name, body): |
| 85 | self.name = str(name) |
| 86 | self.body = body |
| 87 | |
| 88 | class BraceGroup: |
| 89 | def __init__(self, cmds): |
| 90 | self.cmds = list(cmds) |
| 91 | |
| 92 | class 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 | |
| 98 | class Case: |
| 99 | def __init__(self, name, items): |
| 100 | self.name = name |
| 101 | self.items = items |
| 102 | |
| 103 | class SubShell: |
| 104 | def __init__(self, cmds): |
| 105 | self.cmds = cmds |
| 106 | |
| 107 | class RedirectList: |
| 108 | def __init__(self, cmd, redirs): |
| 109 | self.cmd = cmd |
| 110 | self.redirs = list(redirs) |
| 111 | |
| 112 | def 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 | |
| 127 | def 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 | |
| 139 | def 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 | |
| 147 | def 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 | |
| 157 | def 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 | |
| 166 | def p_maybe_bang_word(p): |
| 167 | """maybe_bang_word : Bang""" |
| 168 | p[0] = ('maybe_bang_word', p[1]) |
| 169 | |
| 170 | def 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 | |
| 178 | def 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 | |
| 186 | def 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 | |
| 208 | def 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 | |
| 218 | def p_subshell(p): |
| 219 | """subshell : LPARENS compound_list RPARENS""" |
| 220 | p[0] = ('subshell', SubShell(p[2][1:])) |
| 221 | |
| 222 | def 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 | |
| 237 | def 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 | |
| 248 | def 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 | |
| 253 | def 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 | |
| 272 | def p_name(p): |
| 273 | """name : token""" #Was NAME instead of token |
| 274 | p[0] = p[1] |
| 275 | |
| 276 | def p_in(p): |
| 277 | """in : In""" |
| 278 | p[0] = ('in', p[1]) |
| 279 | |
| 280 | def 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 | |
| 288 | def 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 | |
| 299 | def p_case_list_ns(p): |
| 300 | """case_list_ns : case_list case_item_ns |
| 301 | | case_item_ns""" |
| 302 | p_case_list(p) |
| 303 | |
| 304 | def 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 | |
| 312 | def 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 | |
| 319 | def 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 | |
| 336 | def 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 | |
| 344 | def 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 | |
| 349 | def 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 | |
| 354 | def 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 | |
| 362 | def 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 | |
| 374 | def 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 | |
| 378 | def 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 | |
| 383 | def 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 | |
| 387 | def p_function_definition(p): |
| 388 | """function_definition : fname LPARENS RPARENS linebreak function_body""" |
| 389 | p[0] = ('function_definition', FunDef(p[1], p[5])) |
| 390 | |
| 391 | def 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 | |
| 398 | def p_fname(p): |
| 399 | """fname : TOKEN""" #Was NAME instead of token |
| 400 | p[0] = p[1] |
| 401 | |
| 402 | def p_brace_group(p): |
| 403 | """brace_group : Lbrace compound_list Rbrace""" |
| 404 | p[0] = ('brace_group', BraceGroup(p[2][1:])) |
| 405 | |
| 406 | def 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 | |
| 411 | def p_maybe_do_word(p): |
| 412 | """maybe_do_word : Do""" |
| 413 | p[0] = ('maybe_do_word', p[1]) |
| 414 | |
| 415 | def 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 | |
| 420 | def 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 | |
| 444 | def p_cmd_name(p): |
| 445 | """cmd_name : TOKEN""" |
| 446 | p[0] = ('cmd_name', p[1]) |
| 447 | |
| 448 | def p_cmd_word(p): |
| 449 | """cmd_word : token""" |
| 450 | p[0] = ('cmd_word', p[1]) |
| 451 | |
| 452 | def 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 | |
| 457 | def 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 | |
| 474 | def 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 | |
| 511 | def 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 | |
| 519 | def 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 | |
| 529 | def 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 | |
| 541 | def p_filename(p): |
| 542 | #Return the filename |
| 543 | """filename : TOKEN""" |
| 544 | p[0] = ('filename', p[1]) |
| 545 | |
| 546 | def 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 | |
| 551 | def p_here_end(p): |
| 552 | """here_end : HERENAME TOKEN""" |
| 553 | p[0] = ('here_document', p[1], p[2]) |
| 554 | |
| 555 | def 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 | |
| 561 | def p_newline_list(p): |
| 562 | """newline_list : NEWLINE |
| 563 | | newline_list NEWLINE""" |
| 564 | p[0] = None |
| 565 | |
| 566 | def p_linebreak(p): |
| 567 | """linebreak : newline_list |
| 568 | | empty""" |
| 569 | p[0] = None |
| 570 | |
| 571 | def p_separator_op(p): |
| 572 | """separator_op : COMMA |
| 573 | | AMP""" |
| 574 | p[0] = p[1] |
| 575 | |
| 576 | def p_separator(p): |
| 577 | """separator : separator_op linebreak |
| 578 | | newline_list""" |
| 579 | if len(p)==2: |
| 580 | #Ignore newlines |
| 581 | p[0] = None |
| 582 | else: |
| 583 | #Keep the separator operator |
| 584 | p[0] = ('separator', p[1]) |
| 585 | |
| 586 | def p_sequential_sep(p): |
| 587 | """sequential_sep : COMMA linebreak |
| 588 | | newline_list""" |
| 589 | p[0] = None |
| 590 | |
| 591 | # Low priority TOKEN => for_word conversion. |
| 592 | # Let maybe_for_word be used as a token when necessary in higher priority |
| 593 | # rules. |
| 594 | def p_for_word(p): |
| 595 | """for_word : maybe_for_word""" |
| 596 | p[0] = p[1] |
| 597 | |
| 598 | def p_if_word(p): |
| 599 | """if_word : maybe_if_word""" |
| 600 | p[0] = p[1] |
| 601 | |
| 602 | def p_then_word(p): |
| 603 | """then_word : maybe_then_word""" |
| 604 | p[0] = p[1] |
| 605 | |
| 606 | def p_done_word(p): |
| 607 | """done_word : maybe_done_word""" |
| 608 | p[0] = p[1] |
| 609 | |
| 610 | def p_do_word(p): |
| 611 | """do_word : maybe_do_word""" |
| 612 | p[0] = p[1] |
| 613 | |
| 614 | def p_until_word(p): |
| 615 | """until_word : maybe_until_word""" |
| 616 | p[0] = p[1] |
| 617 | |
| 618 | def p_assignment_word(p): |
| 619 | """assignment_word : maybe_assignment_word""" |
| 620 | p[0] = ('assignment_word', p[1][1]) |
| 621 | |
| 622 | def p_bang_word(p): |
| 623 | """bang_word : maybe_bang_word""" |
| 624 | p[0] = ('bang_word', p[1][1]) |
| 625 | |
| 626 | def p_token(p): |
| 627 | """token : TOKEN |
| 628 | | Fi""" |
| 629 | p[0] = p[1] |
| 630 | |
| 631 | def p_empty(p): |
| 632 | 'empty :' |
| 633 | p[0] = None |
| 634 | |
| 635 | # Error rule for syntax errors |
| 636 | def p_error(p): |
| 637 | msg = [] |
| 638 | w = msg.append |
Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame^] | 639 | if p: |
| 640 | w('%r\n' % p) |
| 641 | w('followed by:\n') |
| 642 | for i in range(5): |
| 643 | n = yacc.token() |
| 644 | if not n: |
| 645 | break |
| 646 | w(' %r\n' % n) |
| 647 | else: |
| 648 | w('Unexpected EOF') |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 649 | raise sherrors.ShellSyntaxError(''.join(msg)) |
| 650 | |
| 651 | # Build the parser |
| 652 | try: |
| 653 | import pyshtables |
| 654 | except ImportError: |
| 655 | outputdir = os.path.dirname(__file__) |
| 656 | if not os.access(outputdir, os.W_OK): |
| 657 | outputdir = '' |
| 658 | yacc.yacc(tabmodule = 'pyshtables', outputdir = outputdir, debug = 0) |
| 659 | else: |
| 660 | yacc.yacc(tabmodule = 'pysh.pyshtables', write_tables = 0, debug = 0) |
| 661 | |
| 662 | |
| 663 | def parse(input, eof=False, debug=False): |
| 664 | """Parse a whole script at once and return the generated AST and unconsumed |
| 665 | data in a tuple. |
| 666 | |
| 667 | NOTE: eof is probably meaningless for now, the parser being unable to work |
| 668 | in pull mode. It should be set to True. |
| 669 | """ |
| 670 | lexer = pyshlex.PLYLexer() |
| 671 | remaining = lexer.add(input, eof) |
| 672 | if lexer.is_empty(): |
| 673 | return [], remaining |
| 674 | if debug: |
| 675 | debug = 2 |
| 676 | return yacc.parse(lexer=lexer, debug=debug), remaining |
| 677 | |
| 678 | #------------------------------------------------------------------------------- |
| 679 | # AST rendering helpers |
| 680 | #------------------------------------------------------------------------------- |
| 681 | |
| 682 | def format_commands(v): |
| 683 | """Return a tree made of strings and lists. Make command trees easier to |
| 684 | display. |
| 685 | """ |
| 686 | if isinstance(v, list): |
| 687 | return [format_commands(c) for c in v] |
| 688 | if isinstance(v, tuple): |
| 689 | if len(v)==2 and isinstance(v[0], str) and not isinstance(v[1], str): |
| 690 | if v[0] == 'async': |
| 691 | return ['AsyncList', map(format_commands, v[1])] |
| 692 | else: |
| 693 | #Avoid decomposing tuples like ('pipeline', Pipeline(...)) |
| 694 | return format_commands(v[1]) |
| 695 | return format_commands(list(v)) |
| 696 | elif isinstance(v, IfCond): |
| 697 | name = ['IfCond'] |
| 698 | name += ['if', map(format_commands, v.cond)] |
| 699 | name += ['then', map(format_commands, v.if_cmds)] |
| 700 | name += ['else', map(format_commands, v.else_cmds)] |
| 701 | return name |
| 702 | elif isinstance(v, ForLoop): |
| 703 | name = ['ForLoop'] |
| 704 | name += [repr(v.name)+' in ', map(str, v.items)] |
| 705 | name += ['commands', map(format_commands, v.cmds)] |
| 706 | return name |
| 707 | elif isinstance(v, AndOr): |
| 708 | return [v.op, format_commands(v.left), format_commands(v.right)] |
| 709 | elif isinstance(v, Pipeline): |
| 710 | name = 'Pipeline' |
| 711 | if v.reverse_status: |
| 712 | name = '!' + name |
| 713 | return [name, format_commands(v.commands)] |
| 714 | elif isinstance(v, Case): |
| 715 | name = ['Case'] |
| 716 | name += [v.name, format_commands(v.items)] |
| 717 | elif isinstance(v, SimpleCommand): |
| 718 | name = ['SimpleCommand'] |
| 719 | if v.words: |
| 720 | name += ['words', map(str, v.words)] |
| 721 | if v.assigns: |
| 722 | assigns = [tuple(a[1]) for a in v.assigns] |
| 723 | name += ['assigns', map(str, assigns)] |
| 724 | if v.redirs: |
| 725 | name += ['redirs', map(format_commands, v.redirs)] |
| 726 | return name |
| 727 | elif isinstance(v, RedirectList): |
| 728 | name = ['RedirectList'] |
| 729 | if v.redirs: |
| 730 | name += ['redirs', map(format_commands, v.redirs)] |
| 731 | name += ['command', format_commands(v.cmd)] |
| 732 | return name |
| 733 | elif isinstance(v, IORedirect): |
| 734 | return ' '.join(map(str, (v.io_number, v.op, v.filename))) |
| 735 | elif isinstance(v, HereDocument): |
| 736 | return ' '.join(map(str, (v.io_number, v.op, repr(v.name), repr(v.content)))) |
| 737 | elif isinstance(v, SubShell): |
| 738 | return ['SubShell', map(format_commands, v.cmds)] |
| 739 | else: |
| 740 | return repr(v) |
| 741 | |
| 742 | def print_commands(cmds, output=sys.stdout): |
| 743 | """Pretty print a command tree.""" |
| 744 | def print_tree(cmd, spaces, output): |
| 745 | if isinstance(cmd, list): |
| 746 | for c in cmd: |
| 747 | print_tree(c, spaces + 3, output) |
| 748 | else: |
| 749 | print >>output, ' '*spaces + str(cmd) |
| 750 | |
| 751 | formatted = format_commands(cmds) |
| 752 | print_tree(formatted, 0, output) |
| 753 | |
| 754 | |
| 755 | def stringify_commands(cmds): |
| 756 | """Serialize a command tree as a string. |
| 757 | |
| 758 | Returned string is not pretty and is currently used for unit tests only. |
| 759 | """ |
| 760 | def stringify(value): |
| 761 | output = [] |
| 762 | if isinstance(value, list): |
| 763 | formatted = [] |
| 764 | for v in value: |
| 765 | formatted.append(stringify(v)) |
| 766 | formatted = ' '.join(formatted) |
| 767 | output.append(''.join(['<', formatted, '>'])) |
| 768 | else: |
| 769 | output.append(value) |
| 770 | return ' '.join(output) |
| 771 | |
| 772 | return stringify(format_commands(cmds)) |
| 773 | |
| 774 | |
| 775 | def visit_commands(cmds, callable): |
| 776 | """Visit the command tree and execute callable on every Pipeline and |
| 777 | SimpleCommand instances. |
| 778 | """ |
| 779 | if isinstance(cmds, (tuple, list)): |
| 780 | map(lambda c: visit_commands(c,callable), cmds) |
| 781 | elif isinstance(cmds, (Pipeline, SimpleCommand)): |
| 782 | callable(cmds) |