1 | from __future__ import print_function
|
2 | """
|
3 | fix.py -- Do source transformations. Somewhat like 'go fix'.
|
4 |
|
5 | TODO: Change := to =, and var/const/set
|
6 | """
|
7 |
|
8 | import sys
|
9 |
|
10 | from asdl import const
|
11 | from core import util
|
12 | from core import word
|
13 | from osh.meta import ast, Id
|
14 |
|
15 | from _devbuild.gen import runtime_asdl
|
16 |
|
17 | word_style_e = runtime_asdl.word_style_e
|
18 |
|
19 | log = util.log
|
20 |
|
21 | command_e = ast.command_e
|
22 | redir_e = ast.redir_e
|
23 | word_e = ast.word_e
|
24 | word_part_e = ast.word_part_e
|
25 | arith_expr_e = ast.arith_expr_e
|
26 | bool_expr_e = ast.bool_expr_e
|
27 | lhs_expr_e = ast.lhs_expr_e
|
28 |
|
29 |
|
30 | class Cursor(object):
|
31 | """
|
32 | Wrapper for printing/transforming a complete source file stored in a single
|
33 | arena.
|
34 | """
|
35 | def __init__(self, arena, f):
|
36 | self.arena = arena
|
37 | self.f = f
|
38 | self.next_span_id = 0
|
39 |
|
40 | def PrintUntil(self, until_span_id):
|
41 | # Sometimes we add +1
|
42 | assert until_span_id < const.NO_INTEGER, 'Missing span ID, got %d' % until_span_id
|
43 | #log('PrintUntil %d', until_span_id)
|
44 | for span_id in range(self.next_span_id, until_span_id):
|
45 | #log('Looking up span id %d', span_id)
|
46 | span = self.arena.GetLineSpan(span_id)
|
47 | #log('SPAN %s', span)
|
48 |
|
49 | line = self.arena.GetLine(span.line_id)
|
50 | piece = line[span.col : span.col + span.length]
|
51 | self.f.write(piece)
|
52 | # Spacing
|
53 | #self.f.write('%r' % piece)
|
54 | #self.f.write('__')
|
55 |
|
56 | self.next_span_id = until_span_id
|
57 |
|
58 | def SkipUntil(self, next_span_id):
|
59 | """Skip everything before next_span_id.
|
60 | Printing will start at next_span_id
|
61 | """
|
62 | assert next_span_id != const.NO_INTEGER, next_span_id
|
63 | self.next_span_id = next_span_id
|
64 |
|
65 |
|
66 | def PrintAsOil(arena, node, debug_spans):
|
67 | #print node
|
68 | #print(spans)
|
69 | if debug_spans:
|
70 | for i, span in enumerate(arena.spans):
|
71 | line = arena.GetLine(span.line_id)
|
72 | piece = line[span.col : span.col + span.length]
|
73 | print('%5d %r' % (i, piece), file=sys.stderr)
|
74 | print('(%d spans)' % len(arena.spans), file=sys.stderr)
|
75 |
|
76 | cursor = Cursor(arena, sys.stdout)
|
77 | fixer = OilPrinter(cursor, arena, sys.stdout)
|
78 | fixer.DoCommand(node, None, at_top_level=True) # no local symbols yet
|
79 | fixer.End()
|
80 |
|
81 |
|
82 | # Cases:
|
83 | #
|
84 | # - Does it look like $foo?
|
85 | # - Pedantic mode, then:
|
86 | # x = @split(foo) No globbing here!
|
87 | # @split($1) or @1 ?
|
88 | # @-foo @-1 in expression mode
|
89 | # And then for command mode, you will have *@1 and *@foo. Split first
|
90 | # then glob.
|
91 | #
|
92 | # - Nice mode, then foo
|
93 | # --assume no-word-splitting
|
94 | # - Does it look like $(( 1 + 2 )) ? or $(echo hi)
|
95 | # pedantic mode: $(1 + 2) or @[echo hi] ?
|
96 | # nice mode: $(1 + 2) or $[echo hi]
|
97 | #
|
98 | # - Does it look like "$foo" or "${foo:-}"? Then it's just x = foo
|
99 | # x = foo or 'default'
|
100 | # - Does it contain any substitutions? Then whole thing is double quoted
|
101 | # - Otherwise single quoted
|
102 | #
|
103 | # PROBLEM: ~ substitution. That is disabled by "".
|
104 | # You can turn it into $HOME I guess
|
105 | # const foo = $HOME/hello
|
106 | # const foo = $~/bar # hm I kind of don't like this but OK
|
107 | # const foo = "$~/bar"
|
108 | # const foo = [ ~/bar ][0] # does this make sense?
|
109 | # const foo = `~/bar`
|
110 |
|
111 | # I think ~ should be like $ -- special. Maybe even inside double quotes?
|
112 | # Or only at the front?
|
113 |
|
114 |
|
115 | # QEFS is wrong? Because RHS never gets split! It can always be foo=$1/foo.
|
116 | # Not used because RHS not split:
|
117 | # $x -> @-x and ${x} -> @-x
|
118 | # ${x:-default} -> @-(x or 'default')
|
119 |
|
120 | def _GetRhsStyle(w):
|
121 | # NOTE: Pattern matching style would be a lot nicer for this...
|
122 |
|
123 | # Arith and command sub both retain $() and $[], so they are not pure
|
124 | # "expressions".
|
125 | VAR_SUBS = (word_part_e.SimpleVarSub, word_part_e.BracedVarSub,
|
126 | word_part_e.TildeSubPart)
|
127 | OTHER_SUBS = (word_part_e.CommandSubPart, word_part_e.ArithSubPart)
|
128 |
|
129 | ALL_SUBS = VAR_SUBS + OTHER_SUBS
|
130 |
|
131 | # Actually splitting NEVER HAPPENS ON ASSIGNMENT. LEAVE IT OFF.
|
132 |
|
133 | if len(w.parts) == 0:
|
134 | raise AssertionError(w)
|
135 |
|
136 | elif len(w.parts) == 1:
|
137 | part0 = w.parts[0]
|
138 | if part0.tag in VAR_SUBS:
|
139 | # $x -> x and ${x} -> x and ${x:-default} -> x or 'default'
|
140 | # ~ -> homedir()
|
141 | # ~andy -> homedir('andy')
|
142 | # tilde()
|
143 | # tilde('andy') ?
|
144 | return word_style_e.Expr
|
145 | elif part0.tag in OTHER_SUBS:
|
146 | return word_style_e.Unquoted
|
147 |
|
148 | elif part0.tag == word_part_e.DoubleQuotedPart:
|
149 | if len(part0.parts) == 1:
|
150 | dq_part0 = part0.parts[0]
|
151 | # "$x" -> x and "${x}" -> x and "${x:-default}" -> x or 'default'
|
152 | if dq_part0.tag in VAR_SUBS:
|
153 | return word_style_e.Expr
|
154 | elif dq_part0.tag in OTHER_SUBS:
|
155 | return word_style_e.Unquoted
|
156 |
|
157 | # Tilde subs also cause double quoted style.
|
158 | for part in w.parts:
|
159 | if part.tag == word_part_e.DoubleQuotedPart:
|
160 | for dq_part in part.parts:
|
161 | if dq_part.tag in ALL_SUBS:
|
162 | return word_style_e.DQ
|
163 | elif part.tag in ALL_SUBS:
|
164 | return word_style_e.DQ
|
165 |
|
166 | return word_style_e.SQ
|
167 |
|
168 |
|
169 | # TODO: Change to --assume, and have a default for each one?
|
170 | #
|
171 | # NICE mode: Assume that the user isn't relying on word splitting. A lot of
|
172 | # users want this!
|
173 | #
|
174 | # Problem cases:
|
175 | #
|
176 | # for name in $(find ...); do echo $name; done
|
177 | #
|
178 | # This doesn't split. Heuristic:
|
179 | #
|
180 | # This should be a bunch of flags:
|
181 | #
|
182 | # --assume 'no-word-splitting no-undefined' etc.
|
183 | # globals-defined-first-outside-func (then we can generated := vs. ::=)
|
184 | # --split-output-from-commands 'find ls' # tokenize these
|
185 |
|
186 | # Special case: "find" is assumed to produce multiple things that you will want
|
187 | # to split? But that doesn't go within function calls. Hm.
|
188 | #
|
189 | # $(find -type f) -> @[find -type f]
|
190 |
|
191 | NICE = 0
|
192 |
|
193 | # Try to convert with pedantic correctness. Not sure if users will want this
|
194 | # though. Most people are not super principled about their shell programs.
|
195 | # But experts might want it. Experts might want to run ShellCheck first and
|
196 | # quote everything, and then everything will be unquoted.
|
197 | #
|
198 | # "$foo" "${foo}" -> $foo $foo
|
199 | # $foo -> @-foo -> split then glob?
|
200 | # *@foo maybe
|
201 | # $(find -type f) -> @[find -type f]
|
202 |
|
203 | PEDANTIC = 1
|
204 |
|
205 |
|
206 | class OilPrinter(object):
|
207 | """
|
208 | Convert osh code to oil.
|
209 |
|
210 | - command invocations
|
211 | - find invocations
|
212 | - xargs
|
213 | """
|
214 | def __init__(self, cursor, arena, f, mode=NICE):
|
215 | self.cursor = cursor
|
216 | self.arena = arena
|
217 | self.f = f
|
218 | # In PEDANTIC mode, we translate unquoted $foo to @-foo, which means it will
|
219 | # be split and globbed?
|
220 | self.mode = mode
|
221 |
|
222 | def _DebugSpid(self, spid):
|
223 | span = self.arena.GetLineSpan(spid)
|
224 | line = self.arena.GetLine(span.line_id)
|
225 | # TODO: This should be factored out
|
226 | s = line[span.col : span.col + span.length]
|
227 | print('SPID %d = %r' % (spid, s), file=sys.stderr)
|
228 |
|
229 | def End(self):
|
230 | """Make sure we print until the end of the file."""
|
231 | end_id = len(self.arena.spans)
|
232 | self.cursor.PrintUntil(end_id)
|
233 |
|
234 | def DoRedirect(self, node, local_symbols):
|
235 | #print(node, file=sys.stderr)
|
236 | self.cursor.PrintUntil(node.spids[0])
|
237 |
|
238 | # TODO:
|
239 | # - Do < and <& the same way.
|
240 | # - How to handle here docs and here docs?
|
241 | # - >> becomes >+ or >-, or maybe >>>
|
242 |
|
243 | if node.tag == redir_e.Redir:
|
244 | if node.fd == const.NO_INTEGER:
|
245 | if node.op_id == Id.Redir_Great:
|
246 | self.f.write('>') # Allow us to replace the operator
|
247 | self.cursor.SkipUntil(node.spids[0] + 1)
|
248 | elif node.op_id == Id.Redir_GreatAnd:
|
249 | self.f.write('> !') # Replace >& 2 with > !2
|
250 | spid = word.LeftMostSpanForWord(node.arg_word)
|
251 | self.cursor.SkipUntil(spid)
|
252 | #self.DoWordInCommand(node.arg_word)
|
253 |
|
254 | else:
|
255 | # NOTE: Spacing like !2>err.txt vs !2 > err.txt can be done in the
|
256 | # formatter.
|
257 | self.f.write('!%d ' % node.fd)
|
258 | if node.op_id == Id.Redir_Great:
|
259 | self.f.write('>')
|
260 | self.cursor.SkipUntil(node.spids[0] + 1)
|
261 | elif node.op_id == Id.Redir_GreatAnd:
|
262 | self.f.write('> !') # Replace 1>& 2 with !1 > !2
|
263 | spid = word.LeftMostSpanForWord(node.arg_word)
|
264 | self.cursor.SkipUntil(spid)
|
265 |
|
266 | self.DoWordInCommand(node.arg_word, local_symbols)
|
267 |
|
268 | elif node.tag == redir_e.HereDoc:
|
269 | # TODO:
|
270 | # If do_expansion, then """, else '''
|
271 | # HereDoc LST node needs spids for both opening and closing delimiter.
|
272 | raise NotImplementedError(node.__class__.__name__)
|
273 |
|
274 | else:
|
275 | raise AssertionError(node.__class__.__name__)
|
276 |
|
277 | # <<< 'here word'
|
278 | # << 'here word'
|
279 | #
|
280 | # 2> out.txt
|
281 | # !2 > out.txt
|
282 |
|
283 | # cat 1<< EOF
|
284 | # hello $name
|
285 | # EOF
|
286 | # cat !1 << """
|
287 | # hello $name
|
288 | # """
|
289 | #
|
290 | # cat << 'EOF'
|
291 | # no expansion
|
292 | # EOF
|
293 | # cat <<- 'EOF'
|
294 | # no expansion and indented
|
295 | #
|
296 | # cat << '''
|
297 | # no expansion
|
298 | # '''
|
299 | # cat << '''
|
300 | # no expansion and indented
|
301 | # '''
|
302 |
|
303 | # Warn about multiple here docs on a line.
|
304 | # As an obscure feature, allow
|
305 | # cat << \'ONE' << \"TWO"
|
306 | # 123
|
307 | # ONE
|
308 | # 234
|
309 | # TWO
|
310 | # The _ is an indicator that it's not a string to be piped in.
|
311 | pass
|
312 |
|
313 | def DoAssignment(self, node, at_top_level, local_symbols):
|
314 | """
|
315 | local_symbols:
|
316 | - Add every 'local' declaration to it
|
317 | - problem: what if you have local in an "if" ?
|
318 | - we could treat it like nested scope and see what happens? Do any
|
319 | programs have a problem with it?
|
320 | case/if/for/while/BraceGroup all define scopes or what?
|
321 | You don't want inconsistency of variables that could be defined at
|
322 | any point.
|
323 | - or maybe you only need it within "if / case" ? Well I guess
|
324 | for/while can break out of the loop and cause problems. A break is
|
325 | an "if".
|
326 |
|
327 | - for subsequent
|
328 | """
|
329 | # Change RHS to expression language. Bare words not allowed. foo -> 'foo'
|
330 |
|
331 | has_rhs = False # TODO: This is on a per-variable basis.
|
332 | # local foo -> var foo = ''
|
333 | # readonly foo -> setconst foo
|
334 | # export foo -> export foo
|
335 |
|
336 | # TODO:
|
337 | # - This depends on self.mode.
|
338 | # - And we also need the enclosing FuncDef node to analyze.
|
339 | # - or we need a symbol table for the current function. Forget about
|
340 | #
|
341 | # Oil keywords:
|
342 | # - global : scope qualifier
|
343 | # - var, const : mutability
|
344 | # - setconst, export : state mutation
|
345 | #
|
346 | # Operators:
|
347 | # = and :=
|
348 | #
|
349 | # NOTE: Bash also has "unset". Does anyone use it?
|
350 | # You can use "delete" like Python I guess. It's not the opposite of
|
351 | # set.
|
352 |
|
353 | # NOTE:
|
354 | # - We CAN tell if a variable has been defined locally.
|
355 | # - We CANNOT tell if it's been defined globally, because different files
|
356 | # share the same global namespace, and we can't statically figure out what
|
357 | # files are in the program.
|
358 | defined_locally = False # is it a local variable in this function?
|
359 | # can't tell if global
|
360 | # We can change it from = to := or ::= (in pedantic mode)
|
361 | new_assign_op_e = None
|
362 |
|
363 | if node.keyword == Id.Assign_Local:
|
364 | # Assume that 'local' it's a declaration. In osh, it's an error if
|
365 | # locals are redefined. In bash, it's OK to do 'local f=1; local f=2'.
|
366 | # Could have a flag if enough people do this.
|
367 | if at_top_level:
|
368 | raise RuntimeError('local at top level is invalid')
|
369 |
|
370 | if defined_locally:
|
371 | raise RuntimeError("Can't redefine local")
|
372 |
|
373 | keyword_spid = node.spids[0]
|
374 | self.cursor.PrintUntil(keyword_spid)
|
375 | self.cursor.SkipUntil(keyword_spid + 1)
|
376 | self.f.write('var')
|
377 |
|
378 | if local_symbols is not None:
|
379 | for pair in node.pairs:
|
380 | # NOTE: Not handling local a[b]=c
|
381 | if pair.lhs.tag == lhs_expr_e.LhsName:
|
382 | #print("REGISTERED %s" % pair.lhs.name)
|
383 | local_symbols[pair.lhs.name] = True
|
384 |
|
385 | elif node.keyword == Id.Assign_None:
|
386 | self.cursor.PrintUntil(node.spids[0])
|
387 |
|
388 | # For now, just detect whether the FIRST assignment on the line has been
|
389 | # declared locally. We might want to split every line into separate
|
390 | # statements.
|
391 | if local_symbols is not None:
|
392 | lhs0 = node.pairs[0].lhs
|
393 | if lhs0.tag == lhs_expr_e.LhsName and lhs0.name in local_symbols:
|
394 | defined_locally = True
|
395 | #print("CHECKING NAME", lhs0.name, defined_locally, local_symbols)
|
396 |
|
397 | # need semantic analysis.
|
398 | # Would be nice to assume that it's a local though.
|
399 | if at_top_level:
|
400 | self.f.write('global ') # can't be redefined
|
401 | new_assign_op_e = '::='
|
402 | #self.f.write('global TODO := TODO') # mutate global or define it
|
403 | elif defined_locally:
|
404 | new_assign_op_e = ':=' # assume mutation of local
|
405 | #self.f.write('[local mutated]')
|
406 | else:
|
407 | # we're in a function, but it's not defined locally.
|
408 | self.f.write('global ') # assume mutation of local
|
409 | if self.mode == PEDANTIC: # assume globals defined
|
410 | new_assign_op_e = '::='
|
411 | else:
|
412 | new_assign_op_e = ':='
|
413 |
|
414 | elif node.keyword == Id.Assign_Readonly:
|
415 | # Explicit const. Assume it can't be redefined.
|
416 | # Verb.
|
417 | #
|
418 | # Top level;
|
419 | # readonly FOO=bar -> const FOO = 'bar'
|
420 | # readonly FOO -> freeze FOO
|
421 | # function level:
|
422 | # readonly FOO=bar -> const global FOO ::= 'bar'
|
423 | # readonly FOO -> freeze FOO
|
424 | keyword_spid = node.spids[0]
|
425 | if at_top_level:
|
426 | self.cursor.PrintUntil(keyword_spid)
|
427 | self.cursor.SkipUntil(keyword_spid + 1)
|
428 | self.f.write('const') # can't be redefined
|
429 | elif defined_locally:
|
430 | self.f.write('setconst FOO = "bar"')
|
431 | else:
|
432 | self.f.write('setconst global FOO = "bar"')
|
433 |
|
434 | elif node.keyword == Id.Assign_Declare:
|
435 | # declare -rx foo spam=eggs
|
436 | # export foo
|
437 | # setconst foo
|
438 | #
|
439 | # spam = eggs
|
440 | # export spam
|
441 |
|
442 | # Have to parse the flags
|
443 | self.f.write('TODO ')
|
444 |
|
445 | # foo=bar spam=eggs -> foo = 'bar', spam = 'eggs'
|
446 | n = len(node.pairs)
|
447 | for i, pair in enumerate(node.pairs):
|
448 | assert pair.lhs.tag == lhs_expr_e.LhsName
|
449 |
|
450 | left_spid = pair.spids[0]
|
451 | self.cursor.PrintUntil(left_spid)
|
452 | # Assume skipping over one Lit_VarLike token
|
453 | self.cursor.SkipUntil(left_spid + 1)
|
454 |
|
455 | # Replace name. I guess it's Lit_Chars.
|
456 | self.f.write(pair.lhs.name)
|
457 | op = new_assign_op_e if new_assign_op_e else '='
|
458 | self.f.write(' %s ' % op)
|
459 |
|
460 | # foo=bar -> foo = 'bar'
|
461 | #print('RHS', pair.rhs, file=sys.stderr)
|
462 | if pair.rhs is None:
|
463 | self.f.write("''") # local i -> var i = ''
|
464 | else:
|
465 | self.DoWordAsExpr(pair.rhs, local_symbols)
|
466 |
|
467 | if i != n - 1:
|
468 | self.f.write(',')
|
469 |
|
470 | def DoCommand(self, node, local_symbols, at_top_level=False):
|
471 | if node.tag == command_e.CommandList:
|
472 | # TODO: How to distinguish between echo hi; echo bye; and on separate
|
473 | # lines
|
474 | for child in node.children:
|
475 | self.DoCommand(child, local_symbols)
|
476 |
|
477 | elif node.tag == command_e.SimpleCommand:
|
478 | # How to preserve spaces between words? Do you want to do it?
|
479 | # Well you need to test this:
|
480 | #
|
481 | # echo foo \
|
482 | # bar
|
483 |
|
484 | # TODO: Need to print until the left most part of the phrase? the phrase
|
485 | # is a word, binding, redirect.
|
486 | #self.cursor.PrintUntil()
|
487 |
|
488 | if node.more_env:
|
489 | (left_spid,) = node.more_env[0].spids
|
490 | self.cursor.PrintUntil(left_spid)
|
491 | self.f.write('env ')
|
492 |
|
493 | # We only need to transform the right side, not left side.
|
494 | for pair in node.more_env:
|
495 | self.DoWordInCommand(pair.val, local_symbols)
|
496 |
|
497 | # More translations:
|
498 | # - . to source
|
499 | # - eval to sh-eval
|
500 |
|
501 | if node.words:
|
502 | first_word = node.words[0]
|
503 | ok, val, quoted = word.StaticEval(first_word)
|
504 | word0_spid = word.LeftMostSpanForWord(first_word)
|
505 | if ok and not quoted:
|
506 | if val == '[':
|
507 | last_word = node.words[-1]
|
508 | # Check if last word is ]
|
509 | ok, val, quoted = word.StaticEval(last_word)
|
510 | if ok and not quoted and val == ']':
|
511 | # Replace [ with 'test'
|
512 | self.cursor.PrintUntil(word0_spid)
|
513 | self.cursor.SkipUntil(word0_spid + 1)
|
514 | self.f.write('test')
|
515 |
|
516 | for w in node.words[1:-1]:
|
517 | self.DoWordInCommand(w, local_symbols)
|
518 |
|
519 | # Now omit ]
|
520 | last_spid = word.LeftMostSpanForWord(last_word)
|
521 | self.cursor.PrintUntil(last_spid - 1) # Get the space before
|
522 | self.cursor.SkipUntil(last_spid + 1) # ] takes one spid
|
523 | return
|
524 | else:
|
525 | raise RuntimeError('Got [ without ]')
|
526 |
|
527 | elif val == '.':
|
528 | self.cursor.PrintUntil(word0_spid)
|
529 | self.cursor.SkipUntil(word0_spid + 1)
|
530 | self.f.write('source')
|
531 | return
|
532 |
|
533 | for w in node.words:
|
534 | self.DoWordInCommand(w, local_symbols)
|
535 |
|
536 | # NOTE: This will change to "phrase"? Word or redirect.
|
537 | for r in node.redirects:
|
538 | self.DoRedirect(r, local_symbols)
|
539 |
|
540 | # TODO: Print the terminator. Could be \n or ;
|
541 | # Need to print env like PYTHONPATH = 'foo' && ls
|
542 | # Need to print redirects:
|
543 | # < > are the same. << is here string, and >> is assignment.
|
544 | # append is >+
|
545 |
|
546 | # TODO: static_eval of simple command
|
547 | # - [ -> "test". Eliminate trailing ].
|
548 | # - . -> source, etc.
|
549 |
|
550 | elif node.tag == command_e.Assignment:
|
551 | self.DoAssignment(node, at_top_level, local_symbols)
|
552 |
|
553 | elif node.tag == command_e.Pipeline:
|
554 | # Obscure: |& turns into |- or |+ for stderr.
|
555 | # TODO:
|
556 | # if ! true; then -> if not true {
|
557 |
|
558 | # if ! echo | grep; then -> if not { echo | grep } {
|
559 | # }
|
560 | # not is like do {}, but it negates the return value I guess.
|
561 |
|
562 | for child in node.children:
|
563 | self.DoCommand(child, local_symbols)
|
564 |
|
565 | elif node.tag == command_e.AndOr:
|
566 | for child in node.children:
|
567 | self.DoCommand(child, local_symbols)
|
568 |
|
569 | elif node.tag == command_e.Sentence:
|
570 | # 'ls &' to 'fork ls'
|
571 | # Keep ; the same.
|
572 | self.DoCommand(node.child, local_symbols)
|
573 |
|
574 | # This has to be different in the function case.
|
575 | elif node.tag == command_e.BraceGroup:
|
576 | # { echo hi; } -> do { echo hi }
|
577 | # For now it might be OK to keep 'do { echo hi; }
|
578 | #left_spid, right_spid = node.spids
|
579 | (left_spid,) = node.spids
|
580 |
|
581 | self.cursor.PrintUntil(left_spid)
|
582 | self.cursor.SkipUntil(left_spid + 1)
|
583 | self.f.write('do {')
|
584 |
|
585 | for child in node.children:
|
586 | self.DoCommand(child, local_symbols)
|
587 |
|
588 | elif node.tag == command_e.Subshell:
|
589 | # (echo hi) -> shell echo hi
|
590 | # (echo hi; echo bye) -> shell {echo hi; echo bye}
|
591 |
|
592 | (left_spid, right_spid) = node.spids
|
593 |
|
594 | self.cursor.PrintUntil(left_spid)
|
595 | self.cursor.SkipUntil(left_spid + 1)
|
596 | self.f.write('shell {')
|
597 |
|
598 | self.DoCommand(node.child, local_symbols)
|
599 |
|
600 | #self._DebugSpid(right_spid)
|
601 | #self._DebugSpid(right_spid + 1)
|
602 |
|
603 | #print('RIGHT SPID', right_spid)
|
604 | self.cursor.PrintUntil(right_spid)
|
605 | self.cursor.SkipUntil(right_spid + 1)
|
606 | self.f.write('}')
|
607 |
|
608 | elif node.tag == command_e.DParen:
|
609 | # Just change (( )) to ( )
|
610 | # Test it with while loop
|
611 | self.DoArithExpr(node.child, local_symbols)
|
612 |
|
613 | elif node.tag == command_e.DBracket:
|
614 | # [[ 1 -eq 2 ]] to (1 == 2)
|
615 | self.DoBoolExpr(node.expr)
|
616 |
|
617 | elif node.tag == command_e.FuncDef:
|
618 | # TODO: skip name
|
619 | #self.f.write('proc %s' % node.name)
|
620 |
|
621 | # New symbol table for every function.
|
622 | new_local_symbols = {}
|
623 |
|
624 | # Should be the left most span, including 'function'
|
625 | self.cursor.PrintUntil(node.spids[0])
|
626 |
|
627 | self.f.write('proc ')
|
628 | self.f.write(node.name)
|
629 | self.cursor.SkipUntil(node.spids[1])
|
630 |
|
631 | if node.body.tag == command_e.BraceGroup:
|
632 | # Don't add "do" like a standalone brace group. Just use {}.
|
633 | for child in node.body.children:
|
634 | self.DoCommand(child, new_local_symbols)
|
635 | else:
|
636 | pass
|
637 | # Add {}.
|
638 | # proc foo {
|
639 | # shell {echo hi; echo bye}
|
640 | # }
|
641 | #self.DoCommand(node.body)
|
642 |
|
643 | elif node.tag == command_e.BraceGroup:
|
644 | for child in node.children:
|
645 | self.DoCommand(child, local_symbols)
|
646 |
|
647 | elif node.tag == command_e.DoGroup:
|
648 | do_spid, done_spid = node.spids
|
649 | self.cursor.PrintUntil(do_spid)
|
650 | self.cursor.SkipUntil(do_spid + 1)
|
651 | self.f.write('{')
|
652 |
|
653 | for child in node.children:
|
654 | self.DoCommand(child, local_symbols)
|
655 |
|
656 | self.cursor.PrintUntil(done_spid)
|
657 | self.cursor.SkipUntil(done_spid + 1)
|
658 | self.f.write('}')
|
659 |
|
660 | elif node.tag == command_e.ForEach:
|
661 | # Need to preserve spaces between words, because there can be line
|
662 | # wrapping.
|
663 | # for x in a b c \
|
664 | # d e f; do
|
665 |
|
666 | in_spid, semi_spid = node.spids
|
667 |
|
668 | if in_spid == const.NO_INTEGER:
|
669 | #self.cursor.PrintUntil() # 'for x' and then space
|
670 | self.f.write('for %s in @Argv ' % node.iter_name)
|
671 | self.cursor.SkipUntil(node.body.spids[0])
|
672 | else:
|
673 | self.cursor.PrintUntil(in_spid + 1) # 'for x in' and then space
|
674 | self.f.write('[')
|
675 | for w in node.iter_words:
|
676 | self.DoWordInCommand(w, local_symbols)
|
677 | self.f.write(']')
|
678 | #print("SKIPPING SEMI %d" % semi_spid, file=sys.stderr)
|
679 |
|
680 | if semi_spid != const.NO_INTEGER:
|
681 | self.cursor.PrintUntil(semi_spid)
|
682 | self.cursor.SkipUntil(semi_spid + 1)
|
683 |
|
684 | self.DoCommand(node.body, local_symbols)
|
685 |
|
686 | elif node.tag == command_e.ForExpr:
|
687 | # Change (( )) to ( ), and then _FixDoGroup
|
688 | pass
|
689 |
|
690 | elif node.tag == command_e.While:
|
691 | cond = node.cond
|
692 | if len(cond) == 1 and cond[0].tag == command_e.Sentence:
|
693 | spid = cond[0].terminator.span_id
|
694 | self.cursor.PrintUntil(spid)
|
695 | self.cursor.SkipUntil(spid + 1)
|
696 |
|
697 | self.DoCommand(node.body, local_symbols)
|
698 |
|
699 | elif node.tag == command_e.If:
|
700 | else_spid, fi_spid = node.spids
|
701 |
|
702 | # if foo; then -> if foo {
|
703 | # elif foo; then -> } elif foo {
|
704 | for arm in node.arms:
|
705 | elif_spid, then_spid = arm.spids
|
706 | if elif_spid != const.NO_INTEGER:
|
707 | self.cursor.PrintUntil(elif_spid)
|
708 | self.f.write('} ')
|
709 |
|
710 | cond = arm.cond
|
711 | if len(cond) == 1 and cond[0].tag == command_e.Sentence:
|
712 | sentence = cond[0]
|
713 | self.DoCommand(sentence, local_symbols)
|
714 |
|
715 | # Remove semi-colon
|
716 | semi_spid = sentence.terminator.span_id
|
717 | self.cursor.PrintUntil(semi_spid)
|
718 | self.cursor.SkipUntil(semi_spid + 1)
|
719 | else:
|
720 | for child in arm.cond:
|
721 | self.DoCommand(child, local_symbols)
|
722 |
|
723 | self.cursor.PrintUntil(then_spid)
|
724 | self.cursor.SkipUntil(then_spid + 1)
|
725 | self.f.write('{')
|
726 |
|
727 | for child in arm.action:
|
728 | self.DoCommand(child, local_symbols)
|
729 |
|
730 | # else -> } else {
|
731 | if node.else_action:
|
732 | self.cursor.PrintUntil(else_spid)
|
733 | self.f.write('} ')
|
734 | self.cursor.PrintUntil(else_spid + 1)
|
735 | self.f.write(' {')
|
736 |
|
737 | for child in node.else_action:
|
738 | self.DoCommand(child, local_symbols)
|
739 |
|
740 | # fi -> }
|
741 | self.cursor.PrintUntil(fi_spid)
|
742 | self.cursor.SkipUntil(fi_spid + 1)
|
743 | self.f.write('}')
|
744 |
|
745 | elif node.tag == command_e.Case:
|
746 | case_spid, in_spid, esac_spid = node.spids
|
747 | self.cursor.PrintUntil(case_spid)
|
748 | self.cursor.SkipUntil(case_spid + 1)
|
749 | self.f.write('matchstr')
|
750 |
|
751 | # Reformat "$1" to $1
|
752 | self.DoWordInCommand(node.to_match, local_symbols)
|
753 |
|
754 | self.cursor.PrintUntil(in_spid)
|
755 | self.cursor.SkipUntil(in_spid + 1)
|
756 | self.f.write('{') # matchstr $var {
|
757 |
|
758 | # each arm needs the ) and the ;; node to skip over?
|
759 | for arm in node.arms:
|
760 | left_spid, rparen_spid, dsemi_spid, last_spid = arm.spids
|
761 | #print(left_spid, rparen_spid, dsemi_spid)
|
762 |
|
763 | self.cursor.PrintUntil(left_spid)
|
764 | # Hm maybe keep | because it's semi-deprecated? You can use
|
765 | # reload|force-reload {
|
766 | # }
|
767 | # e/reload|force-reload/ {
|
768 | # }
|
769 | # / 'reload' or 'force-reload' / {
|
770 | # }
|
771 | #
|
772 | # Yeah it's the more abbreviated syntax.
|
773 |
|
774 | # change | to 'or'
|
775 | for pat in arm.pat_list:
|
776 | pass
|
777 |
|
778 | # Skip this
|
779 | self.cursor.PrintUntil(rparen_spid)
|
780 | self.cursor.SkipUntil(rparen_spid + 1)
|
781 | self.f.write(' {') # surround it with { }
|
782 |
|
783 | for child in arm.action:
|
784 | self.DoCommand(child, local_symbols)
|
785 |
|
786 | if dsemi_spid != const.NO_INTEGER:
|
787 | self.cursor.PrintUntil(dsemi_spid)
|
788 | self.cursor.SkipUntil(dsemi_spid + 1)
|
789 | # NOTE: indentation here will be off because ;; is likely indented
|
790 | # with body.
|
791 | self.f.write('}')
|
792 | elif last_spid != const.NO_INTEGER:
|
793 | self.cursor.PrintUntil(last_spid)
|
794 | # NOTE: Indentation is also off here. Arbitrarily put 4 spaces.
|
795 | self.f.write(' }\n')
|
796 | else:
|
797 | raise AssertionError(
|
798 | "Expected with dsemi_spid or last_spid in case arm")
|
799 |
|
800 | self.cursor.PrintUntil(esac_spid)
|
801 | self.cursor.SkipUntil(esac_spid + 1)
|
802 | self.f.write('}') # strmatch $var {
|
803 |
|
804 | elif node.tag == command_e.NoOp:
|
805 | pass
|
806 |
|
807 | elif node.tag == command_e.ControlFlow:
|
808 | # No change for break / return / continue
|
809 | pass
|
810 |
|
811 | elif node.tag == command_e.TimeBlock:
|
812 | self.DoCommand(node.pipeline, local_symbols)
|
813 |
|
814 | else:
|
815 | #log('Command not handled: %s', node)
|
816 | raise AssertionError(node.__class__.__name__)
|
817 |
|
818 | def DoWordAsExpr(self, node, local_symbols):
|
819 | style = _GetRhsStyle(node)
|
820 | if style == word_style_e.SQ:
|
821 | self.f.write("'")
|
822 | self.DoWordInCommand(node, local_symbols)
|
823 | self.f.write("'")
|
824 | elif style == word_style_e.DQ:
|
825 | self.f.write('"')
|
826 | self.DoWordInCommand(node, local_symbols)
|
827 | self.f.write('"')
|
828 | else:
|
829 | # "${foo:-default}" -> foo or 'default'
|
830 | # ${foo:-default} -> @split(foo or 'default')
|
831 | # @(foo or 'default') -- implicit split.
|
832 |
|
833 | if word.IsVarSub(node): # ${1} or "$1"
|
834 | # Do it in expression mode
|
835 | pass
|
836 | # NOTE: ArithSub with $(1 +2 ) is different than 1 + 2 because of
|
837 | # conversion to string.
|
838 |
|
839 | # For now, just stub it out
|
840 | self.DoWordInCommand(node, local_symbols)
|
841 |
|
842 | def DoWordInCommand(self, node, local_symbols):
|
843 | """
|
844 | New reserved symbols:
|
845 | echo == must be changed to echo '==' because = is a reserved symbol.
|
846 | echo @$foo -> echo "@$foo" because @ is reserved
|
847 |
|
848 | Problems:
|
849 | rm --verbose=true
|
850 | rm '--verbose=true' -- is this bad?
|
851 |
|
852 | Same with comma
|
853 | foo, bar = 1
|
854 |
|
855 | # I guess we can allow this
|
856 | ls --long foo,bar
|
857 |
|
858 | or force:
|
859 | (foo, bar) = 1
|
860 |
|
861 | Maybe we need a clever 'pre-lex'
|
862 | overwhelmingly the second char will be ' '
|
863 |
|
864 | foo/bar/foo.py
|
865 | foo.py
|
866 | ./hello
|
867 | foo_bar
|
868 | [a-zA-Z0-9] / - . _ -- filename chars
|
869 |
|
870 |
|
871 | first word:
|
872 | var, const, export, setconst, global
|
873 | func, proc, do, not, shell,
|
874 | maybe: time, coproc, etc.
|
875 |
|
876 | = -- generic expression, = 1+2
|
877 |
|
878 | non-filename char AFTER first word
|
879 | cmd:
|
880 | ' ' foo bar baz
|
881 | '\n' foo
|
882 | '<' foo < bar
|
883 | '>' foo > bar
|
884 | ! ls !2 > !1
|
885 | | who | wc -l
|
886 | |- who |- wc -l
|
887 |
|
888 | expr:
|
889 | = foo = bar
|
890 | , a, b = x
|
891 | [ a[x] = 1
|
892 | ( f(x) for( while( if(
|
893 |
|
894 | 1+2 -- I think this tries to run the command
|
895 | """
|
896 | # Are we getting rid of word joining? Or maybe keep it but discourage and
|
897 | # provide alternatives.
|
898 | #
|
899 | # You don't really have a problem with byte strings, those are b'foo', but
|
900 | # that's in expression mode, not command mode.
|
901 |
|
902 | # Problems:
|
903 | # - Tilde sub can't be quoted. ls ~/foo/"foo" are incompatible with the
|
904 | # rule.
|
905 | # - Globs can't be quoted. ls 'foo'*.py can't be ls "foo*.py" -- it means
|
906 | # something different.
|
907 | # Might need to finish more of the globber to figure this out.
|
908 |
|
909 | # What about here docs words? It's a double quoted part, but with
|
910 | # different formatting!
|
911 | if node.tag == word_e.CompoundWord:
|
912 |
|
913 | # UNQUOTE simple var subs
|
914 |
|
915 | # TODO: I think we have to print the beginning and the end?
|
916 |
|
917 | #left_spid = word.LeftMostSpanForWord(node)
|
918 | #right_spid = word.RightMostSpanForWord(node)
|
919 | #right_spid = -1
|
920 | #print('DoWordInCommand %s %s' % (left_spid, right_spid), file=sys.stderr)
|
921 |
|
922 | # Special case for "$@". Wow this needs pattern matching!
|
923 | # TODO:
|
924 | # "$foo" -> $foo
|
925 | # "${foo}" -> $foo
|
926 |
|
927 | if (len(node.parts) == 1 and
|
928 | node.parts[0].tag == word_part_e.DoubleQuotedPart):
|
929 | dq_part = node.parts[0]
|
930 |
|
931 | # TODO: Double quoted part needs left and right IDs
|
932 | left_spid, right_spid = dq_part.spids
|
933 | assert right_spid != const.NO_INTEGER, right_spid
|
934 |
|
935 | if len(dq_part.parts) == 1:
|
936 | part0 = dq_part.parts[0]
|
937 | if part0.tag == word_part_e.SimpleVarSub:
|
938 | vsub_part = dq_part.parts[0]
|
939 | if vsub_part.token.id == Id.VSub_At:
|
940 | # NOTE: This is off for double quoted part. Hack to subtract 1.
|
941 | self.cursor.PrintUntil(left_spid)
|
942 | self.cursor.SkipUntil(right_spid + 1) # " then $@ then "
|
943 | self.f.write('@Argv')
|
944 | return # Done replacing
|
945 |
|
946 | # "$1" -> $1, "$foo" -> $foo
|
947 | if vsub_part.token.id in (Id.VSub_Number, Id.VSub_Name):
|
948 | self.cursor.PrintUntil(left_spid)
|
949 | self.cursor.SkipUntil(right_spid + 1)
|
950 | self.f.write(vsub_part.token.val)
|
951 | return
|
952 |
|
953 | # Single arith sub, command sub, etc.
|
954 | # On the other hand, an unquoted one needs to turn into
|
955 | #
|
956 | # $(echo one two) -> @[echo one two]
|
957 | # `echo one two` -> @[echo one two]
|
958 | #
|
959 | # ${var:-'the default'} -> @$(var or 'the default')
|
960 | #
|
961 | # $((1 + 2)) -> $(1 + 2) -- this is OK unquoted
|
962 |
|
963 | elif part0.tag == word_part_e.BracedVarSub:
|
964 | # Skip over quote
|
965 | self.cursor.PrintUntil(left_spid)
|
966 | self.cursor.SkipUntil(left_spid + 1)
|
967 | self.DoWordPart(part0, local_symbols)
|
968 | self.cursor.SkipUntil(right_spid + 1)
|
969 | return
|
970 |
|
971 | elif part0.tag == word_part_e.CommandSubPart:
|
972 | self.cursor.PrintUntil(left_spid)
|
973 | self.cursor.SkipUntil(left_spid + 1)
|
974 | self.DoWordPart(part0, local_symbols)
|
975 | self.cursor.SkipUntil(right_spid + 1)
|
976 | return
|
977 |
|
978 | # It's None for here docs I think.
|
979 | #log("NODE %s", node)
|
980 | #if left_spid is not None and left_spid >= 0:
|
981 | #span = self.arena.GetLineSpan(span_id)
|
982 | #print(span)
|
983 |
|
984 | #self.cursor.PrintUntil(left_spid)
|
985 | #pass
|
986 |
|
987 | # TODO: 'foo'"bar" should be "foobar", etc.
|
988 | # If any part is double quoted, you can always double quote the whole
|
989 | # thing?
|
990 | for part in node.parts:
|
991 | self.DoWordPart(part, local_symbols)
|
992 |
|
993 | #if right_spid >= 0:
|
994 | #self.cursor.PrintUntil(right_spid)
|
995 | #pass
|
996 |
|
997 | else:
|
998 | raise AssertionError(node.__class__.__name__)
|
999 |
|
1000 | def DoWordPart(self, node, local_symbols, quoted=False):
|
1001 | span_id = word.LeftMostSpanForPart(node)
|
1002 | if span_id is not None and span_id != const.NO_INTEGER:
|
1003 | span = self.arena.GetLineSpan(span_id)
|
1004 | #print(span)
|
1005 |
|
1006 | self.cursor.PrintUntil(span_id)
|
1007 |
|
1008 | if node.tag == word_part_e.ArrayLiteralPart:
|
1009 | pass
|
1010 |
|
1011 | elif node.tag == word_part_e.EscapedLiteralPart:
|
1012 | if quoted:
|
1013 | pass
|
1014 | else:
|
1015 | # If unquoted \e, it should quoted instead. ' ' vs. \<invisible space>
|
1016 | # Hm is this necessary though? I think the only motivation is changing
|
1017 | # \{ and \( for macros. And ' ' to be readable/visible.
|
1018 | t = node.token
|
1019 | val = t.val[1:]
|
1020 | assert len(val) == 1, val
|
1021 | if val != '\n':
|
1022 | self.cursor.PrintUntil(t.span_id)
|
1023 | self.cursor.SkipUntil(t.span_id + 1)
|
1024 | self.f.write("'%s'" % val)
|
1025 |
|
1026 | elif node.tag == word_part_e.LiteralPart:
|
1027 | # Print it literally.
|
1028 | # TODO: We might want to do it all on the word level though. For
|
1029 | # example, foo"bar" becomes "foobar" in oil.
|
1030 | spid = node.token.span_id
|
1031 | if spid is None:
|
1032 | #raise RuntimeError('%s has no span_id' % node.token)
|
1033 | # TODO: Fix word.TildeDetect to construct proper tokens.
|
1034 | print('WARNING:%s has no span_id' % node.token, file=sys.stderr)
|
1035 | else:
|
1036 | self.cursor.PrintUntil(spid + 1)
|
1037 |
|
1038 | elif node.tag == word_part_e.TildeSubPart: # No change
|
1039 | pass
|
1040 |
|
1041 | elif node.tag == word_part_e.SingleQuotedPart:
|
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 | if node.tokens: # Empty string has no tokens
|
1048 | last_spid = node.tokens[-1].span_id
|
1049 | self.cursor.PrintUntil(last_spid + 1)
|
1050 |
|
1051 | elif node.tag == word_part_e.DoubleQuotedPart:
|
1052 | for part in node.parts:
|
1053 | self.DoWordPart(part, local_symbols, quoted=True)
|
1054 |
|
1055 | elif node.tag == word_part_e.SimpleVarSub:
|
1056 | spid = node.token.span_id
|
1057 | op_id = node.token.id
|
1058 |
|
1059 | if op_id == Id.VSub_Name:
|
1060 | self.cursor.PrintUntil(spid + 1)
|
1061 |
|
1062 | elif op_id == Id.VSub_Number:
|
1063 | self.cursor.PrintUntil(spid + 1)
|
1064 |
|
1065 | elif op_id == Id.VSub_Bang: # $!
|
1066 | self.f.write('$BgPid') # Job most recently placed in background
|
1067 | self.cursor.SkipUntil(spid + 1)
|
1068 |
|
1069 | elif op_id == Id.VSub_At: # $@
|
1070 | self.f.write('$ifsjoin(Argv)')
|
1071 | self.cursor.SkipUntil(spid + 1)
|
1072 |
|
1073 | elif op_id == Id.VSub_Pound: # $#
|
1074 | self.f.write('$Argc')
|
1075 | self.cursor.SkipUntil(spid + 1)
|
1076 |
|
1077 | elif op_id == Id.VSub_Dollar: # $$
|
1078 | self.f.write('$Pid')
|
1079 | self.cursor.SkipUntil(spid + 1)
|
1080 |
|
1081 | elif op_id == Id.VSub_Star: # $*
|
1082 | # PEDANTIC: Depends if quoted or unquoted
|
1083 | self.f.write('$ifsjoin(Argv)')
|
1084 | self.cursor.SkipUntil(spid + 1)
|
1085 |
|
1086 | elif op_id == Id.VSub_Hyphen: # $*
|
1087 | self.f.write('$Flags')
|
1088 | self.cursor.SkipUntil(spid + 1)
|
1089 |
|
1090 | elif op_id == Id.VSub_QMark: # $?
|
1091 | self.f.write('$Status')
|
1092 | self.cursor.SkipUntil(spid + 1)
|
1093 |
|
1094 | else:
|
1095 | raise AssertionError(op_id)
|
1096 |
|
1097 | elif node.tag == word_part_e.BracedVarSub:
|
1098 | left_spid, right_spid = node.spids
|
1099 |
|
1100 | # NOTE: Why do we need this but we don't need it in command sub?
|
1101 | self.cursor.PrintUntil(left_spid)
|
1102 |
|
1103 | name_spid = node.token.span_id
|
1104 | op_id = node.token.id
|
1105 |
|
1106 | parens_needed = True
|
1107 | if node.bracket_op:
|
1108 | # a[1]
|
1109 | # These two change the sigil! ${a[@]} is now @a!
|
1110 | # a[@]
|
1111 | # a[*]
|
1112 | pass
|
1113 |
|
1114 | if node.prefix_op:
|
1115 | # len()
|
1116 | pass
|
1117 | if node.suffix_op:
|
1118 | # foo.trimLeft()
|
1119 | # foo.trimGlobLeft()
|
1120 | # foo.trimGlobLeft(longest=True)
|
1121 | #
|
1122 | # python lstrip() does something different
|
1123 |
|
1124 | # a[1:1]
|
1125 |
|
1126 | # .replace()
|
1127 | # .replaceGlob()
|
1128 |
|
1129 | pass
|
1130 |
|
1131 | if op_id == Id.VSub_QMark:
|
1132 | self.cursor.PrintUntil(name_spid + 1)
|
1133 |
|
1134 | if parens_needed:
|
1135 | # Skip over left bracket and write our own.
|
1136 | self.f.write('$(')
|
1137 | self.cursor.SkipUntil(left_spid + 1)
|
1138 |
|
1139 | # Placeholder for now
|
1140 | self.cursor.PrintUntil(right_spid)
|
1141 |
|
1142 | # Skip over right bracket and write our own.
|
1143 | self.f.write(')')
|
1144 | else:
|
1145 | pass
|
1146 |
|
1147 | self.cursor.SkipUntil(right_spid + 1)
|
1148 |
|
1149 | elif node.tag == word_part_e.CommandSubPart:
|
1150 | left_spid, right_spid = node.spids
|
1151 |
|
1152 | #self.cursor.PrintUntil(left_spid)
|
1153 | self.f.write('$[')
|
1154 | self.cursor.SkipUntil(left_spid + 1)
|
1155 |
|
1156 | self.DoCommand(node.command_list, local_symbols)
|
1157 |
|
1158 | self.f.write(']')
|
1159 | self.cursor.SkipUntil(right_spid + 1)
|
1160 | # change to $[echo hi]
|
1161 |
|
1162 | elif node.tag == word_part_e.ArithSubPart:
|
1163 | left_spid, right_spid = node.spids
|
1164 |
|
1165 | # Skip over left bracket and write our own.
|
1166 | self.f.write('$(')
|
1167 | self.cursor.SkipUntil(left_spid + 1)
|
1168 |
|
1169 | # NOTE: This doesn't do anything yet.
|
1170 | self.DoArithExpr(node.anode, local_symbols)
|
1171 | # Placeholder for now
|
1172 | self.cursor.PrintUntil(right_spid - 1)
|
1173 |
|
1174 | # Skip over right bracket and write our own.
|
1175 | self.f.write(')')
|
1176 | self.cursor.SkipUntil(right_spid + 1)
|
1177 |
|
1178 | else:
|
1179 | raise AssertionError(node.__class__.__name__)
|
1180 |
|
1181 | def DoArithExpr(self, node, local_symbols):
|
1182 | if node.tag == arith_expr_e.ArithBinary:
|
1183 | # Maybe I should just write the left span and right span for each word?
|
1184 | #self.f.write(str(node.left))
|
1185 |
|
1186 | if node.op_id == Id.Arith_Plus:
|
1187 | # NOTE: Right isn't necessarily a word!
|
1188 | r_id = word.LeftMostSpanForWord(node.right.w)
|
1189 | #self.cursor.SkipUntil(r_id)
|
1190 | #self.f.write('PLUS')
|
1191 |
|
1192 | #self.f.write(str(node.right))
|
1193 | elif node.tag == arith_expr_e.ArithWord:
|
1194 | self.DoWordInCommand(node.w, local_symbols)
|
1195 |
|
1196 | else:
|
1197 | raise AssertionError(node.__class__.__name__)
|
1198 |
|
1199 | def DoBoolExpr(self, node):
|
1200 | # TODO: switch on node.tag
|
1201 | pass
|
1202 |
|
1203 | # WordPart?
|
1204 |
|
1205 | # array_item
|
1206 | #
|
1207 | # These get turned into expressions
|
1208 | #
|
1209 | # bracket_op
|
1210 | # suffix_op
|
1211 | # prefix_op
|