| 1 | from __future__ import print_function
 | 
| 2 | """
 | 
| 3 | ysh_ify.py: Roughly translate OSH to YSH.  Doesn't respect semantics.
 | 
| 4 | 
 | 
| 5 | ESSENTIAL
 | 
| 6 | 
 | 
| 7 | Command:
 | 
| 8 |   
 | 
| 9 |   then/fi, do/done -> { }
 | 
| 10 | 
 | 
| 11 |   new case statement
 | 
| 12 | 
 | 
| 13 |   f() { } -> proc f { }  (changes scope)
 | 
| 14 | 
 | 
| 15 |   subshell -> forkwait, because () is taken
 | 
| 16 |     { } to fopen { }?
 | 
| 17 | 
 | 
| 18 |   Approximate: var declaration:
 | 
| 19 |     local a=b -> var a = 'b', I think
 | 
| 20 | 
 | 
| 21 |   <<EOF here docs to '''
 | 
| 22 | 
 | 
| 23 | Word:
 | 
| 24 |   "$@" -> @ARGV
 | 
| 25 | 
 | 
| 26 |   Not common: unquoted $x -> @[split(x)]
 | 
| 27 | 
 | 
| 28 | LEGACY that I don't personally use
 | 
| 29 | 
 | 
| 30 | Builtins:
 | 
| 31 |   [ -> test
 | 
| 32 |   . -> source
 | 
| 33 | 
 | 
| 34 | Word:
 | 
| 35 |   backticks -> $() (I don't use this) 
 | 
| 36 |   quote removal "$foo" -> $foo
 | 
| 37 |   brace removal ${foo} and "${foo}" -> $foo
 | 
| 38 | 
 | 
| 39 | --tool format
 | 
| 40 | 
 | 
| 41 |   fix indentation and spacing, like clang-format
 | 
| 42 |   can "lower" the LST to a rough representation with keywords / "first words",
 | 
| 43 |   { } ( ), and comments
 | 
| 44 |   - the "atoms" should not have newlines
 | 
| 45 | """
 | 
| 46 | 
 | 
| 47 | from _devbuild.gen.id_kind_asdl import Id, Id_str
 | 
| 48 | from _devbuild.gen.runtime_asdl import word_style_e, word_style_t
 | 
| 49 | from _devbuild.gen.syntax_asdl import (
 | 
| 50 |     loc,
 | 
| 51 |     CompoundWord,
 | 
| 52 |     Token,
 | 
| 53 |     SimpleVarSub,
 | 
| 54 |     BracedVarSub,
 | 
| 55 |     CommandSub,
 | 
| 56 |     DoubleQuoted,
 | 
| 57 |     SingleQuoted,
 | 
| 58 |     word_e,
 | 
| 59 |     word_t,
 | 
| 60 |     word_part,
 | 
| 61 |     word_part_e,
 | 
| 62 |     word_part_t,
 | 
| 63 |     rhs_word_e,
 | 
| 64 |     rhs_word_t,
 | 
| 65 |     sh_lhs,
 | 
| 66 |     sh_lhs_e,
 | 
| 67 |     command,
 | 
| 68 |     command_e,
 | 
| 69 |     BraceGroup,
 | 
| 70 |     for_iter_e,
 | 
| 71 |     case_arg_e,
 | 
| 72 |     case_arg,
 | 
| 73 |     condition,
 | 
| 74 |     condition_e,
 | 
| 75 |     redir_param,
 | 
| 76 |     redir_param_e,
 | 
| 77 |     Redir,
 | 
| 78 | )
 | 
| 79 | from asdl import runtime
 | 
| 80 | from core.error import p_die
 | 
| 81 | from frontend import lexer
 | 
| 82 | from frontend import location
 | 
| 83 | from osh import word_
 | 
| 84 | from mycpp import mylib
 | 
| 85 | from mycpp.mylib import log, print_stderr, tagswitch
 | 
| 86 | 
 | 
| 87 | from typing import Dict, cast, TYPE_CHECKING
 | 
| 88 | if TYPE_CHECKING:
 | 
| 89 |     from _devbuild.gen.syntax_asdl import command_t
 | 
| 90 |     from core import alloc
 | 
| 91 | 
 | 
| 92 | _ = log
 | 
| 93 | 
 | 
| 94 | 
 | 
| 95 | class Cursor(object):
 | 
| 96 |     """
 | 
| 97 |     API to print/transform a complete source file, stored in a single arena.
 | 
| 98 | 
 | 
| 99 |     In, core/alloc.py, SnipCodeBlock() and SnipCodeString work on lines.  They
 | 
| 100 |     don't iterate over tokens.
 | 
| 101 | 
 | 
| 102 |     Or add a separate hash table of Token -> span ID?  That makes sense because
 | 
| 103 |     we need that kind of "address hash" for type checking anyway.
 | 
| 104 | 
 | 
| 105 |     You use the hash table to go from next_token_id .. TokenId(until_token).
 | 
| 106 |     """
 | 
| 107 | 
 | 
| 108 |     def __init__(self, arena, f):
 | 
| 109 |         # type: (alloc.Arena, mylib.Writer) -> None
 | 
| 110 |         self.arena = arena
 | 
| 111 |         self.f = f
 | 
| 112 |         self.next_span_id = 0
 | 
| 113 | 
 | 
| 114 |     def _PrintUntilSpid(self, until_span_id):
 | 
| 115 |         # type: (int) -> None
 | 
| 116 | 
 | 
| 117 |         # Sometimes we add +1
 | 
| 118 |         if until_span_id == runtime.NO_SPID:
 | 
| 119 |             assert 0, 'Missing span ID, got %d' % until_span_id
 | 
| 120 | 
 | 
| 121 |         for span_id in xrange(self.next_span_id, until_span_id):
 | 
| 122 |             span = self.arena.GetToken(span_id)
 | 
| 123 | 
 | 
| 124 |             # A span for Eof may not have a line when the file is completely empty.
 | 
| 125 |             if span.line is None:
 | 
| 126 |                 continue
 | 
| 127 | 
 | 
| 128 |             # Special case for recovering stripped leading space!
 | 
| 129 |             # See osh/word_compile.py
 | 
| 130 |             start_index = (0 if span.id == Id.Lit_CharsWithoutPrefix else
 | 
| 131 |                            span.col)
 | 
| 132 |             end_index = span.col + span.length
 | 
| 133 | 
 | 
| 134 |             piece = span.line.content[start_index:end_index]
 | 
| 135 |             self.f.write(piece)
 | 
| 136 | 
 | 
| 137 |         self.next_span_id = until_span_id
 | 
| 138 | 
 | 
| 139 |     def _SkipUntilSpid(self, next_span_id):
 | 
| 140 |         # type: (int) -> None
 | 
| 141 |         """Skip everything before next_span_id.
 | 
| 142 | 
 | 
| 143 |         Printing will start at next_span_id
 | 
| 144 |         """
 | 
| 145 |         if (next_span_id == runtime.NO_SPID or
 | 
| 146 |                 next_span_id == runtime.NO_SPID + 1):
 | 
| 147 |             assert 0, 'Missing span ID, got %d' % next_span_id
 | 
| 148 |         self.next_span_id = next_span_id
 | 
| 149 | 
 | 
| 150 |     def SkipUntil(self, tok):
 | 
| 151 |         # type: (Token) -> None
 | 
| 152 |         span_id = self.arena.GetSpanId(tok)
 | 
| 153 |         self._SkipUntilSpid(span_id)
 | 
| 154 | 
 | 
| 155 |     def SkipPast(self, tok):
 | 
| 156 |         # type: (Token) -> None
 | 
| 157 |         span_id = self.arena.GetSpanId(tok)
 | 
| 158 |         self._SkipUntilSpid(span_id + 1)
 | 
| 159 | 
 | 
| 160 |     def PrintUntil(self, tok):
 | 
| 161 |         # type: (Token) -> None
 | 
| 162 |         span_id = self.arena.GetSpanId(tok)
 | 
| 163 | 
 | 
| 164 |         # Test invariant
 | 
| 165 |         if mylib.PYTHON:
 | 
| 166 |             arena_tok = self.arena.GetToken(span_id)
 | 
| 167 |             if tok != arena_tok:
 | 
| 168 |                 raise AssertionError(
 | 
| 169 |                     '%s %d %d != %s %d %d' %
 | 
| 170 |                     (tok, span_id, id(tok), arena_tok,
 | 
| 171 |                      self.arena.GetSpanId(arena_tok), id(arena_tok)))
 | 
| 172 | 
 | 
| 173 |         self._PrintUntilSpid(span_id)
 | 
| 174 | 
 | 
| 175 |     def PrintIncluding(self, tok):
 | 
| 176 |         # type: (Token) -> None
 | 
| 177 |         span_id = self.arena.GetSpanId(tok)
 | 
| 178 |         self._PrintUntilSpid(span_id + 1)
 | 
| 179 | 
 | 
| 180 |     def PrintUntilEnd(self):
 | 
| 181 |         # type: () -> None
 | 
| 182 |         self._PrintUntilSpid(self.arena.LastSpanId())
 | 
| 183 | 
 | 
| 184 | 
 | 
| 185 | def LosslessCat(arena):
 | 
| 186 |     # type: (alloc.Arena) -> None
 | 
| 187 |     """
 | 
| 188 |     For testing the lossless invariant: the tokens "add up" to the original
 | 
| 189 |     doc.
 | 
| 190 |     """
 | 
| 191 |     cursor = Cursor(arena, mylib.Stdout())
 | 
| 192 |     cursor.PrintUntilEnd()
 | 
| 193 | 
 | 
| 194 | 
 | 
| 195 | def PrintTokens(arena):
 | 
| 196 |     # type: (alloc.Arena) -> None
 | 
| 197 |     """Debugging tool to see tokens."""
 | 
| 198 | 
 | 
| 199 |     if len(arena.tokens) == 1:  # Special case for line_id == -1
 | 
| 200 |         print('Empty file with EOF token on invalid line:')
 | 
| 201 |         print('%s' % arena.tokens[0])
 | 
| 202 |         return
 | 
| 203 | 
 | 
| 204 |     for i, tok in enumerate(arena.tokens):
 | 
| 205 |         piece = tok.line.content[tok.col:tok.col + tok.length]
 | 
| 206 |         print('%5d %-20s %r' % (i, Id_str(tok.id), piece))
 | 
| 207 |     print_stderr('(%d tokens)' % len(arena.tokens))
 | 
| 208 | 
 | 
| 209 | 
 | 
| 210 | def Ysh_ify(arena, node):
 | 
| 211 |     # type: (alloc.Arena, command_t) -> None
 | 
| 212 |     cursor = Cursor(arena, mylib.Stdout())
 | 
| 213 |     fixer = YshPrinter(cursor, arena, mylib.Stdout())
 | 
| 214 |     fixer.DoCommand(node, None, at_top_level=True)  # no local symbols yet
 | 
| 215 |     fixer.End()
 | 
| 216 | 
 | 
| 217 | 
 | 
| 218 | # PROBLEM: ~ substitution.  That is disabled by "".
 | 
| 219 | # You can turn it into $HOME I guess
 | 
| 220 | # const foo = "$HOME/src"
 | 
| 221 | # const foo = %( ~/src )[0]  # does this make sense?
 | 
| 222 | 
 | 
| 223 | 
 | 
| 224 | def _GetRhsStyle(w):
 | 
| 225 |     # type: (rhs_word_t) -> word_style_t
 | 
| 226 |     """Determine what style an assignment should use. '' or "", or an
 | 
| 227 |     expression.
 | 
| 228 | 
 | 
| 229 |     SQ      foo=         setglobal foo = ''
 | 
| 230 |     SQ      foo=''       setglobal foo = ''
 | 
| 231 |     DQ      foo=""       setglobal foo = ""  # Or we could normalize it if no subs?
 | 
| 232 |     DQ      foo=""       setglobal foo = ""  # Or we could normalize it if no subs?
 | 
| 233 | 
 | 
| 234 |     # Need these too.
 | 
| 235 |     # Or honestly should C strings be the default?  And then raw strings are
 | 
| 236 |     # optional?  Because most usages of \n and \0 can turn into Oil?
 | 
| 237 |     # Yeah I want the default to be statically parseable, so we subvert the \t
 | 
| 238 |     # and \n of command line tools?
 | 
| 239 |     # As long as we are fully analyzing the strings, we might as well go all the
 | 
| 240 |     # way!
 | 
| 241 |     # I think I need a PartialStaticEval() to paper over this.
 | 
| 242 |     #
 | 
| 243 |     # The main issue is regex and globs, because they use escape for a different
 | 
| 244 |     # purpose.  I think just do
 | 
| 245 |     # grep r'foo\tbar' or something.
 | 
| 246 | 
 | 
| 247 |     C_SQ    foo=$'\n'          setglobal foo = C'\n'
 | 
| 248 |     C_DQ    foo=$'\n'"$bar"    setglobal foo = C"\n$(bar)"
 | 
| 249 | 
 | 
| 250 |     Expr    path=${1:-}             setglobal path = $1 or ''
 | 
| 251 |     Expr    host=${2:-$(hostname)}  setglobal host = $2 or $[hostname]
 | 
| 252 | 
 | 
| 253 |     What's the difference between Expr and Unquoted?  I think they're the same/
 | 
| 254 |     """
 | 
| 255 |     # Actually splitting NEVER HAPPENS ON ASSIGNMENT.  LEAVE IT OFF.
 | 
| 256 | 
 | 
| 257 |     UP_w = w
 | 
| 258 |     with tagswitch(w) as case:
 | 
| 259 |         if case(rhs_word_e.Empty):
 | 
| 260 |             return word_style_e.SQ
 | 
| 261 | 
 | 
| 262 |         elif case(rhs_word_e.Compound):
 | 
| 263 |             w = cast(CompoundWord, UP_w)
 | 
| 264 |             if len(w.parts) == 0:
 | 
| 265 |                 raise AssertionError(w)
 | 
| 266 | 
 | 
| 267 |             elif len(w.parts) == 1:
 | 
| 268 |                 part0 = w.parts[0]
 | 
| 269 |                 UP_part0 = part0
 | 
| 270 |                 with tagswitch(part0) as case:
 | 
| 271 |                     # VAR_SUBS
 | 
| 272 |                     if case(word_part_e.TildeSub):
 | 
| 273 |                         #    x=~andy/src
 | 
| 274 |                         # -> setvar x = homedir('andy') + '/src'
 | 
| 275 |                         return word_style_e.Expr
 | 
| 276 | 
 | 
| 277 |                     elif case(word_part_e.Literal):
 | 
| 278 |                         #    local x=y
 | 
| 279 |                         # -> var x = 'y'
 | 
| 280 |                         return word_style_e.SQ
 | 
| 281 | 
 | 
| 282 |                     elif case(word_part_e.SimpleVarSub):
 | 
| 283 |                         #    local x=$myvar
 | 
| 284 |                         # -> var x = "$myvar"
 | 
| 285 |                         # or var x = ${myvar}
 | 
| 286 |                         # or var x = myvar
 | 
| 287 |                         return word_style_e.DQ
 | 
| 288 | 
 | 
| 289 |                     elif case(word_part_e.BracedVarSub, word_part_e.CommandSub,
 | 
| 290 |                               word_part_e.ArithSub):
 | 
| 291 |                         #    x=$(hostname)
 | 
| 292 |                         # -> setvar x = $(hostname)
 | 
| 293 |                         return word_style_e.Unquoted
 | 
| 294 | 
 | 
| 295 |                     elif case(word_part_e.DoubleQuoted):
 | 
| 296 |                         part0 = cast(DoubleQuoted, UP_part0)
 | 
| 297 | 
 | 
| 298 |                         # TODO: remove quotes in single part like "$(hostname)" -> $(hostname)
 | 
| 299 |                         return word_style_e.DQ
 | 
| 300 | 
 | 
| 301 |             else:
 | 
| 302 |                 # multiple parts use YSTR in general?
 | 
| 303 |                 # Depends if there are subs
 | 
| 304 |                 return word_style_e.DQ
 | 
| 305 | 
 | 
| 306 |     # Default
 | 
| 307 |     return word_style_e.SQ
 | 
| 308 | 
 | 
| 309 | 
 | 
| 310 | class YshPrinter(object):
 | 
| 311 |     """Prettify OSH to YSH."""
 | 
| 312 | 
 | 
| 313 |     def __init__(self, cursor, arena, f):
 | 
| 314 |         # type: (Cursor, alloc.Arena, mylib.Writer) -> None
 | 
| 315 |         self.cursor = cursor
 | 
| 316 |         self.arena = arena
 | 
| 317 |         self.f = f
 | 
| 318 | 
 | 
| 319 |     def _DebugSpid(self, spid):
 | 
| 320 |         # type: (int) -> None
 | 
| 321 |         span = self.arena.GetToken(spid)
 | 
| 322 |         s = span.line.content[span.col:span.col + span.length]
 | 
| 323 |         print_stderr('SPID %d = %r' % (spid, s))
 | 
| 324 | 
 | 
| 325 |     def End(self):
 | 
| 326 |         # type: () -> None
 | 
| 327 |         """Make sure we print until the end of the file."""
 | 
| 328 |         self.cursor.PrintUntilEnd()
 | 
| 329 | 
 | 
| 330 |     def DoRedirect(self, node, local_symbols):
 | 
| 331 |         # type: (Redir, Dict[str, bool]) -> None
 | 
| 332 |         """
 | 
| 333 |         Change here docs to <<< '''
 | 
| 334 |         """
 | 
| 335 |         #print(node, file=sys.stderr)
 | 
| 336 |         op_id = node.op.id
 | 
| 337 |         self.cursor.PrintUntil(node.op)
 | 
| 338 | 
 | 
| 339 |         if node.arg.tag() == redir_param_e.HereDoc:
 | 
| 340 |             here_doc = cast(redir_param.HereDoc, node.arg)
 | 
| 341 | 
 | 
| 342 |             here_begin = here_doc.here_begin
 | 
| 343 |             ok, delimiter, delim_quoted = word_.StaticEval(here_begin)
 | 
| 344 |             if not ok:
 | 
| 345 |                 p_die('Invalid here doc delimiter', loc.Word(here_begin))
 | 
| 346 | 
 | 
| 347 |             # Turn everything into <<<.  We just change the quotes
 | 
| 348 |             self.f.write('<<<')
 | 
| 349 | 
 | 
| 350 |             if delim_quoted:
 | 
| 351 |                 self.f.write(" '''")
 | 
| 352 |             else:
 | 
| 353 |                 self.f.write(' """')
 | 
| 354 | 
 | 
| 355 |             delim_end_tok = location.RightTokenForWord(here_begin)
 | 
| 356 |             self.cursor.SkipPast(delim_end_tok)
 | 
| 357 | 
 | 
| 358 |             # Now print the lines.  TODO: Have a flag to indent these to the level of
 | 
| 359 |             # the owning command, e.g.
 | 
| 360 |             #   cat <<EOF
 | 
| 361 |             # EOF
 | 
| 362 |             # Or since most here docs are the top level, you could just have a hack
 | 
| 363 |             # for a fixed indent?  TODO: Look at real use cases.
 | 
| 364 |             for part in here_doc.stdin_parts:
 | 
| 365 |                 self.DoWordPart(part, local_symbols)
 | 
| 366 | 
 | 
| 367 |             self.cursor.SkipPast(here_doc.here_end_tok)
 | 
| 368 |             if delim_quoted:
 | 
| 369 |                 self.f.write("'''\n")
 | 
| 370 |             else:
 | 
| 371 |                 self.f.write('"""\n')
 | 
| 372 | 
 | 
| 373 |         else:
 | 
| 374 |             pass
 | 
| 375 | 
 | 
| 376 |         # cat << EOF
 | 
| 377 |         # hello $name
 | 
| 378 |         # EOF
 | 
| 379 |         # cat <<< """
 | 
| 380 |         # hello $name
 | 
| 381 |         # """
 | 
| 382 | 
 | 
| 383 |         # cat << 'EOF'
 | 
| 384 |         # no expansion
 | 
| 385 |         # EOF
 | 
| 386 | 
 | 
| 387 |         # cat <<< '''
 | 
| 388 |         # no expansion
 | 
| 389 |         # '''
 | 
| 390 | 
 | 
| 391 |     def DoShAssignment(self, node, at_top_level, local_symbols):
 | 
| 392 |         # type: (command.ShAssignment, bool, Dict[str, bool]) -> None
 | 
| 393 |         """
 | 
| 394 |         local_symbols:
 | 
| 395 |           - Add every 'local' declaration to it
 | 
| 396 |             - problem: what if you have local in an "if" ?
 | 
| 397 |             - we could treat it like nested scope and see what happens?  Do any
 | 
| 398 |               programs have a problem with it?
 | 
| 399 |               case/if/for/while/BraceGroup all define scopes or what?
 | 
| 400 |               You don't want inconsistency of variables that could be defined at
 | 
| 401 |               any point.
 | 
| 402 |               - or maybe you only need it within "if / case" ?  Well I guess
 | 
| 403 |                 for/while can break out of the loop and cause problems.  A break is
 | 
| 404 |                   an "if".
 | 
| 405 | 
 | 
| 406 |           - for subsequent
 | 
| 407 |         """
 | 
| 408 |         # Change RHS to expression language.  Bare words not allowed.  foo -> 'foo'
 | 
| 409 | 
 | 
| 410 |         has_rhs = False  # TODO: Should be on a per-variable basis.
 | 
| 411 |         # local a=b c=d, or just punt on those
 | 
| 412 |         defined_locally = False  # is it a local variable in this function?
 | 
| 413 |         # can't tell if global
 | 
| 414 | 
 | 
| 415 |         if True:
 | 
| 416 |             self.cursor.PrintUntil(node.pairs[0].left)
 | 
| 417 | 
 | 
| 418 |             # For now, just detect whether the FIRST assignment on the line has been
 | 
| 419 |             # declared locally.  We might want to split every line into separate
 | 
| 420 |             # statements.
 | 
| 421 |             if local_symbols is not None:
 | 
| 422 |                 lhs0 = node.pairs[0].lhs
 | 
| 423 |                 #if lhs0.tag() == sh_lhs_e.Name and lhs0.name in local_symbols:
 | 
| 424 |                 #  defined_locally = True
 | 
| 425 | 
 | 
| 426 |                 #print("CHECKING NAME", lhs0.name, defined_locally, local_symbols)
 | 
| 427 | 
 | 
| 428 |             # TODO: Avoid translating these
 | 
| 429 |             has_array_index = [
 | 
| 430 |                 pair.lhs.tag() == sh_lhs_e.UnparsedIndex for pair in node.pairs
 | 
| 431 |             ]
 | 
| 432 | 
 | 
| 433 |             # need semantic analysis.
 | 
| 434 |             # Would be nice to assume that it's a local though.
 | 
| 435 |             if at_top_level:
 | 
| 436 |                 self.f.write('setvar ')
 | 
| 437 |             elif defined_locally:
 | 
| 438 |                 self.f.write('set ')
 | 
| 439 |                 #self.f.write('[local mutated]')
 | 
| 440 |             else:
 | 
| 441 |                 # We're in a function, but it's not defined locally, so we must be
 | 
| 442 |                 # mutating a global.
 | 
| 443 |                 self.f.write('setvar ')
 | 
| 444 | 
 | 
| 445 |         # foo=bar spam=eggs -> foo = 'bar', spam = 'eggs'
 | 
| 446 |         n = len(node.pairs)
 | 
| 447 |         for i, pair in enumerate(node.pairs):
 | 
| 448 |             lhs = pair.lhs
 | 
| 449 |             UP_lhs = lhs
 | 
| 450 |             with tagswitch(lhs) as case:
 | 
| 451 |                 if case(sh_lhs_e.Name):
 | 
| 452 |                     lhs = cast(sh_lhs.Name, UP_lhs)
 | 
| 453 | 
 | 
| 454 |                     self.cursor.PrintUntil(pair.left)
 | 
| 455 |                     # Assume skipping over one Lit_VarLike token
 | 
| 456 |                     self.cursor.SkipPast(pair.left)
 | 
| 457 | 
 | 
| 458 |                     # Replace name.  I guess it's Lit_Chars.
 | 
| 459 |                     self.f.write(lhs.name)
 | 
| 460 |                     self.f.write(' = ')
 | 
| 461 | 
 | 
| 462 |                     # TODO: This should be translated from Empty.
 | 
| 463 |                     if pair.rhs.tag() == rhs_word_e.Empty:
 | 
| 464 |                         self.f.write("''")  # local i -> var i = ''
 | 
| 465 |                     else:
 | 
| 466 |                         self.DoRhsWord(pair.rhs, local_symbols)
 | 
| 467 | 
 | 
| 468 |                 elif case(sh_lhs_e.UnparsedIndex):
 | 
| 469 |                     # --one-pass-parse gives us this node, instead of IndexedName
 | 
| 470 |                     pass
 | 
| 471 | 
 | 
| 472 |                 else:
 | 
| 473 |                     raise AssertionError(pair.lhs.__class__.__name__)
 | 
| 474 | 
 | 
| 475 |             if i != n - 1:
 | 
| 476 |                 self.f.write(',')
 | 
| 477 | 
 | 
| 478 |     def _DoSimple(self, node, local_symbols):
 | 
| 479 |         # type: (command.Simple, Dict[str, bool]) -> None
 | 
| 480 | 
 | 
| 481 |         # How to preserve spaces between words?  Do you want to do it?
 | 
| 482 |         # Well you need to test this:
 | 
| 483 |         #
 | 
| 484 |         # echo foo \
 | 
| 485 |         #   bar
 | 
| 486 | 
 | 
| 487 |         if len(node.more_env):
 | 
| 488 |             # We only need to transform the right side, not left side.
 | 
| 489 |             for pair in node.more_env:
 | 
| 490 |                 self.DoRhsWord(pair.val, local_symbols)
 | 
| 491 | 
 | 
| 492 |         if len(node.words):
 | 
| 493 |             first_word = node.words[0]
 | 
| 494 |             ok, val, quoted = word_.StaticEval(first_word)
 | 
| 495 |             word0_tok = location.LeftTokenForWord(first_word)
 | 
| 496 |             if ok and not quoted:
 | 
| 497 |                 if val == '[' and len(node.words) >= 3:
 | 
| 498 |                     word2 = node.words[-2]
 | 
| 499 |                     last_word = node.words[-1]
 | 
| 500 | 
 | 
| 501 |                     # Check if last word is ]
 | 
| 502 |                     ok, val, quoted = word_.StaticEval(last_word)
 | 
| 503 |                     if ok and not quoted and val == ']':
 | 
| 504 |                         # Replace [ with 'test'
 | 
| 505 |                         self.cursor.PrintUntil(word0_tok)
 | 
| 506 |                         self.cursor.SkipPast(word0_tok)
 | 
| 507 |                         self.f.write('test')
 | 
| 508 | 
 | 
| 509 |                         for w in node.words[1:-1]:
 | 
| 510 |                             self.DoWordInCommand(w, local_symbols)
 | 
| 511 | 
 | 
| 512 |                         # Now omit ]
 | 
| 513 |                         tok2 = location.RightTokenForWord(word2)
 | 
| 514 |                         rbrack_tok = location.LeftTokenForWord(last_word)
 | 
| 515 | 
 | 
| 516 |                         # Skip the space token before ]
 | 
| 517 |                         self.cursor.PrintIncluding(tok2)
 | 
| 518 |                         # ] takes one spid
 | 
| 519 |                         self.cursor.SkipPast(rbrack_tok)
 | 
| 520 |                         return
 | 
| 521 |                     else:
 | 
| 522 |                         raise RuntimeError('Got [ without ]')
 | 
| 523 | 
 | 
| 524 |                 elif val == '.':
 | 
| 525 |                     self.cursor.PrintUntil(word0_tok)
 | 
| 526 |                     self.cursor.SkipPast(word0_tok)
 | 
| 527 |                     self.f.write('source')
 | 
| 528 |                     return
 | 
| 529 | 
 | 
| 530 |         for w in node.words:
 | 
| 531 |             self.DoWordInCommand(w, local_symbols)
 | 
| 532 | 
 | 
| 533 |         # TODO: Print the terminator.  Could be \n or ;
 | 
| 534 |         # Need to print env like PYTHONPATH = 'foo' && ls
 | 
| 535 |         # Need to print redirects:
 | 
| 536 |         # < > are the same.  << is here string, and >> is assignment.
 | 
| 537 |         # append is >+
 | 
| 538 | 
 | 
| 539 |         # TODO: static_eval of simple command
 | 
| 540 |         # - [ -> "test".  Eliminate trailing ].
 | 
| 541 |         # - . -> source, etc.
 | 
| 542 | 
 | 
| 543 |     def DoCommand(self, node, local_symbols, at_top_level=False):
 | 
| 544 |         # type: (command_t, Dict[str, bool], bool) -> None
 | 
| 545 | 
 | 
| 546 |         UP_node = node
 | 
| 547 | 
 | 
| 548 |         with tagswitch(node) as case:
 | 
| 549 |             if case(command_e.CommandList):
 | 
| 550 |                 node = cast(command.CommandList, UP_node)
 | 
| 551 | 
 | 
| 552 |                 # TODO: How to distinguish between echo hi; echo bye; and on
 | 
| 553 |                 # separate lines
 | 
| 554 |                 for child in node.children:
 | 
| 555 |                     self.DoCommand(child,
 | 
| 556 |                                    local_symbols,
 | 
| 557 |                                    at_top_level=at_top_level)
 | 
| 558 | 
 | 
| 559 |             elif case(command_e.Redirect):
 | 
| 560 |                 node = cast(command.Redirect, UP_node)
 | 
| 561 | 
 | 
| 562 |                 self.DoCommand(node.child,
 | 
| 563 |                                local_symbols,
 | 
| 564 |                                at_top_level=at_top_level)
 | 
| 565 |                 for r in node.redirects:
 | 
| 566 |                     self.DoRedirect(r, local_symbols)
 | 
| 567 | 
 | 
| 568 |             elif case(command_e.Simple):
 | 
| 569 |                 node = cast(command.Simple, UP_node)
 | 
| 570 | 
 | 
| 571 |                 self._DoSimple(node, local_symbols)
 | 
| 572 | 
 | 
| 573 |             elif case(command_e.ShAssignment):
 | 
| 574 |                 node = cast(command.ShAssignment, UP_node)
 | 
| 575 | 
 | 
| 576 |                 self.DoShAssignment(node, at_top_level, local_symbols)
 | 
| 577 | 
 | 
| 578 |             elif case(command_e.Pipeline):
 | 
| 579 |                 node = cast(command.Pipeline, UP_node)
 | 
| 580 | 
 | 
| 581 |                 for child in node.children:
 | 
| 582 |                     self.DoCommand(child, local_symbols)
 | 
| 583 | 
 | 
| 584 |             elif case(command_e.AndOr):
 | 
| 585 |                 node = cast(command.AndOr, UP_node)
 | 
| 586 | 
 | 
| 587 |                 for child in node.children:
 | 
| 588 |                     self.DoCommand(child, local_symbols)
 | 
| 589 | 
 | 
| 590 |             elif case(command_e.Sentence):
 | 
| 591 |                 node = cast(command.Sentence, UP_node)
 | 
| 592 | 
 | 
| 593 |                 # 'ls &' to 'fork ls'
 | 
| 594 |                 # Keep ; the same.
 | 
| 595 |                 self.DoCommand(node.child, local_symbols)
 | 
| 596 | 
 | 
| 597 |             # This has to be different in the function case.
 | 
| 598 |             elif case(command_e.BraceGroup):
 | 
| 599 |                 node = cast(BraceGroup, UP_node)
 | 
| 600 | 
 | 
| 601 |                 # { echo hi; } -> do { echo hi }
 | 
| 602 |                 # For now it might be OK to keep 'do { echo hi; }
 | 
| 603 |                 self.cursor.PrintUntil(node.left)
 | 
| 604 |                 self.cursor.SkipPast(node.left)
 | 
| 605 |                 self.f.write('do {')
 | 
| 606 | 
 | 
| 607 |                 for child in node.children:
 | 
| 608 |                     self.DoCommand(child, local_symbols)
 | 
| 609 | 
 | 
| 610 |             elif case(command_e.Subshell):
 | 
| 611 |                 node = cast(command.Subshell, UP_node)
 | 
| 612 | 
 | 
| 613 |                 # (echo hi) -> shell echo hi
 | 
| 614 |                 # (echo hi; echo bye) -> shell {echo hi; echo bye}
 | 
| 615 | 
 | 
| 616 |                 self.cursor.PrintUntil(node.left)
 | 
| 617 |                 self.cursor.SkipPast(node.left)
 | 
| 618 |                 self.f.write('shell {')
 | 
| 619 | 
 | 
| 620 |                 self.DoCommand(node.child, local_symbols)
 | 
| 621 | 
 | 
| 622 |                 #self._DebugSpid(right_spid)
 | 
| 623 |                 #self._DebugSpid(right_spid + 1)
 | 
| 624 | 
 | 
| 625 |                 #print('RIGHT SPID', right_spid)
 | 
| 626 |                 self.cursor.PrintUntil(node.right)
 | 
| 627 |                 self.cursor.SkipPast(node.right)
 | 
| 628 |                 self.f.write('}')
 | 
| 629 | 
 | 
| 630 |             elif case(command_e.ShFunction):
 | 
| 631 |                 node = cast(command.ShFunction, UP_node)
 | 
| 632 | 
 | 
| 633 |                 # TODO: skip name
 | 
| 634 |                 #self.f.write('proc %s' % node.name)
 | 
| 635 | 
 | 
| 636 |                 # New symbol table for every function.
 | 
| 637 |                 new_local_symbols = {}  # type: Dict[str, bool]
 | 
| 638 | 
 | 
| 639 |                 # Should be the left most span, including 'function'
 | 
| 640 |                 if node.keyword:  # function foo { ...
 | 
| 641 |                     self.cursor.PrintUntil(node.keyword)
 | 
| 642 |                 else:  # foo() { ...
 | 
| 643 |                     self.cursor.PrintUntil(node.name_tok)
 | 
| 644 | 
 | 
| 645 |                 self.f.write('proc %s ' % node.name)
 | 
| 646 | 
 | 
| 647 |                 UP_body = node.body
 | 
| 648 |                 with tagswitch(UP_body) as case:
 | 
| 649 |                     if case(command_e.BraceGroup):
 | 
| 650 |                         body = cast(BraceGroup, UP_body)
 | 
| 651 |                         self.cursor.SkipUntil(body.left)
 | 
| 652 | 
 | 
| 653 |                         # Don't add "do" like a standalone brace group.  Just use {}.
 | 
| 654 |                         for child in body.children:
 | 
| 655 |                             self.DoCommand(child, new_local_symbols)
 | 
| 656 |                     else:
 | 
| 657 |                         # very rare cases like f() ( subshell )
 | 
| 658 |                         pass
 | 
| 659 | 
 | 
| 660 |             elif case(command_e.DoGroup):
 | 
| 661 |                 node = cast(command.DoGroup, UP_node)
 | 
| 662 | 
 | 
| 663 |                 self.cursor.PrintUntil(node.left)
 | 
| 664 |                 self.cursor.SkipPast(node.left)
 | 
| 665 |                 self.f.write('{')
 | 
| 666 | 
 | 
| 667 |                 for child in node.children:
 | 
| 668 |                     self.DoCommand(child, local_symbols)
 | 
| 669 | 
 | 
| 670 |                 self.cursor.PrintUntil(node.right)
 | 
| 671 |                 self.cursor.SkipPast(node.right)
 | 
| 672 |                 self.f.write('}')
 | 
| 673 | 
 | 
| 674 |             elif case(command_e.ForEach):
 | 
| 675 |                 node = cast(command.ForEach, UP_node)
 | 
| 676 | 
 | 
| 677 |                 # Need to preserve spaces between words, because there can be line
 | 
| 678 |                 # wrapping.
 | 
| 679 |                 # for x in a b c \
 | 
| 680 |                 #    d e f; do
 | 
| 681 | 
 | 
| 682 |                 UP_iterable = node.iterable
 | 
| 683 |                 with tagswitch(node.iterable) as case:
 | 
| 684 |                     if case(for_iter_e.Args):
 | 
| 685 |                         self.f.write('for %s in @ARGV ' % node.iter_names[0])
 | 
| 686 | 
 | 
| 687 |                         # note: command_t doesn't have .spids
 | 
| 688 |                         body_tok = location.TokenForCommand(node.body)
 | 
| 689 |                         self.cursor.SkipUntil(body_tok)
 | 
| 690 | 
 | 
| 691 |                     elif case(for_iter_e.Words):
 | 
| 692 |                         pass
 | 
| 693 | 
 | 
| 694 |                     elif case(for_iter_e.YshExpr):
 | 
| 695 |                         pass
 | 
| 696 | 
 | 
| 697 |                 if node.semi_tok is not None:
 | 
| 698 |                     self.cursor.PrintUntil(node.semi_tok)
 | 
| 699 |                     self.cursor.SkipPast(node.semi_tok)
 | 
| 700 | 
 | 
| 701 |                 self.DoCommand(node.body, local_symbols)
 | 
| 702 | 
 | 
| 703 |             elif case(command_e.WhileUntil):
 | 
| 704 |                 node = cast(command.WhileUntil, UP_node)
 | 
| 705 | 
 | 
| 706 |                 # Skip 'until', and replace it with 'while not'
 | 
| 707 |                 if node.keyword.id == Id.KW_Until:
 | 
| 708 |                     self.cursor.PrintUntil(node.keyword)
 | 
| 709 |                     self.cursor.SkipPast(node.keyword)
 | 
| 710 |                     self.f.write('while !')
 | 
| 711 | 
 | 
| 712 |                 if node.cond.tag() == condition_e.Shell:
 | 
| 713 |                     commands = cast(condition.Shell, node.cond).commands
 | 
| 714 |                     # Skip the semi-colon in the condition, which is usually a Sentence
 | 
| 715 |                     if len(commands) == 1 and commands[0].tag(
 | 
| 716 |                     ) == command_e.Sentence:
 | 
| 717 |                         sentence = cast(command.Sentence, commands[0])
 | 
| 718 |                         self.DoCommand(sentence.child, local_symbols)
 | 
| 719 |                         self.cursor.SkipPast(sentence.terminator)
 | 
| 720 | 
 | 
| 721 |                 self.DoCommand(node.body, local_symbols)
 | 
| 722 | 
 | 
| 723 |             elif case(command_e.If):
 | 
| 724 |                 node = cast(command.If, UP_node)
 | 
| 725 | 
 | 
| 726 |                 # if foo; then -> if foo {
 | 
| 727 |                 # elif foo; then -> } elif foo {
 | 
| 728 |                 for i, arm in enumerate(node.arms):
 | 
| 729 |                     elif_tok = arm.keyword
 | 
| 730 |                     then_tok = arm.then_tok
 | 
| 731 | 
 | 
| 732 |                     if i != 0:  # 'if' not 'elif' on the first arm
 | 
| 733 |                         self.cursor.PrintUntil(elif_tok)
 | 
| 734 |                         self.f.write('} ')
 | 
| 735 | 
 | 
| 736 |                     cond = arm.cond
 | 
| 737 |                     if cond.tag() == condition_e.Shell:
 | 
| 738 |                         commands = cast(condition.Shell, cond).commands
 | 
| 739 |                         if (len(commands) == 1 and
 | 
| 740 |                                 commands[0].tag() == command_e.Sentence):
 | 
| 741 |                             sentence = cast(command.Sentence, commands[0])
 | 
| 742 |                             self.DoCommand(sentence, local_symbols)
 | 
| 743 | 
 | 
| 744 |                             # Remove semi-colon
 | 
| 745 |                             self.cursor.PrintUntil(sentence.terminator)
 | 
| 746 |                             self.cursor.SkipPast(sentence.terminator)
 | 
| 747 |                         else:
 | 
| 748 |                             for child in commands:
 | 
| 749 |                                 self.DoCommand(child, local_symbols)
 | 
| 750 | 
 | 
| 751 |                     self.cursor.PrintUntil(then_tok)
 | 
| 752 |                     self.cursor.SkipPast(then_tok)
 | 
| 753 |                     self.f.write('{')
 | 
| 754 | 
 | 
| 755 |                     for child in arm.action:
 | 
| 756 |                         self.DoCommand(child, local_symbols)
 | 
| 757 | 
 | 
| 758 |                 # else -> } else {
 | 
| 759 |                 if len(node.else_action):
 | 
| 760 |                     self.cursor.PrintUntil(node.else_kw)
 | 
| 761 |                     self.f.write('} ')
 | 
| 762 |                     self.cursor.PrintIncluding(node.else_kw)
 | 
| 763 |                     self.f.write(' {')
 | 
| 764 | 
 | 
| 765 |                     for child in node.else_action:
 | 
| 766 |                         self.DoCommand(child, local_symbols)
 | 
| 767 | 
 | 
| 768 |                 # fi -> }
 | 
| 769 |                 self.cursor.PrintUntil(node.fi_kw)
 | 
| 770 |                 self.cursor.SkipPast(node.fi_kw)
 | 
| 771 |                 self.f.write('}')
 | 
| 772 | 
 | 
| 773 |             elif case(command_e.Case):
 | 
| 774 |                 node = cast(command.Case, UP_node)
 | 
| 775 | 
 | 
| 776 |                 to_match = None  # type: word_t
 | 
| 777 |                 with tagswitch(node.to_match) as case:
 | 
| 778 |                     if case(case_arg_e.YshExpr):
 | 
| 779 |                         return
 | 
| 780 |                     elif case(case_arg_e.Word):
 | 
| 781 |                         to_match = cast(case_arg.Word, node.to_match).w
 | 
| 782 |                     else:
 | 
| 783 |                         raise AssertionError()
 | 
| 784 | 
 | 
| 785 |                 self.cursor.PrintIncluding(node.case_kw)
 | 
| 786 | 
 | 
| 787 |                 # Figure out the variable name, so we can translate
 | 
| 788 |                 # - $var to (var)
 | 
| 789 |                 # - "$var" to (var)
 | 
| 790 |                 var_part = None  # type: SimpleVarSub
 | 
| 791 |                 with tagswitch(to_match) as case:
 | 
| 792 |                     if case(word_e.Compound):
 | 
| 793 |                         w = cast(CompoundWord, to_match)
 | 
| 794 |                         part0 = w.parts[0]
 | 
| 795 | 
 | 
| 796 |                         with tagswitch(part0) as case2:
 | 
| 797 |                             if case2(word_part_e.SimpleVarSub):
 | 
| 798 |                                 var_part = cast(SimpleVarSub, part0)
 | 
| 799 | 
 | 
| 800 |                             elif case2(word_part_e.DoubleQuoted):
 | 
| 801 |                                 dq_part = cast(DoubleQuoted, part0)
 | 
| 802 |                                 if len(dq_part.parts) == 1:
 | 
| 803 |                                     dq_part0 = dq_part.parts[0]
 | 
| 804 | 
 | 
| 805 |                                     # Nesting is annoying -- it would be nice to use pattern
 | 
| 806 |                                     # matching, but mycpp won't like it.
 | 
| 807 |                                     # TODO: extract into a common function
 | 
| 808 |                                     with tagswitch(dq_part0) as case3:
 | 
| 809 |                                         if case3(word_part_e.SimpleVarSub):
 | 
| 810 |                                             var_part = cast(
 | 
| 811 |                                                 SimpleVarSub, dq_part0)
 | 
| 812 |                                             #log("VAR PART %s", var_part)
 | 
| 813 | 
 | 
| 814 |                 if var_part:
 | 
| 815 |                     self.f.write(' (')
 | 
| 816 |                     self.f.write(lexer.LazyStr(var_part.tok))
 | 
| 817 |                     self.f.write(') ')
 | 
| 818 | 
 | 
| 819 |                 self.cursor.SkipPast(node.arms_start)  # Skip past 'in'
 | 
| 820 |                 self.f.write('{')
 | 
| 821 | 
 | 
| 822 |                 missing_last_dsemi = False
 | 
| 823 | 
 | 
| 824 |                 for case_arm in node.arms:
 | 
| 825 |                     # Replace ) with {
 | 
| 826 |                     self.cursor.PrintUntil(case_arm.middle)
 | 
| 827 |                     self.f.write(' {')
 | 
| 828 |                     self.cursor.SkipPast(case_arm.middle)
 | 
| 829 | 
 | 
| 830 |                     for child in case_arm.action:
 | 
| 831 |                         self.DoCommand(child, local_symbols)
 | 
| 832 | 
 | 
| 833 |                     if case_arm.right:
 | 
| 834 |                         # Change ;; to }
 | 
| 835 |                         self.cursor.PrintUntil(case_arm.right)
 | 
| 836 |                         self.f.write('}')
 | 
| 837 |                         self.cursor.SkipPast(case_arm.right)
 | 
| 838 |                     else:
 | 
| 839 |                         # valid: case $x in pat) echo hi ; esac
 | 
| 840 |                         missing_last_dsemi = True
 | 
| 841 | 
 | 
| 842 |                 self.cursor.PrintUntil(node.arms_end)  # 'esac' or }
 | 
| 843 | 
 | 
| 844 |                 if missing_last_dsemi:  # Print } for missing ;;
 | 
| 845 |                     self.f.write('}\n')
 | 
| 846 | 
 | 
| 847 |                 self.cursor.SkipPast(node.arms_end)  # 'esac' or }
 | 
| 848 | 
 | 
| 849 |                 self.f.write('}')  # in place of 'esac'
 | 
| 850 | 
 | 
| 851 |             elif case(command_e.TimeBlock):
 | 
| 852 |                 node = cast(command.TimeBlock, UP_node)
 | 
| 853 | 
 | 
| 854 |                 self.DoCommand(node.pipeline, local_symbols)
 | 
| 855 | 
 | 
| 856 |             elif case(command_e.DParen):
 | 
| 857 |                 node = cast(command.DParen, UP_node)
 | 
| 858 |                 # TODO: arith expressions can words with command subs
 | 
| 859 |                 pass
 | 
| 860 | 
 | 
| 861 |             elif case(command_e.DBracket):
 | 
| 862 |                 node = cast(command.DBracket, UP_node)
 | 
| 863 | 
 | 
| 864 |                 # TODO: bool_expr_t can have words with command subs
 | 
| 865 |                 pass
 | 
| 866 | 
 | 
| 867 |             else:
 | 
| 868 |                 pass
 | 
| 869 |                 #log('Command not handled: %s', node)
 | 
| 870 |                 #raise AssertionError(node.__class__.__name__)
 | 
| 871 | 
 | 
| 872 |     def DoRhsWord(self, node, local_symbols):
 | 
| 873 |         # type: (rhs_word_t, Dict[str, bool]) -> None
 | 
| 874 |         """For the RHS of assignments.
 | 
| 875 | 
 | 
| 876 |         TODO: for complex cases of word joining:
 | 
| 877 |             local a=unquoted'single'"double"'"'
 | 
| 878 | 
 | 
| 879 |         We can try to handle it:
 | 
| 880 |             var a = y"unquotedsingledouble\""
 | 
| 881 | 
 | 
| 882 |         Or simply abort and LEAVE IT ALONE.  We should only translate things we
 | 
| 883 |         recognize.
 | 
| 884 |         """
 | 
| 885 |         UP_node = node
 | 
| 886 |         with tagswitch(node) as case:
 | 
| 887 |             if case(rhs_word_e.Empty):
 | 
| 888 |                 self.f.write("''")
 | 
| 889 | 
 | 
| 890 |             elif case(rhs_word_e.Compound):
 | 
| 891 |                 node = cast(CompoundWord, UP_node)
 | 
| 892 | 
 | 
| 893 |                 # TODO: This is wrong!
 | 
| 894 |                 style = _GetRhsStyle(node)
 | 
| 895 |                 if style == word_style_e.SQ:
 | 
| 896 |                     self.f.write("'")
 | 
| 897 |                     self.DoWordInCommand(node, local_symbols)
 | 
| 898 |                     self.f.write("'")
 | 
| 899 |                 elif style == word_style_e.DQ:
 | 
| 900 |                     self.f.write('"')
 | 
| 901 |                     self.DoWordInCommand(node, local_symbols)
 | 
| 902 |                     self.f.write('"')
 | 
| 903 |                 # TODO: Put these back
 | 
| 904 |                 #elif style == word_style_e.Expr:
 | 
| 905 |                 #  pass
 | 
| 906 |                 #elif style == word_style_e.Unquoted:
 | 
| 907 |                 #  pass
 | 
| 908 |                 else:
 | 
| 909 |                     # "${foo:-default}" -> foo or 'default'
 | 
| 910 |                     # ${foo:-default} -> @split(foo or 'default')
 | 
| 911 |                     #                    @(foo or 'default')  -- implicit split.
 | 
| 912 | 
 | 
| 913 |                     if word_.IsVarSub(node):  # ${1} or "$1"
 | 
| 914 |                         # Do it in expression mode
 | 
| 915 |                         pass
 | 
| 916 |                     # NOTE: ArithSub with $(1 +2 ) is different than 1 + 2 because of
 | 
| 917 |                     # conversion to string.
 | 
| 918 | 
 | 
| 919 |                     # For now, just stub it out
 | 
| 920 |                     self.DoWordInCommand(node, local_symbols)
 | 
| 921 | 
 | 
| 922 |     def DoWordInCommand(self, node, local_symbols):
 | 
| 923 |         # type: (word_t, Dict[str, bool]) -> None
 | 
| 924 |         """E.g. remove unquoted.
 | 
| 925 | 
 | 
| 926 |         echo "$x" -> echo $x
 | 
| 927 |         """
 | 
| 928 |         UP_node = node
 | 
| 929 | 
 | 
| 930 |         with tagswitch(node) as case:
 | 
| 931 |             if case(word_e.Compound):
 | 
| 932 |                 node = cast(CompoundWord, UP_node)
 | 
| 933 | 
 | 
| 934 |                 # UNQUOTE simple var subs
 | 
| 935 | 
 | 
| 936 |                 # Special case for "$@".
 | 
| 937 |                 # TODO:
 | 
| 938 |                 # "$foo" -> $foo
 | 
| 939 |                 # "${foo}" -> $foo
 | 
| 940 | 
 | 
| 941 |                 if (len(node.parts) == 1 and
 | 
| 942 |                         node.parts[0].tag() == word_part_e.DoubleQuoted):
 | 
| 943 |                     dq_part = cast(DoubleQuoted, node.parts[0])
 | 
| 944 | 
 | 
| 945 |                     # NOTE: In double quoted case, this is the begin and end quote.
 | 
| 946 |                     # Do we need a HereDoc part?
 | 
| 947 | 
 | 
| 948 |                     if len(dq_part.parts) == 1:
 | 
| 949 |                         part0 = dq_part.parts[0]
 | 
| 950 |                         if part0.tag() == word_part_e.SimpleVarSub:
 | 
| 951 |                             vsub_part = cast(SimpleVarSub, dq_part.parts[0])
 | 
| 952 |                             if vsub_part.tok.id == Id.VSub_At:
 | 
| 953 |                                 self.cursor.PrintUntil(dq_part.left)
 | 
| 954 |                                 self.cursor.SkipPast(
 | 
| 955 |                                     dq_part.right)  # " then $@ then "
 | 
| 956 |                                 self.f.write('@ARGV')
 | 
| 957 |                                 return  # Done replacing
 | 
| 958 | 
 | 
| 959 |                             # "$1" -> $1, "$foo" -> $foo
 | 
| 960 |                             if vsub_part.tok.id in (Id.VSub_Number,
 | 
| 961 |                                                     Id.VSub_DollarName):
 | 
| 962 |                                 self.cursor.PrintUntil(dq_part.left)
 | 
| 963 |                                 self.cursor.SkipPast(dq_part.right)
 | 
| 964 |                                 self.f.write(lexer.TokenVal(vsub_part.tok))
 | 
| 965 |                                 return
 | 
| 966 | 
 | 
| 967 |                         # Single arith sub, command sub, etc.
 | 
| 968 |                         # On the other hand, an unquoted one needs to turn into
 | 
| 969 |                         #
 | 
| 970 |                         # $(echo one two) -> @[echo one two]
 | 
| 971 |                         # `echo one two` -> @[echo one two]
 | 
| 972 |                         #
 | 
| 973 |                         # ${var:-'the default'} -> @$(var or 'the default')
 | 
| 974 |                         #
 | 
| 975 |                         # $((1 + 2)) -> $(1 + 2) -- this is OK unquoted
 | 
| 976 | 
 | 
| 977 |                         elif part0.tag() == word_part_e.BracedVarSub:
 | 
| 978 |                             # Skip over quote
 | 
| 979 |                             self.cursor.PrintUntil(dq_part.left)
 | 
| 980 |                             self.cursor.SkipPast(dq_part.left)
 | 
| 981 |                             self.DoWordPart(part0, local_symbols)
 | 
| 982 |                             self.cursor.SkipPast(dq_part.right)
 | 
| 983 |                             return
 | 
| 984 | 
 | 
| 985 |                         elif part0.tag() == word_part_e.CommandSub:
 | 
| 986 |                             self.cursor.PrintUntil(dq_part.left)
 | 
| 987 |                             self.cursor.SkipPast(dq_part.left)
 | 
| 988 |                             self.DoWordPart(part0, local_symbols)
 | 
| 989 |                             self.cursor.SkipPast(dq_part.right)
 | 
| 990 |                             return
 | 
| 991 | 
 | 
| 992 |                 # TODO: 'foo'"bar" should be "foobar", etc.
 | 
| 993 |                 # If any part is double quoted, you can always double quote the whole
 | 
| 994 |                 # thing?
 | 
| 995 |                 for part in node.parts:
 | 
| 996 |                     self.DoWordPart(part, local_symbols)
 | 
| 997 | 
 | 
| 998 |             elif case(word_e.BracedTree):
 | 
| 999 |                 # Not doing anything now
 | 
| 1000 |                 pass
 | 
| 1001 | 
 | 
| 1002 |             else:
 | 
| 1003 |                 raise AssertionError(node.__class__.__name__)
 | 
| 1004 | 
 | 
| 1005 |     def DoWordPart(self, node, local_symbols, quoted=False):
 | 
| 1006 |         # type: (word_part_t, Dict[str, bool], bool) -> None
 | 
| 1007 | 
 | 
| 1008 |         left_tok = location.LeftTokenForWordPart(node)
 | 
| 1009 |         if left_tok:
 | 
| 1010 |             self.cursor.PrintUntil(left_tok)
 | 
| 1011 | 
 | 
| 1012 |         UP_node = node
 | 
| 1013 | 
 | 
| 1014 |         with tagswitch(node) as case:
 | 
| 1015 |             if case(word_part_e.ShArrayLiteral, word_part_e.BashAssocLiteral,
 | 
| 1016 |                     word_part_e.TildeSub, word_part_e.ExtGlob):
 | 
| 1017 |                 pass
 | 
| 1018 | 
 | 
| 1019 |             elif case(word_part_e.EscapedLiteral):
 | 
| 1020 |                 node = cast(word_part.EscapedLiteral, UP_node)
 | 
| 1021 |                 if quoted:
 | 
| 1022 |                     pass
 | 
| 1023 |                 else:
 | 
| 1024 |                     # If unquoted \e, it should quoted instead.  ' ' vs. \<invisible space>
 | 
| 1025 |                     # Hm is this necessary though?  I think the only motivation is changing
 | 
| 1026 |                     # \{ and \( for macros.  And ' ' to be readable/visible.
 | 
| 1027 |                     t = node.token
 | 
| 1028 |                     val = lexer.TokenSliceLeft(t, 1)
 | 
| 1029 |                     assert len(val) == 1, val
 | 
| 1030 |                     if val != '\n':
 | 
| 1031 |                         self.cursor.PrintUntil(t)
 | 
| 1032 |                         self.cursor.SkipPast(t)
 | 
| 1033 |                         self.f.write("'%s'" % val)
 | 
| 1034 | 
 | 
| 1035 |             elif case(word_part_e.Literal):
 | 
| 1036 |                 node = cast(Token, UP_node)
 | 
| 1037 |                 self.cursor.PrintIncluding(node)
 | 
| 1038 | 
 | 
| 1039 |             elif case(word_part_e.SingleQuoted):
 | 
| 1040 |                 node = cast(SingleQuoted, UP_node)
 | 
| 1041 | 
 | 
| 1042 |                 # TODO:
 | 
| 1043 |                 # '\n' is '\\n'
 | 
| 1044 |                 # $'\n' is '\n'
 | 
| 1045 |                 # TODO: Should print until right_spid
 | 
| 1046 |                 # left_spid, right_spid = node.spids
 | 
| 1047 |                 self.cursor.PrintUntil(node.right)
 | 
| 1048 | 
 | 
| 1049 |             elif case(word_part_e.DoubleQuoted):
 | 
| 1050 |                 node = cast(DoubleQuoted, UP_node)
 | 
| 1051 |                 for part in node.parts:
 | 
| 1052 |                     self.DoWordPart(part, local_symbols, quoted=True)
 | 
| 1053 | 
 | 
| 1054 |             elif case(word_part_e.SimpleVarSub):
 | 
| 1055 |                 node = cast(SimpleVarSub, UP_node)
 | 
| 1056 | 
 | 
| 1057 |                 op_id = node.tok.id
 | 
| 1058 | 
 | 
| 1059 |                 if op_id == Id.VSub_DollarName:
 | 
| 1060 |                     self.cursor.PrintIncluding(node.tok)
 | 
| 1061 | 
 | 
| 1062 |                 elif op_id == Id.VSub_Number:
 | 
| 1063 |                     self.cursor.PrintIncluding(node.tok)
 | 
| 1064 | 
 | 
| 1065 |                 elif op_id == Id.VSub_At:  # $@ -- handled quoted case above
 | 
| 1066 |                     self.f.write('$[join(ARGV)]')
 | 
| 1067 |                     self.cursor.SkipPast(node.tok)
 | 
| 1068 | 
 | 
| 1069 |                 elif op_id == Id.VSub_Star:  # $*
 | 
| 1070 |                     # PEDANTIC: Depends if quoted or unquoted
 | 
| 1071 |                     self.f.write('$[join(ARGV)]')
 | 
| 1072 |                     self.cursor.SkipPast(node.tok)
 | 
| 1073 | 
 | 
| 1074 |                 elif op_id == Id.VSub_Pound:  # $#
 | 
| 1075 |                     # len(ARGV) ?
 | 
| 1076 |                     self.f.write('$Argc')
 | 
| 1077 |                     self.cursor.SkipPast(node.tok)
 | 
| 1078 | 
 | 
| 1079 |                 else:
 | 
| 1080 |                     pass
 | 
| 1081 | 
 | 
| 1082 |             elif case(word_part_e.BracedVarSub):
 | 
| 1083 |                 node = cast(BracedVarSub, UP_node)
 | 
| 1084 | 
 | 
| 1085 |                 # NOTE: Why do we need this but we don't need it in command sub?
 | 
| 1086 |                 self.cursor.PrintUntil(node.left)
 | 
| 1087 | 
 | 
| 1088 |                 if node.bracket_op:
 | 
| 1089 |                     # a[1]
 | 
| 1090 |                     # These two change the sigil!  ${a[@]} is now @a!
 | 
| 1091 |                     # a[@]
 | 
| 1092 |                     # a[*]
 | 
| 1093 |                     pass
 | 
| 1094 | 
 | 
| 1095 |                 if node.prefix_op:
 | 
| 1096 |                     # len()
 | 
| 1097 |                     pass
 | 
| 1098 |                 if node.suffix_op:
 | 
| 1099 |                     pass
 | 
| 1100 | 
 | 
| 1101 |                 op_id = node.token.id
 | 
| 1102 |                 if op_id == Id.VSub_QMark:
 | 
| 1103 |                     self.cursor.PrintIncluding(node.token)
 | 
| 1104 | 
 | 
| 1105 |                 self.cursor.PrintIncluding(node.right)
 | 
| 1106 | 
 | 
| 1107 |             elif case(word_part_e.CommandSub):
 | 
| 1108 |                 node = cast(CommandSub, UP_node)
 | 
| 1109 | 
 | 
| 1110 |                 if node.left_token.id == Id.Left_Backtick:
 | 
| 1111 |                     self.cursor.PrintUntil(node.left_token)
 | 
| 1112 |                     self.f.write('$(')
 | 
| 1113 |                     self.cursor.SkipPast(node.left_token)
 | 
| 1114 | 
 | 
| 1115 |                     self.DoCommand(node.child, local_symbols)
 | 
| 1116 | 
 | 
| 1117 |                     # Skip over right `
 | 
| 1118 |                     self.cursor.SkipPast(node.right)
 | 
| 1119 |                     self.f.write(')')
 | 
| 1120 | 
 | 
| 1121 |                 else:
 | 
| 1122 |                     self.cursor.PrintIncluding(node.right)
 | 
| 1123 | 
 | 
| 1124 |             else:
 | 
| 1125 |                 pass
 |