OILS / osh / word_eval.py View on Github | oils.pub

2663 lines, 1654 significant
1"""
2word_eval.py - Evaluator for the word language.
3"""
4
5from _devbuild.gen.id_kind_asdl import Id, Kind, Kind_str
6from _devbuild.gen.syntax_asdl import (
7 Token,
8 SimpleVarSub,
9 loc,
10 loc_t,
11 BracedVarSub,
12 CommandSub,
13 bracket_op,
14 bracket_op_e,
15 suffix_op,
16 suffix_op_e,
17 ShArrayLiteral,
18 SingleQuoted,
19 DoubleQuoted,
20 word_e,
21 word_t,
22 CompoundWord,
23 rhs_word,
24 rhs_word_e,
25 rhs_word_t,
26 word_part,
27 word_part_e,
28)
29from _devbuild.gen.runtime_asdl import (
30 part_value,
31 part_value_e,
32 part_value_t,
33 cmd_value,
34 cmd_value_e,
35 cmd_value_t,
36 error_code_e,
37 AssignArg,
38 a_index,
39 a_index_e,
40 VTestPlace,
41 VarSubState,
42 Piece,
43)
44from _devbuild.gen.option_asdl import option_i, builtin_i
45from _devbuild.gen.value_asdl import (
46 value,
47 value_e,
48 value_t,
49 sh_lvalue,
50 sh_lvalue_t,
51)
52from core import bash_impl
53from core import error
54from core import pyos
55from core import pyutil
56from core import state
57from display import ui
58from core import util
59from data_lang import j8
60from data_lang import j8_lite
61from core.error import e_die
62from frontend import consts
63from frontend import lexer
64from frontend import location
65from mycpp import mops
66from mycpp.mylib import log, tagswitch, NewDict
67from osh import braces
68from osh import glob_
69from osh import string_ops
70from osh import word_
71from ysh import expr_eval
72from ysh import val_ops
73
74from typing import Optional, Tuple, List, Dict, cast, TYPE_CHECKING
75
76if TYPE_CHECKING:
77 from _devbuild.gen.syntax_asdl import word_part_t
78 from _devbuild.gen.option_asdl import builtin_t
79 from core import optview
80 from core.state import Mem
81 from core.vm import _Executor
82 from osh.split import SplitContext
83 from osh import prompt
84 from osh import sh_expr_eval
85
86# Flags for _EvalWordToParts and _EvalWordPart (not all are used for both)
87QUOTED = 1 << 0
88IS_SUBST = 1 << 1
89
90EXTGLOB_FILES = 1 << 2 # allow @(cc) from file system?
91EXTGLOB_MATCH = 1 << 3 # allow @(cc) in pattern matching?
92EXTGLOB_NESTED = 1 << 4 # for @(one|!(two|three))
93
94# For EvalWordToString
95QUOTE_FNMATCH = 1 << 5
96QUOTE_ERE = 1 << 6
97
98# For compatibility, ${BASH_SOURCE} and ${BASH_SOURCE[@]} are both valid.
99# Ditto for ${FUNCNAME} and ${BASH_LINENO}.
100_STRING_AND_ARRAY = ['BASH_SOURCE', 'FUNCNAME', 'BASH_LINENO']
101
102
103def ShouldArrayDecay(var_name, exec_opts, is_plain_var_sub=True):
104 # type: (str, optview.Exec, bool) -> bool
105 """Return whether we should allow ${a} to mean ${a[0]}."""
106 return (not exec_opts.strict_array() or
107 is_plain_var_sub and var_name in _STRING_AND_ARRAY)
108
109
110def DecayArray(val):
111 # type: (value_t) -> value_t
112 """Resolve ${array} to ${array[0]}."""
113 if val.tag() in (value_e.BashArray, value_e.SparseArray):
114 if val.tag() == value_e.BashArray:
115 array_val = cast(value.BashArray, val)
116 s, error_code = bash_impl.BashArray_GetElement(array_val, 0)
117 elif val.tag() == value_e.SparseArray:
118 sparse_val = cast(value.SparseArray, val)
119 s, error_code = bash_impl.SparseArray_GetElement(
120 sparse_val, mops.ZERO)
121 else:
122 raise AssertionError(val.tag())
123
124 # Note: index 0 should never cause the out-of-bound index error.
125 assert error_code == error_code_e.OK
126
127 elif val.tag() == value_e.BashAssoc:
128 assoc_val = cast(value.BashAssoc, val)
129 s = bash_impl.BashAssoc_GetElement(assoc_val, '0')
130 else:
131 raise AssertionError(val.tag())
132
133 if s is None:
134 return value.Undef
135 else:
136 return value.Str(s)
137
138
139def _DetectMetaBuiltinStr(s):
140 # type: (str) -> bool
141 """
142 We need to detect all of these cases:
143
144 builtin local
145 command local
146 builtin builtin local
147 builtin command local
148
149 Fundamentally, assignment builtins have different WORD EVALUATION RULES
150 for a=$x (no word splitting), so it seems hard to do this in
151 meta_oils.Builtin() or meta_oils.Command()
152 """
153 return (consts.LookupNormalBuiltin(s)
154 in (builtin_i.builtin, builtin_i.command))
155
156
157def _DetectMetaBuiltin(val0):
158 # type: (part_value_t) -> bool
159 UP_val0 = val0
160 if val0.tag() == part_value_e.String:
161 val0 = cast(Piece, UP_val0)
162 if not val0.quoted:
163 return _DetectMetaBuiltinStr(val0.s)
164 return False
165
166
167def _SplitAssignArg(arg, blame_word):
168 # type: (str, CompoundWord) -> AssignArg
169 """Dynamically parse argument to declare, export, etc.
170
171 This is a fallback to the static parsing done below.
172 """
173 # Note: it would be better to cache regcomp(), but we don't have an API for
174 # that, and it probably isn't a bottleneck now
175 m = util.RegexSearch(consts.ASSIGN_ARG_RE, arg)
176 if m is None:
177 e_die("Assignment builtin expected NAME=value, got %r" % arg,
178 blame_word)
179
180 var_name = m[1]
181 # m[2] is used for grouping; ERE doesn't have non-capturing groups
182
183 op = m[3]
184 assert op is not None, op
185 if len(op): # declare NAME=
186 val = value.Str(m[4]) # type: Optional[value_t]
187 append = op[0] == '+'
188 else: # declare NAME
189 val = None # no operator
190 append = False
191
192 return AssignArg(var_name, val, append, blame_word)
193
194
195# NOTE: Could be done with util.BackslashEscape like glob_.GlobEscape().
196def _BackslashEscape(s):
197 # type: (str) -> str
198 """Double up backslashes.
199
200 Useful for strings about to be globbed and strings about to be IFS
201 escaped.
202 """
203 return s.replace('\\', '\\\\')
204
205
206def _ValueToPartValue(val, quoted, part_loc):
207 # type: (value_t, bool, word_part_t) -> part_value_t
208 """Helper for VarSub evaluation.
209
210 Called by _EvalBracedVarSub and _EvalWordPart for SimpleVarSub.
211 """
212 UP_val = val
213
214 with tagswitch(val) as case:
215 if case(value_e.Undef):
216 # This happens in the case of ${undef+foo}. We skipped _ProcessUndef,
217 # but we have to append to the empty string.
218 return Piece('', quoted, not quoted)
219
220 elif case(value_e.Str):
221 val = cast(value.Str, UP_val)
222 return Piece(val.s, quoted, not quoted)
223
224 elif case(value_e.BashArray):
225 val = cast(value.BashArray, UP_val)
226 return part_value.Array(bash_impl.BashArray_GetValues(val), quoted)
227
228 elif case(value_e.SparseArray):
229 val = cast(value.SparseArray, UP_val)
230 return part_value.Array(bash_impl.SparseArray_GetValues(val), quoted)
231
232 elif case(value_e.BashAssoc):
233 val = cast(value.BashAssoc, UP_val)
234 # bash behavior: splice values!
235 return part_value.Array(bash_impl.BashAssoc_GetValues(val), quoted)
236
237 # Cases added for YSH
238 # value_e.List is also here - we use val_ops.Stringify()s err message
239 elif case(value_e.Null, value_e.Bool, value_e.Int, value_e.Float,
240 value_e.Eggex, value_e.List):
241 s = val_ops.Stringify(val, loc.WordPart(part_loc), 'Word eval ')
242 return Piece(s, quoted, not quoted)
243
244 else:
245 raise error.TypeErr(val, "Can't substitute into word",
246 loc.WordPart(part_loc))
247
248 raise AssertionError('for -Wreturn-type in C++')
249
250
251def _MakeWordFrames(part_vals):
252 # type: (List[part_value_t]) -> List[List[Piece]]
253 """A word evaluates to a flat list of part_value (String or Array). frame
254 is a portion that results in zero or more args. It can never be joined.
255 This idea exists because of arrays like "$@" and "${a[@]}".
256
257 Example:
258
259 a=(1 '2 3' 4)
260 x=x
261 y=y
262
263 # This word
264 $x"${a[@]}"$y
265
266 # Results in Three frames:
267 [ ('x', False, True), ('1', True, False) ]
268 [ ('2 3', True, False) ]
269 [ ('4', True, False), ('y', False, True) ]
270
271 Note: A frame is a 3-tuple that's identical to Piece()? Maybe we
272 should make that top level type.
273
274 TODO:
275 - Instead of List[List[Piece]], where List[Piece] is a Frame
276 - Change this representation to
277 Frames = (List[Piece] pieces, List[int] break_indices)
278 # where break_indices are the end
279
280 Consider a common case like "$x" or "${x}" - I think this a lot more
281 efficient?
282
283 And then change _EvalWordFrame(pieces: List[Piece], start: int, end: int)
284 """
285 current = [] # type: List[Piece]
286 frames = [current]
287
288 for p in part_vals:
289 UP_p = p
290
291 with tagswitch(p) as case:
292 if case(part_value_e.String):
293 p = cast(Piece, UP_p)
294 current.append(p)
295
296 elif case(part_value_e.Array):
297 p = cast(part_value.Array, UP_p)
298
299 is_first = True
300 for s in p.strs:
301 if s is None:
302 continue # ignore undefined array entries
303
304 # Arrays parts are not quoted for $* and $@
305 piece = Piece(s, p.quoted, not p.quoted)
306 if is_first:
307 current.append(piece)
308 is_first = False
309 else:
310 current = [piece]
311 frames.append(current) # singleton frame
312
313 else:
314 raise AssertionError()
315
316 return frames
317
318
319# TODO: This could be _MakeWordFrames and then sep.join(). It's redundant.
320def _DecayPartValuesToString(part_vals, join_char):
321 # type: (List[part_value_t], str) -> str
322 # Decay ${a=x"$@"x} to string.
323 out = [] # type: List[str]
324 for p in part_vals:
325 UP_p = p
326 with tagswitch(p) as case:
327 if case(part_value_e.String):
328 p = cast(Piece, UP_p)
329 out.append(p.s)
330 elif case(part_value_e.Array):
331 p = cast(part_value.Array, UP_p)
332 # TODO: Eliminate double join for speed?
333 tmp = [s for s in p.strs if s is not None]
334 out.append(join_char.join(tmp))
335 else:
336 raise AssertionError()
337 return ''.join(out)
338
339
340def _PerformSlice(
341 val, # type: value_t
342 offset, # type: mops.BigInt
343 length, # type: int
344 has_length, # type: bool
345 part, # type: BracedVarSub
346 arg0_val, # type: value.Str
347):
348 # type: (...) -> value_t
349 UP_val = val
350 with tagswitch(val) as case:
351 if case(value_e.Str): # Slice UTF-8 characters in a string.
352 val = cast(value.Str, UP_val)
353 s = val.s
354 n = len(s)
355
356 begin = mops.BigTruncate(offset)
357 if begin < 0: # Compute offset with unicode
358 byte_begin = n
359 num_iters = -begin
360 for _ in xrange(num_iters):
361 byte_begin = string_ops.PreviousUtf8Char(s, byte_begin)
362 else:
363 byte_begin = string_ops.AdvanceUtf8Chars(s, begin, 0)
364
365 if has_length:
366 if length < 0: # Compute offset with unicode
367 # Confusing: this is a POSITION
368 byte_end = n
369 num_iters = -length
370 for _ in xrange(num_iters):
371 byte_end = string_ops.PreviousUtf8Char(s, byte_end)
372 else:
373 byte_end = string_ops.AdvanceUtf8Chars(
374 s, length, byte_begin)
375 else:
376 byte_end = len(s)
377
378 substr = s[byte_begin:byte_end]
379 result = value.Str(substr) # type: value_t
380
381 elif case(value_e.BashArray,
382 value_e.SparseArray): # Slice array entries.
383 # NOTE: This error is ALWAYS fatal in bash. It's inconsistent with
384 # strings.
385 if has_length and length < 0:
386 e_die("Array slice can't have negative length: %d" % length,
387 loc.WordPart(part))
388
389 if bash_impl.BigInt_Less(offset, mops.ZERO):
390 # ${@:-3} starts counts from the end
391 if val.tag() == value_e.BashArray:
392 val = cast(value.BashArray, UP_val)
393 array_length = mops.IntWiden(
394 bash_impl.BashArray_Length(val))
395 elif val.tag() == value_e.SparseArray:
396 val = cast(value.SparseArray, UP_val)
397 array_length = bash_impl.SparseArray_Length(val)
398 else:
399 raise AssertionError()
400
401 # The array length counts $0 for $@ and $*
402 if arg0_val is not None:
403 array_length = mops.Add(array_length, mops.ONE)
404
405 offset = mops.Add(offset, array_length)
406
407 if bash_impl.BigInt_Less(offset, mops.ZERO):
408 strs = [] # type: List[str]
409 else:
410 # Quirk: "offset" for positional arguments ($@ and $*) counts $0.
411 prepends_arg0 = False
412 if arg0_val is not None:
413 if bash_impl.BigInt_Greater(offset, mops.ZERO):
414 offset = mops.Sub(offset, mops.ONE)
415 elif not has_length or length >= 1:
416 prepends_arg0 = True
417 length = length - 1
418
419 if has_length and length == 0:
420 strs = []
421
422 elif val.tag() == value_e.BashArray:
423 val = cast(value.BashArray, UP_val)
424 orig = bash_impl.BashArray_GetValues(val)
425 n = len(orig)
426
427 strs = []
428 i = mops.BigTruncate(offset)
429 count = 0
430 while i < n:
431 if has_length and count == length: # length could be 0
432 break
433 s = orig[i]
434 if s is not None: # Unset elements don't count towards the length
435 strs.append(s)
436 count += 1
437 i += 1
438
439 elif val.tag() == value_e.SparseArray:
440 val = cast(value.SparseArray, UP_val)
441
442 # TODO: We may optimize this by finding the first index
443 # using the binary search. Furthermore, the sorting by
444 # SparseArray_GetKeys can be replaced with the heap sort so
445 # that we only extract the first LENGTH elements of the
446 # indices greater or equal to OFFSET.
447 i = 0
448 for index in bash_impl.SparseArray_GetKeys(val):
449 if bash_impl.BigInt_GreaterEq(index, offset):
450 break
451 i = i + 1
452
453 if has_length:
454 strs = bash_impl.SparseArray_GetValues(val)[i:i +
455 length]
456 else:
457 strs = bash_impl.SparseArray_GetValues(val)[i:]
458
459 else:
460 raise AssertionError()
461
462 if prepends_arg0:
463 new_list = [arg0_val.s]
464 new_list.extend(strs)
465 strs = new_list
466
467 result = value.BashArray(strs)
468
469 elif case(value_e.BashAssoc):
470 e_die("Can't slice associative arrays", loc.WordPart(part))
471
472 else:
473 raise error.TypeErr(val, 'Slice op expected Str or BashArray',
474 loc.WordPart(part))
475
476 return result
477
478
479class StringWordEvaluator(object):
480 """Interface used by ArithEvaluator / BoolEvaluator"""
481
482 def __init__(self):
483 # type: () -> None
484 """Empty constructor for mycpp."""
485 pass
486
487 def EvalWordToString(self, w, eval_flags=0):
488 # type: (word_t, int) -> value.Str
489 raise NotImplementedError()
490
491
492def _GetDollarHyphen(exec_opts):
493 # type: (optview.Exec) -> str
494 chars = [] # type: List[str]
495 if exec_opts.interactive():
496 chars.append('i')
497
498 if exec_opts.errexit():
499 chars.append('e')
500 if exec_opts.noglob():
501 chars.append('f')
502 if exec_opts.noexec():
503 chars.append('n')
504 if exec_opts.nounset():
505 chars.append('u')
506 # NO letter for pipefail?
507 if exec_opts.xtrace():
508 chars.append('x')
509 if exec_opts.noclobber():
510 chars.append('C')
511
512 # bash has:
513 # - c for sh -c, i for sh -i (mksh also has this)
514 # - h for hashing (mksh also has this)
515 # - B for brace expansion
516 return ''.join(chars)
517
518
519class TildeEvaluator(object):
520
521 def __init__(self, mem, exec_opts):
522 # type: (Mem, optview.Exec) -> None
523 self.mem = mem
524 self.exec_opts = exec_opts
525
526 def GetMyHomeDir(self):
527 # type: () -> Optional[str]
528 """Consult $HOME first, and then make a libc call.
529
530 Important: the libc call can FAIL, which is why we prefer $HOME. See issue
531 #1578.
532 """
533 # First look up the HOME var, ENV.HOME, ...
534 s = self.mem.env_config.Get('HOME')
535 if s is not None:
536 return s
537
538 # Then ask the OS. This is what bash does.
539 return pyos.GetMyHomeDir()
540
541 def Eval(self, part):
542 # type: (word_part.TildeSub) -> str
543 """Evaluates ~ and ~user, given a Lit_TildeLike token."""
544
545 if part.user_name is None:
546 result = self.GetMyHomeDir()
547 else:
548 result = pyos.GetHomeDir(part.user_name)
549
550 if result is None:
551 if self.exec_opts.strict_tilde():
552 e_die("Error expanding tilde (e.g. invalid user)", part.left)
553 else:
554 # Return ~ or ~user literally
555 result = '~'
556 if part.user_name is not None:
557 result = result + part.user_name # mycpp doesn't have +=
558
559 return result
560
561
562class AbstractWordEvaluator(StringWordEvaluator):
563 """Abstract base class for word evaluators.
564
565 Public entry points:
566 EvalWordToString EvalForPlugin EvalRhsWord
567 EvalWordSequence EvalWordSequence2
568 """
569
570 def __init__(
571 self,
572 mem, # type: state.Mem
573 exec_opts, # type: optview.Exec
574 mutable_opts, # type: state.MutableOpts
575 tilde_ev, # type: TildeEvaluator
576 splitter, # type: SplitContext
577 errfmt, # type: ui.ErrorFormatter
578 ):
579 # type: (...) -> None
580 self.arith_ev = None # type: sh_expr_eval.ArithEvaluator
581 self.expr_ev = None # type: expr_eval.ExprEvaluator
582 self.prompt_ev = None # type: prompt.Evaluator
583
584 self.unsafe_arith = None # type: sh_expr_eval.UnsafeArith
585
586 self.tilde_ev = tilde_ev
587
588 self.mem = mem # for $HOME, $1, etc.
589 self.exec_opts = exec_opts # for nounset
590 self.mutable_opts = mutable_opts # for _allow_command_sub
591 self.splitter = splitter
592 self.errfmt = errfmt
593
594 self.globber = glob_.Globber(exec_opts)
595
596 def CheckCircularDeps(self):
597 # type: () -> None
598 raise NotImplementedError()
599
600 def _EvalCommandSub(self, cs_part, quoted):
601 # type: (CommandSub, bool) -> part_value_t
602 """Abstract since it has a side effect."""
603 raise NotImplementedError()
604
605 def _EvalProcessSub(self, cs_part):
606 # type: (CommandSub) -> part_value_t
607 """Abstract since it has a side effect."""
608 raise NotImplementedError()
609
610 def _EvalVarNum(self, var_num):
611 # type: (int) -> value_t
612 assert var_num >= 0
613 return self.mem.GetArgNum(var_num)
614
615 def _EvalSpecialVar(self, op_id, quoted, vsub_state):
616 # type: (int, bool, VarSubState) -> value_t
617 """Evaluate $?
618
619 and so forth
620 """
621 # $@ is special -- it need to know whether it is in a double quoted
622 # context.
623 #
624 # - If it's $@ in a double quoted context, return an ARRAY.
625 # - If it's $@ in a normal context, return a STRING, which then will be
626 # subject to splitting.
627
628 if op_id in (Id.VSub_At, Id.VSub_Star):
629 argv = self.mem.GetArgv()
630 val = value.BashArray(argv) # type: value_t
631 if op_id == Id.VSub_At:
632 # "$@" evaluates to an array, $@ should be decayed
633 vsub_state.join_array = not quoted
634 else: # $* "$*" are both decayed
635 vsub_state.join_array = True
636
637 elif op_id == Id.VSub_Hyphen:
638 val = value.Str(_GetDollarHyphen(self.exec_opts))
639
640 else:
641 val = self.mem.GetSpecialVar(op_id)
642
643 return val
644
645 def _ApplyTestOp(
646 self,
647 val, # type: value_t
648 op, # type: suffix_op.Unary
649 quoted, # type: bool
650 part_vals, # type: Optional[List[part_value_t]]
651 vtest_place, # type: VTestPlace
652 blame_token, # type: Token
653 vsub_state, # type: VarSubState
654 ):
655 # type: (...) -> bool
656 """
657 Returns:
658 Whether part_vals was mutated
659
660 ${a:-} returns part_value[]
661 ${a:+} returns part_value[]
662 ${a:?error} returns error word?
663 ${a:=} returns part_value[] but also needs self.mem for side effects.
664
665 So I guess it should return part_value[], and then a flag for raising
666 an error, and then a flag for assigning it?
667 The original BracedVarSub will have the name.
668
669 Example of needing multiple part_value[]
670
671 echo X-${a:-'def'"ault"}-X
672
673 We return two part values from the BracedVarSub. Also consider:
674
675 echo ${a:-x"$@"x}
676 """
677 eval_flags = IS_SUBST
678 if quoted:
679 eval_flags |= QUOTED
680
681 tok = op.op
682 # NOTE: Splicing part_values is necessary because of code like
683 # ${undef:-'a b' c 'd # e'}. Each part_value can have a different
684 # do_glob/do_elide setting.
685 UP_val = val
686 with tagswitch(val) as case:
687 if case(value_e.Undef):
688 is_falsey = True
689
690 elif case(value_e.Str):
691 val = cast(value.Str, UP_val)
692 if tok.id in (Id.VTest_ColonHyphen, Id.VTest_ColonEquals,
693 Id.VTest_ColonQMark, Id.VTest_ColonPlus):
694 is_falsey = len(val.s) == 0
695 else:
696 is_falsey = False
697
698 elif case(value_e.BashArray, value_e.SparseArray,
699 value_e.BashAssoc):
700 if val.tag() == value_e.BashArray:
701 val = cast(value.BashArray, UP_val)
702 strs = bash_impl.BashArray_GetValues(val)
703 elif val.tag() == value_e.SparseArray:
704 val = cast(value.SparseArray, UP_val)
705 strs = bash_impl.SparseArray_GetValues(val)
706 elif val.tag() == value_e.BashAssoc:
707 val = cast(value.BashAssoc, UP_val)
708 strs = bash_impl.BashAssoc_GetValues(val)
709 else:
710 raise AssertionError()
711
712 if tok.id in (Id.VTest_ColonHyphen, Id.VTest_ColonEquals,
713 Id.VTest_ColonQMark, Id.VTest_ColonPlus):
714 # "$*" - the separator is the first character of IFS
715 # $* $@ "$@" - the separator is a space
716 if quoted and vsub_state.join_array:
717 sep_width = len(self.splitter.GetJoinChar())
718 else:
719 sep_width = 1
720
721 # We test whether the joined string will be empty. When
722 # the separator is empty, all the elements need to be
723 # empty. When the separator is non-empty, one element is
724 # allowed at most and needs to be an empty string if any.
725 if sep_width == 0:
726 is_falsey = True
727 for s in strs:
728 if len(s) != 0:
729 is_falsey = False
730 break
731 else:
732 is_falsey = len(strs) == 0 or (len(strs) == 1 and
733 len(strs[0]) == 0)
734 else:
735 # TODO: allow undefined
736 is_falsey = len(strs) == 0
737
738 else:
739 # value.Eggex, etc. are all false
740 is_falsey = False
741
742 if tok.id in (Id.VTest_ColonHyphen, Id.VTest_Hyphen):
743 if is_falsey:
744 self._EvalRhsWordToParts(op.arg_word, part_vals, eval_flags)
745 return True
746 else:
747 return False
748
749 # Inverse of the above.
750 elif tok.id in (Id.VTest_ColonPlus, Id.VTest_Plus):
751 if is_falsey:
752 return False
753 else:
754 self._EvalRhsWordToParts(op.arg_word, part_vals, eval_flags)
755 return True
756
757 # Splice and assign
758 elif tok.id in (Id.VTest_ColonEquals, Id.VTest_Equals):
759 if is_falsey:
760 # Collect new part vals.
761 assign_part_vals = [] # type: List[part_value_t]
762 self._EvalRhsWordToParts(op.arg_word, assign_part_vals,
763 eval_flags)
764 # Append them to out param AND return them.
765 part_vals.extend(assign_part_vals)
766
767 if vtest_place.name is None:
768 # TODO: error context
769 e_die("Can't assign to special variable")
770 else:
771 # NOTE: This decays arrays too! 'shopt -s strict_array' could
772 # avoid it.
773 rhs_str = _DecayPartValuesToString(
774 assign_part_vals, self.splitter.GetJoinChar())
775 if vtest_place.index is None: # using None when no index
776 lval = location.LName(
777 vtest_place.name) # type: sh_lvalue_t
778 else:
779 var_name = vtest_place.name
780 var_index = vtest_place.index
781 UP_var_index = var_index
782
783 with tagswitch(var_index) as case:
784 if case(a_index_e.Int):
785 var_index = cast(a_index.Int, UP_var_index)
786 lval = sh_lvalue.Indexed(
787 var_name, var_index.i, loc.Missing)
788 elif case(a_index_e.Str):
789 var_index = cast(a_index.Str, UP_var_index)
790 lval = sh_lvalue.Keyed(var_name, var_index.s,
791 loc.Missing)
792 else:
793 raise AssertionError()
794
795 state.OshLanguageSetValue(self.mem, lval,
796 value.Str(rhs_str))
797 return True
798
799 else:
800 return False
801
802 elif tok.id in (Id.VTest_ColonQMark, Id.VTest_QMark):
803 if is_falsey:
804 # The arg is the error message
805 error_part_vals = [] # type: List[part_value_t]
806 self._EvalRhsWordToParts(op.arg_word, error_part_vals,
807 eval_flags)
808 error_str = _DecayPartValuesToString(
809 error_part_vals, self.splitter.GetJoinChar())
810
811 #
812 # Display fancy/helpful error
813 #
814 if vtest_place.name is None:
815 var_name = '???'
816 else:
817 var_name = vtest_place.name
818
819 if 0:
820 # This hint is nice, but looks too noisy for now
821 op_str = lexer.LazyStr(tok)
822 if tok.id == Id.VTest_ColonQMark:
823 why = 'empty or unset'
824 else:
825 why = 'unset'
826
827 self.errfmt.Print_(
828 "Hint: operator %s means a variable can't be %s" %
829 (op_str, why), tok)
830
831 if val.tag() == value_e.Undef:
832 actual = 'unset'
833 else:
834 actual = 'empty'
835
836 if len(error_str):
837 suffix = ': %r' % error_str
838 else:
839 suffix = ''
840 e_die("Var %s is %s%s" % (var_name, actual, suffix),
841 blame_token)
842
843 else:
844 return False
845
846 else:
847 raise AssertionError(tok.id)
848
849 def _Count(self, val, token):
850 # type: (value_t, Token) -> int
851 """Returns the length of the value, for ${#var}"""
852 UP_val = val
853 with tagswitch(val) as case:
854 if case(value_e.Str):
855 val = cast(value.Str, UP_val)
856 # NOTE: Whether bash counts bytes or chars is affected by LANG
857 # environment variables.
858 # Should we respect that, or another way to select? set -o
859 # count-bytes?
860
861 # https://stackoverflow.com/questions/17368067/length-of-string-in-bash
862 try:
863 count = string_ops.CountUtf8Chars(val.s)
864 except error.Strict as e:
865 # Add this here so we don't have to add it so far down the stack.
866 # TODO: It's better to show BOTH this CODE an the actual DATA
867 # somehow.
868 e.location = token
869
870 if self.exec_opts.strict_word_eval():
871 raise
872 else:
873 # NOTE: Doesn't make the command exit with 1; it just returns a
874 # length of -1.
875 self.errfmt.PrettyPrintError(e, prefix='warning: ')
876 return -1
877
878 elif case(value_e.BashArray):
879 val = cast(value.BashArray, UP_val)
880 count = bash_impl.BashArray_Count(val)
881
882 elif case(value_e.BashAssoc):
883 val = cast(value.BashAssoc, UP_val)
884 count = bash_impl.BashAssoc_Count(val)
885
886 elif case(value_e.SparseArray):
887 val = cast(value.SparseArray, UP_val)
888 count = bash_impl.SparseArray_Count(val)
889
890 else:
891 raise error.TypeErr(
892 val, "Length op expected Str, BashArray, BashAssoc", token)
893
894 return count
895
896 def _Keys(self, val, token):
897 # type: (value_t, Token) -> value_t
898 """Return keys of a container, for ${!array[@]}"""
899
900 UP_val = val
901 with tagswitch(val) as case:
902 if case(value_e.BashArray):
903 val = cast(value.BashArray, UP_val)
904 indices = [str(i) for i in bash_impl.BashArray_GetKeys(val)]
905 return value.BashArray(indices)
906
907 elif case(value_e.SparseArray):
908 val = cast(value.SparseArray, UP_val)
909 indices = [
910 mops.ToStr(i) for i in bash_impl.SparseArray_GetKeys(val)
911 ]
912 return value.BashArray(indices)
913
914 elif case(value_e.BashAssoc):
915 val = cast(value.BashAssoc, UP_val)
916 assert val.d is not None # for MyPy, so it's not Optional[]
917
918 # BUG: Keys aren't ordered according to insertion!
919 keys = bash_impl.BashAssoc_GetKeys(val)
920 return value.BashArray(keys)
921
922 else:
923 raise error.TypeErr(
924 val,
925 'Keys op expected Str, BashArray, SparseArray, or BashAssoc',
926 token)
927
928 def _EvalVarRef(self, val, blame_tok, quoted, vsub_state, vtest_place):
929 # type: (value_t, Token, bool, VarSubState, VTestPlace) -> value_t
930 """Handles indirect expansion like ${!var} and ${!a[0]}.
931
932 Args:
933 blame_tok: 'foo' for ${!foo}
934 """
935 UP_val = val
936 with tagswitch(val) as case:
937 if case(value_e.Undef):
938 # bash-4.4 returned value.Undef here. bash-5.0 started to treat
939 # the variable name to be empty so that the indirection fails.
940 var_ref_str = ''
941
942 elif case(value_e.Str):
943 val = cast(value.Str, UP_val)
944 var_ref_str = val.s
945
946 elif case(value_e.BashArray): # caught earlier but OK
947 val = cast(value.BashArray, UP_val)
948 # When there are more than one element in the array, this
949 # produces a wrong variable name containing spaces.
950 var_ref_str = ' '.join(bash_impl.BashArray_GetValues(val))
951
952 elif case(value_e.SparseArray): # caught earlier but OK
953 val = cast(value.SparseArray, UP_val)
954 var_ref_str = ' '.join(bash_impl.SparseArray_GetValues(val))
955
956 elif case(value_e.BashAssoc): # caught earlier but OK
957 val = cast(value.BashAssoc, UP_val)
958 var_ref_str = ' '.join(bash_impl.BashAssoc_GetValues(val))
959
960 else:
961 raise error.TypeErr(
962 val,
963 'Var Ref op expected Str, BashArray, SparseArray, or BashAssoc',
964 blame_tok)
965
966 try:
967 bvs_part = self.unsafe_arith.ParseVarRef(var_ref_str, blame_tok)
968 except error.FatalRuntime as e:
969 raise error.VarSubFailure(e.msg, e.location)
970
971 return self._VarRefValue(bvs_part, quoted, vsub_state, vtest_place)
972
973 def _ApplyUnarySuffixOp(self, val, op):
974 # type: (value_t, suffix_op.Unary) -> value_t
975 assert val.tag() != value_e.Undef
976
977 op_kind = consts.GetKind(op.op.id)
978
979 if op_kind == Kind.VOp1:
980 # NOTE: glob syntax is supported in ^ ^^ , ,, ! As well as % %% # ##.
981 # Detect has_extglob so that DoUnarySuffixOp doesn't use the fast
982 # shortcut for constant strings.
983 arg_val, has_extglob = self.EvalWordToPattern(op.arg_word)
984 assert arg_val.tag() == value_e.Str
985
986 UP_val = val
987 with tagswitch(val) as case:
988 if case(value_e.Str):
989 val = cast(value.Str, UP_val)
990 s = string_ops.DoUnarySuffixOp(val.s, op.op, arg_val.s,
991 has_extglob)
992 #log('%r %r -> %r', val.s, arg_val.s, s)
993 new_val = value.Str(s) # type: value_t
994
995 elif case(value_e.BashArray, value_e.SparseArray,
996 value_e.BashAssoc):
997 # get values
998 if val.tag() == value_e.BashArray:
999 val = cast(value.BashArray, UP_val)
1000 values = bash_impl.BashArray_GetValues(val)
1001 elif val.tag() == value_e.SparseArray:
1002 val = cast(value.SparseArray, UP_val)
1003 values = bash_impl.SparseArray_GetValues(val)
1004 elif val.tag() == value_e.BashAssoc:
1005 val = cast(value.BashAssoc, UP_val)
1006 values = bash_impl.BashAssoc_GetValues(val)
1007 else:
1008 raise AssertionError()
1009
1010 # ${a[@]#prefix} is VECTORIZED on arrays. YSH should have this too.
1011 strs = [
1012 string_ops.DoUnarySuffixOp(s, op.op, arg_val.s,
1013 has_extglob) for s in values
1014 ]
1015 new_val = value.BashArray(strs)
1016
1017 else:
1018 raise error.TypeErr(
1019 val, 'Unary op expected Str, BashArray, BashAssoc',
1020 op.op)
1021
1022 else:
1023 raise AssertionError(Kind_str(op_kind))
1024
1025 return new_val
1026
1027 def _PatSub(self, val, op):
1028 # type: (value_t, suffix_op.PatSub) -> value_t
1029
1030 pat_val, has_extglob = self.EvalWordToPattern(op.pat)
1031 # Extended globs aren't supported because we only translate * ? etc. to
1032 # ERE. I don't think there's a straightforward translation from !(*.py) to
1033 # ERE! You would need an engine that supports negation? (Derivatives?)
1034 if has_extglob:
1035 e_die('extended globs not supported in ${x//GLOB/}', op.pat)
1036
1037 if op.replace:
1038 replace_val = self.EvalRhsWord(op.replace)
1039 # Can't have an array, so must be a string
1040 assert replace_val.tag() == value_e.Str, replace_val
1041 replace_str = cast(value.Str, replace_val).s
1042 else:
1043 replace_str = ''
1044
1045 # note: doesn't support self.exec_opts.extglob()!
1046 regex, warnings = glob_.GlobToERE(pat_val.s)
1047 if len(warnings):
1048 # TODO:
1049 # - Add 'shopt -s strict_glob' mode and expose warnings.
1050 # "Glob is not in CANONICAL FORM".
1051 # - Propagate location info back to the 'op.pat' word.
1052 pass
1053 #log('regex %r', regex)
1054 replacer = string_ops.GlobReplacer(regex, replace_str, op.slash_tok)
1055
1056 with tagswitch(val) as case2:
1057 if case2(value_e.Str):
1058 str_val = cast(value.Str, val)
1059 s = replacer.Replace(str_val.s, op)
1060 val = value.Str(s)
1061
1062 elif case2(value_e.BashArray, value_e.SparseArray,
1063 value_e.BashAssoc):
1064 if val.tag() == value_e.BashArray:
1065 array_val = cast(value.BashArray, val)
1066 values = bash_impl.BashArray_GetValues(array_val)
1067 elif val.tag() == value_e.SparseArray:
1068 sparse_val = cast(value.SparseArray, val)
1069 values = bash_impl.SparseArray_GetValues(sparse_val)
1070 elif val.tag() == value_e.BashAssoc:
1071 assoc_val = cast(value.BashAssoc, val)
1072 values = bash_impl.BashAssoc_GetValues(assoc_val)
1073 else:
1074 raise AssertionError()
1075 strs = [replacer.Replace(s, op) for s in values]
1076 val = value.BashArray(strs)
1077
1078 else:
1079 raise error.TypeErr(
1080 val, 'Pat Sub op expected Str, BashArray, BashAssoc',
1081 op.slash_tok)
1082
1083 return val
1084
1085 def _Slice(self, val, op, var_name, part):
1086 # type: (value_t, suffix_op.Slice, Optional[str], BracedVarSub) -> value_t
1087
1088 begin = self.arith_ev.EvalToBigInt(op.begin)
1089
1090 # Note: bash allows lengths to be negative (with odd semantics), but
1091 # we don't allow that right now.
1092 has_length = False
1093 length = -1
1094 if op.length:
1095 has_length = True
1096 length = self.arith_ev.EvalToInt(op.length)
1097
1098 try:
1099 arg0_val = None # type: value.Str
1100 if var_name is None: # $* or $@
1101 arg0_val = self.mem.GetArg0()
1102 val = _PerformSlice(val, begin, length, has_length, part, arg0_val)
1103 except error.Strict as e:
1104 if self.exec_opts.strict_word_eval():
1105 raise
1106 else:
1107 self.errfmt.PrettyPrintError(e, prefix='warning: ')
1108 with tagswitch(val) as case2:
1109 if case2(value_e.Str):
1110 val = value.Str('')
1111 elif case2(value_e.BashArray):
1112 val = value.BashArray([])
1113 else:
1114 raise NotImplementedError()
1115 return val
1116
1117 def _Nullary(self, val, op, var_name, vsub_token, vsub_state):
1118 # type: (value_t, Token, Optional[str], Token, VarSubState) -> Tuple[value_t, bool]
1119
1120 quoted2 = False
1121 op_id = op.id
1122 if op_id == Id.VOp0_P:
1123 val = self._ProcessUndef(val, vsub_token, vsub_state)
1124 UP_val = val
1125 with tagswitch(val) as case:
1126 if case(value_e.Undef):
1127 result = value.Str('') # type: value_t
1128 elif case(value_e.Str):
1129 str_val = cast(value.Str, UP_val)
1130 prompt = self.prompt_ev.EvalPrompt(str_val.s)
1131 # readline gets rid of these, so we should too.
1132 p = prompt.replace('\x01', '').replace('\x02', '')
1133 result = value.Str(p)
1134 elif case(value_e.BashArray, value_e.SparseArray,
1135 value_e.BashAssoc):
1136 if val.tag() == value_e.BashArray:
1137 val = cast(value.BashArray, UP_val)
1138 values = [
1139 s for s in bash_impl.BashArray_GetValues(val)
1140 if s is not None
1141 ]
1142 elif val.tag() == value_e.SparseArray:
1143 val = cast(value.SparseArray, UP_val)
1144 values = bash_impl.SparseArray_GetValues(val)
1145 elif val.tag() == value_e.BashAssoc:
1146 val = cast(value.BashAssoc, UP_val)
1147 values = bash_impl.BashAssoc_GetValues(val)
1148 else:
1149 raise AssertionError()
1150
1151 tmp = [
1152 self.prompt_ev.EvalPrompt(s).replace(
1153 '\x01', '').replace('\x02', '') for s in values
1154 ]
1155 result = value.BashArray(tmp)
1156 else:
1157 e_die("Can't use @P on %s" % ui.ValType(val), op)
1158
1159 elif op_id == Id.VOp0_Q:
1160 UP_val = val
1161 with tagswitch(val) as case:
1162 if case(value_e.Undef):
1163 # We need to issue an error when "-o nounset" is enabled.
1164 # Although we do not need to check val for value_e.Undef,
1165 # we call _ProcessUndef for consistency in the error
1166 # message.
1167 self._ProcessUndef(val, vsub_token, vsub_state)
1168
1169 # For unset variables, we do not generate any quoted words.
1170 if vsub_state.array_ref is not None:
1171 result = value.BashArray([])
1172 else:
1173 result = value.Str('')
1174
1175 elif case(value_e.Str):
1176 str_val = cast(value.Str, UP_val)
1177 result = value.Str(j8_lite.MaybeShellEncode(str_val.s))
1178 # oddly, 'echo ${x@Q}' is equivalent to 'echo "${x@Q}"' in
1179 # bash
1180 quoted2 = True
1181 elif case(value_e.BashArray, value_e.SparseArray,
1182 value_e.BashAssoc):
1183 if val.tag() == value_e.BashArray:
1184 val = cast(value.BashArray, UP_val)
1185 values = [
1186 s for s in bash_impl.BashArray_GetValues(val)
1187 if s is not None
1188 ]
1189 elif val.tag() == value_e.SparseArray:
1190 val = cast(value.SparseArray, UP_val)
1191 values = bash_impl.SparseArray_GetValues(val)
1192 elif val.tag() == value_e.BashAssoc:
1193 val = cast(value.BashAssoc, UP_val)
1194 values = bash_impl.BashAssoc_GetValues(val)
1195 else:
1196 raise AssertionError()
1197
1198 tmp = [
1199 # TODO: should use fastfunc.ShellEncode
1200 j8_lite.MaybeShellEncode(s) for s in values
1201 ]
1202 result = value.BashArray(tmp)
1203 else:
1204 e_die("Can't use @Q on %s" % ui.ValType(val), op)
1205
1206 elif op_id == Id.VOp0_a:
1207 val = self._ProcessUndef(val, vsub_token, vsub_state)
1208 UP_val = val
1209 # We're ONLY simluating -a and -A, not -r -x -n for now. See
1210 # spec/ble-idioms.test.sh.
1211 chars = [] # type: List[str]
1212 with tagswitch(vsub_state.h_value) as case:
1213 if case(value_e.BashArray, value_e.SparseArray):
1214 chars.append('a')
1215 elif case(value_e.BashAssoc):
1216 chars.append('A')
1217
1218 if var_name is not None: # e.g. ${?@a} is allowed
1219 cell = self.mem.GetCell(var_name)
1220 if cell:
1221 if cell.readonly:
1222 chars.append('r')
1223 if cell.exported:
1224 chars.append('x')
1225 if cell.nameref:
1226 chars.append('n')
1227
1228 count = 1
1229 with tagswitch(val) as case:
1230 if case(value_e.Undef):
1231 count = 0
1232 elif case(value_e.BashArray):
1233 val = cast(value.BashArray, UP_val)
1234 count = bash_impl.BashArray_Count(val)
1235 elif case(value_e.SparseArray):
1236 val = cast(value.SparseArray, UP_val)
1237 count = bash_impl.SparseArray_Count(val)
1238 elif case(value_e.BashAssoc):
1239 val = cast(value.BashAssoc, UP_val)
1240 count = bash_impl.BashAssoc_Count(val)
1241
1242 result = value.BashArray([''.join(chars)] * count)
1243
1244 else:
1245 e_die('Var op %r not implemented' % lexer.TokenVal(op), op)
1246
1247 return result, quoted2
1248
1249 def _WholeArray(self, val, part, quoted, vsub_state):
1250 # type: (value_t, BracedVarSub, bool, VarSubState) -> value_t
1251 op_id = cast(bracket_op.WholeArray, part.bracket_op).op_id
1252
1253 if op_id == Id.Lit_At:
1254 op_str = '@'
1255 vsub_state.join_array = not quoted # ${a[@]} decays but "${a[@]}" doesn't
1256 elif op_id == Id.Arith_Star:
1257 op_str = '*'
1258 vsub_state.join_array = True # both ${a[*]} and "${a[*]}" decay
1259 else:
1260 raise AssertionError(op_id) # unknown
1261
1262 with tagswitch(val) as case2:
1263 if case2(value_e.Undef):
1264 # For an undefined array, we save the token of the array
1265 # reference for the later error message.
1266 vsub_state.array_ref = part.name_tok
1267 elif case2(value_e.Str):
1268 if self.exec_opts.strict_array():
1269 e_die("Can't index string with %s" % op_str,
1270 loc.WordPart(part))
1271 elif case2(value_e.BashArray, value_e.SparseArray,
1272 value_e.BashAssoc):
1273 pass # no-op
1274 else:
1275 # The other YSH types such as List, Dict, and Float are not
1276 # supported. Error messages will be printed later, so we here
1277 # return the unsupported objects without modification.
1278 pass # no-op
1279
1280 return val
1281
1282 def _ArrayIndex(self, val, part, vtest_place):
1283 # type: (value_t, BracedVarSub, VTestPlace) -> value_t
1284 """Process a numeric array index like ${a[i+1]}"""
1285 anode = cast(bracket_op.ArrayIndex, part.bracket_op).expr
1286
1287 UP_val = val
1288 with tagswitch(val) as case2:
1289 if case2(value_e.Undef):
1290 pass # it will be checked later
1291
1292 elif case2(value_e.Str):
1293 # Bash treats any string as an array, so we can't add our own
1294 # behavior here without making valid OSH invalid bash.
1295 e_die("Can't index string %r with integer" % part.var_name,
1296 part.name_tok)
1297
1298 elif case2(value_e.BashArray):
1299 array_val = cast(value.BashArray, UP_val)
1300 index = self.arith_ev.EvalToInt(anode)
1301 vtest_place.index = a_index.Int(index)
1302
1303 s, error_code = bash_impl.BashArray_GetElement(
1304 array_val, index)
1305 if error_code == error_code_e.IndexOutOfRange:
1306 # Note: Bash outputs warning but does not make it a real
1307 # error. We follow the Bash behavior here.
1308 self.errfmt.Print_(
1309 "Index %d out of bounds for array of length %d" %
1310 (index, bash_impl.BashArray_Length(array_val)),
1311 blame_loc=part.name_tok)
1312
1313 if s is None:
1314 val = value.Undef
1315 else:
1316 val = value.Str(s)
1317
1318 elif case2(value_e.SparseArray):
1319 sparse_val = cast(value.SparseArray, UP_val)
1320 big_index = self.arith_ev.EvalToBigInt(anode)
1321 vtest_place.index = a_index.Int(mops.BigTruncate(big_index))
1322
1323 s, error_code = bash_impl.SparseArray_GetElement(
1324 sparse_val, big_index)
1325 if error_code == error_code_e.IndexOutOfRange:
1326 # Note: Bash outputs warning but does not make it a real
1327 # error. We follow the Bash behavior here.
1328 big_length = bash_impl.SparseArray_Length(sparse_val)
1329 self.errfmt.Print_(
1330 "Index %s out of bounds for array of length %s" %
1331 (mops.ToStr(big_index), mops.ToStr(big_length)),
1332 blame_loc=part.name_tok)
1333
1334 if s is None:
1335 val = value.Undef
1336 else:
1337 val = value.Str(s)
1338
1339 elif case2(value_e.BashAssoc):
1340 assoc_val = cast(value.BashAssoc, UP_val)
1341 # Location could also be attached to bracket_op? But
1342 # arith_expr.VarSub works OK too
1343 key = self.arith_ev.EvalWordToString(
1344 anode, blame_loc=location.TokenForArith(anode))
1345
1346 vtest_place.index = a_index.Str(key) # out param
1347 s = bash_impl.BashAssoc_GetElement(assoc_val, key)
1348
1349 if s is None:
1350 val = value.Undef
1351 else:
1352 val = value.Str(s)
1353
1354 else:
1355 raise error.TypeErr(val,
1356 'Index op expected BashArray, BashAssoc',
1357 loc.WordPart(part))
1358
1359 return val
1360
1361 def _EvalDoubleQuoted(self, parts, part_vals):
1362 # type: (List[word_part_t], List[part_value_t]) -> None
1363 """Evaluate parts of a DoubleQuoted part.
1364
1365 Args:
1366 part_vals: output param to append to.
1367 """
1368 # Example of returning array:
1369 # $ a=(1 2); b=(3); $ c=(4 5)
1370 # $ argv "${a[@]}${b[@]}${c[@]}"
1371 # ['1', '234', '5']
1372 #
1373 # Example of multiple parts
1374 # $ argv "${a[@]}${undef[@]:-${c[@]}}"
1375 # ['1', '24', '5']
1376
1377 # Special case for "". The parser outputs (DoubleQuoted []), instead
1378 # of (DoubleQuoted [Literal '']). This is better but it means we
1379 # have to check for it.
1380 if len(parts) == 0:
1381 v = Piece('', True, False)
1382 part_vals.append(v)
1383 return
1384
1385 for p in parts:
1386 self._EvalWordPart(p, part_vals, QUOTED)
1387
1388 def EvalDoubleQuotedToString(self, dq_part):
1389 # type: (DoubleQuoted) -> str
1390 """For double quoted strings in YSH expressions.
1391
1392 Example: var x = "$foo-${foo}"
1393 """
1394 part_vals = [] # type: List[part_value_t]
1395 self._EvalDoubleQuoted(dq_part.parts, part_vals)
1396 return self._ConcatPartVals(part_vals, dq_part.left)
1397
1398 def _JoinArray(self, val, quoted, vsub_state):
1399 # type: (value_t, bool, VarSubState) -> value_t
1400 """Decay "$*" to a string."""
1401
1402 if quoted and vsub_state.join_array:
1403 sep = self.splitter.GetJoinChar()
1404 tmp = None # type: List[str]
1405
1406 UP_val = val
1407 with tagswitch(val) as case:
1408 if case(value_e.BashArray):
1409 val = cast(value.BashArray, UP_val)
1410 tmp = [
1411 s for s in bash_impl.BashArray_GetValues(val)
1412 if s is not None
1413 ]
1414 elif case(value_e.BashAssoc):
1415 val = cast(value.BashAssoc, UP_val)
1416 tmp = bash_impl.BashAssoc_GetValues(val)
1417
1418 if tmp is not None:
1419 return value.Str(sep.join(tmp))
1420
1421 return val
1422
1423 def _ProcessUndef(self, val, name_tok, vsub_state):
1424 # type: (value_t, Token, VarSubState) -> value_t
1425 assert name_tok is not None
1426
1427 if val.tag() != value_e.Undef:
1428 return val
1429
1430 if vsub_state.array_ref is not None:
1431 array_tok = vsub_state.array_ref
1432 if self.exec_opts.nounset():
1433 e_die('Undefined array %r' % lexer.TokenVal(array_tok),
1434 array_tok)
1435 else:
1436 return value.BashArray([])
1437 else:
1438 if self.exec_opts.nounset():
1439 tok_str = lexer.TokenVal(name_tok)
1440 name = tok_str[1:] if tok_str.startswith('$') else tok_str
1441 e_die('Undefined variable %r' % name, name_tok)
1442 else:
1443 return value.Str('')
1444
1445 def _EvalBracketOp(self, val, part, quoted, vsub_state, vtest_place):
1446 # type: (value_t, BracedVarSub, bool, VarSubState, VTestPlace) -> value_t
1447
1448 if part.bracket_op:
1449 with tagswitch(part.bracket_op) as case:
1450 if case(bracket_op_e.WholeArray):
1451 val = self._WholeArray(val, part, quoted, vsub_state)
1452
1453 elif case(bracket_op_e.ArrayIndex):
1454 val = self._ArrayIndex(val, part, vtest_place)
1455
1456 else:
1457 raise AssertionError(part.bracket_op.tag())
1458
1459 else: # no bracket op
1460 var_name = vtest_place.name
1461 if (var_name is not None and
1462 val.tag() in (value_e.BashArray, value_e.SparseArray,
1463 value_e.BashAssoc)):
1464 if ShouldArrayDecay(var_name, self.exec_opts,
1465 not (part.prefix_op or part.suffix_op)):
1466 # for ${BASH_SOURCE}, etc.
1467 val = DecayArray(val)
1468 else:
1469 e_die(
1470 "Array %r can't be referred to as a scalar (without @ or *)"
1471 % var_name, loc.WordPart(part))
1472
1473 return val
1474
1475 def _VarRefValue(self, part, quoted, vsub_state, vtest_place):
1476 # type: (BracedVarSub, bool, VarSubState, VTestPlace) -> value_t
1477 """Duplicates some logic from _EvalBracedVarSub, but returns a
1478 value_t."""
1479
1480 # 1. Evaluate from (var_name, var_num, token Id) -> value
1481 if part.name_tok.id == Id.VSub_Name:
1482 vtest_place.name = part.var_name
1483 val = self.mem.GetValue(part.var_name)
1484
1485 elif part.name_tok.id == Id.VSub_Number:
1486 var_num = int(part.var_name)
1487 val = self._EvalVarNum(var_num)
1488
1489 else:
1490 # $* decays
1491 val = self._EvalSpecialVar(part.name_tok.id, quoted, vsub_state)
1492
1493 # update h-value (i.e., the holder of the current value)
1494 vsub_state.h_value = val
1495
1496 # We don't need var_index because it's only for L-Values of test ops?
1497 if self.exec_opts.eval_unsafe_arith():
1498 val = self._EvalBracketOp(val, part, quoted, vsub_state,
1499 vtest_place)
1500 else:
1501 with state.ctx_Option(self.mutable_opts,
1502 [option_i._allow_command_sub], False):
1503 val = self._EvalBracketOp(val, part, quoted, vsub_state,
1504 vtest_place)
1505
1506 return val
1507
1508 def _EvalBracedVarSub(self, part, part_vals, quoted):
1509 # type: (BracedVarSub, List[part_value_t], bool) -> None
1510 """
1511 Args:
1512 part_vals: output param to append to.
1513 """
1514 # We have different operators that interact in a non-obvious order.
1515 #
1516 # 1. bracket_op: value -> value, with side effect on vsub_state
1517 #
1518 # 2. prefix_op
1519 # a. length ${#x}: value -> value
1520 # b. var ref ${!ref}: can expand to an array
1521 #
1522 # 3. suffix_op:
1523 # a. no operator: you have a value
1524 # b. Test: value -> part_value[]
1525 # c. Other Suffix: value -> value
1526 #
1527 # 4. Process vsub_state.join_array here before returning.
1528 #
1529 # These cases are hard to distinguish:
1530 # - ${!prefix@} prefix query
1531 # - ${!array[@]} keys
1532 # - ${!ref} named reference
1533 # - ${!ref[0]} named reference
1534 #
1535 # I think we need several stages:
1536 #
1537 # 1. value: name, number, special, prefix query
1538 # 2. bracket_op
1539 # 3. prefix length -- this is TERMINAL
1540 # 4. indirection? Only for some of the ! cases
1541 # 5. string transformation suffix ops like ##
1542 # 6. test op
1543 # 7. vsub_state.join_array
1544
1545 # vsub_state.join_array is for joining "${a[*]}" and unquoted ${a[@]} AFTER
1546 # suffix ops are applied. If we take the length with a prefix op, the
1547 # distinction is ignored.
1548
1549 var_name = None # type: Optional[str] # used throughout the function
1550 vtest_place = VTestPlace(var_name, None) # For ${foo=default}
1551 vsub_state = VarSubState.CreateNull() # for $*, ${a[*]}, etc.
1552
1553 # 1. Evaluate from (var_name, var_num, token Id) -> value
1554 if part.name_tok.id == Id.VSub_Name:
1555 # Handle ${!prefix@} first, since that looks at names and not values
1556 # Do NOT handle ${!A[@]@a} here!
1557 if (part.prefix_op is not None and part.bracket_op is None and
1558 part.suffix_op is not None and
1559 part.suffix_op.tag() == suffix_op_e.Nullary):
1560 nullary_op = cast(Token, part.suffix_op)
1561 # ${!x@} but not ${!x@P}
1562 if consts.GetKind(nullary_op.id) == Kind.VOp3:
1563 names = self.mem.VarNamesStartingWith(part.var_name)
1564 names.sort()
1565
1566 if quoted and nullary_op.id == Id.VOp3_Star:
1567 sep = self.splitter.GetJoinChar()
1568 part_vals.append(Piece(sep.join(names), quoted, True))
1569 else:
1570 part_vals.append(part_value.Array(names, quoted))
1571 return # EARLY RETURN
1572
1573 var_name = part.var_name
1574 vtest_place.name = var_name # for _ApplyTestOp
1575
1576 val = self.mem.GetValue(var_name)
1577
1578 elif part.name_tok.id == Id.VSub_Number:
1579 var_num = int(part.var_name)
1580 val = self._EvalVarNum(var_num)
1581 else:
1582 # $* decays
1583 val = self._EvalSpecialVar(part.name_tok.id, quoted, vsub_state)
1584
1585 suffix_op_ = part.suffix_op
1586 if suffix_op_:
1587 UP_op = suffix_op_
1588 vsub_state.h_value = val
1589
1590 # 2. Bracket Op
1591 val = self._EvalBracketOp(val, part, quoted, vsub_state, vtest_place)
1592
1593 if part.prefix_op:
1594 if part.prefix_op.id == Id.VSub_Pound: # ${#var} for length
1595 # undef -> '' BEFORE length
1596 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1597
1598 n = self._Count(val, part.name_tok)
1599 part_vals.append(Piece(str(n), quoted, False))
1600 return # EARLY EXIT: nothing else can come after length
1601
1602 elif part.prefix_op.id == Id.VSub_Bang:
1603 if (part.bracket_op and
1604 part.bracket_op.tag() == bracket_op_e.WholeArray and
1605 not suffix_op_):
1606 # undef -> empty array
1607 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1608
1609 # ${!array[@]} to get indices/keys
1610 val = self._Keys(val, part.name_tok)
1611 # already set vsub_State.join_array ABOVE
1612 else:
1613 # Process ${!ref}. SURPRISE: ${!a[0]} is an indirect expansion unlike
1614 # ${!a[@]} !
1615 # ${!ref} can expand into an array if ref='array[@]'
1616
1617 # Clear it now that we have a var ref
1618 vtest_place.name = None
1619 vtest_place.index = None
1620
1621 val = self._EvalVarRef(val, part.name_tok, quoted,
1622 vsub_state, vtest_place)
1623
1624 else:
1625 raise AssertionError(part.prefix_op)
1626
1627 quoted2 = False # another bit for @Q
1628 if suffix_op_:
1629 op = suffix_op_ # could get rid of this alias
1630
1631 with tagswitch(suffix_op_) as case:
1632 if case(suffix_op_e.Nullary):
1633 op = cast(Token, UP_op)
1634 val, quoted2 = self._Nullary(val, op, var_name,
1635 part.name_tok, vsub_state)
1636
1637 elif case(suffix_op_e.Unary):
1638 op = cast(suffix_op.Unary, UP_op)
1639 if consts.GetKind(op.op.id) == Kind.VTest:
1640 # Note: _ProcessUndef (i.e., the conversion of undef ->
1641 # '') is not applied to the VTest operators such as
1642 # ${a:-def}, ${a+set}, etc.
1643 if self._ApplyTestOp(val, op, quoted, part_vals,
1644 vtest_place, part.name_tok,
1645 vsub_state):
1646 # e.g. to evaluate ${undef:-'default'}, we already appended
1647 # what we need
1648 return
1649
1650 else:
1651 # Other suffix: value -> value
1652 val = self._ProcessUndef(val, part.name_tok,
1653 vsub_state)
1654 val = self._ApplyUnarySuffixOp(val, op)
1655
1656 elif case(suffix_op_e.PatSub): # PatSub, vectorized
1657 op = cast(suffix_op.PatSub, UP_op)
1658 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1659 val = self._PatSub(val, op)
1660
1661 elif case(suffix_op_e.Slice):
1662 op = cast(suffix_op.Slice, UP_op)
1663 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1664 val = self._Slice(val, op, var_name, part)
1665
1666 elif case(suffix_op_e.Static):
1667 op = cast(suffix_op.Static, UP_op)
1668 e_die('Not implemented', op.tok)
1669
1670 else:
1671 raise AssertionError()
1672 else:
1673 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1674
1675 # After applying suffixes, process join_array here.
1676 val = self._JoinArray(val, quoted, vsub_state)
1677
1678 # For example, ${a} evaluates to value.Str(), but we want a
1679 # Piece().
1680 part_val = _ValueToPartValue(val, quoted or quoted2, part)
1681 part_vals.append(part_val)
1682
1683 def _ConcatPartVals(self, part_vals, location):
1684 # type: (List[part_value_t], loc_t) -> str
1685
1686 strs = [] # type: List[str]
1687 for part_val in part_vals:
1688 UP_part_val = part_val
1689 with tagswitch(part_val) as case:
1690 if case(part_value_e.String):
1691 part_val = cast(Piece, UP_part_val)
1692 s = part_val.s
1693
1694 elif case(part_value_e.Array):
1695 part_val = cast(part_value.Array, UP_part_val)
1696 if self.exec_opts.strict_array():
1697 # Examples: echo f > "$@"; local foo="$@"
1698 e_die("Illegal array word part (strict_array)",
1699 location)
1700 else:
1701 # It appears to not respect IFS
1702 # TODO: eliminate double join()?
1703 tmp = [s for s in part_val.strs if s is not None]
1704 s = ' '.join(tmp)
1705
1706 else:
1707 raise AssertionError()
1708
1709 strs.append(s)
1710
1711 return ''.join(strs)
1712
1713 def EvalBracedVarSubToString(self, part):
1714 # type: (BracedVarSub) -> str
1715 """For double quoted strings in YSH expressions.
1716
1717 Example: var x = "$foo-${foo}"
1718 """
1719 part_vals = [] # type: List[part_value_t]
1720 self._EvalBracedVarSub(part, part_vals, False)
1721 # blame ${ location
1722 return self._ConcatPartVals(part_vals, part.left)
1723
1724 def _EvalSimpleVarSub(self, part, part_vals, quoted):
1725 # type: (SimpleVarSub, List[part_value_t], bool) -> None
1726
1727 token = part.tok
1728
1729 vsub_state = VarSubState.CreateNull()
1730
1731 # 1. Evaluate from (var_name, var_num, Token) -> defined, value
1732 if token.id == Id.VSub_DollarName:
1733 var_name = lexer.LazyStr(token)
1734 # TODO: Special case for LINENO
1735 val = self.mem.GetValue(var_name)
1736 if val.tag() in (value_e.BashArray, value_e.SparseArray,
1737 value_e.BashAssoc):
1738 if ShouldArrayDecay(var_name, self.exec_opts):
1739 # for $BASH_SOURCE, etc.
1740 val = DecayArray(val)
1741 else:
1742 e_die(
1743 "Array %r can't be referred to as a scalar (without @ or *)"
1744 % var_name, token)
1745
1746 elif token.id == Id.VSub_Number:
1747 var_num = int(lexer.LazyStr(token))
1748 val = self._EvalVarNum(var_num)
1749
1750 else:
1751 val = self._EvalSpecialVar(token.id, quoted, vsub_state)
1752
1753 #log('SIMPLE %s', part)
1754 val = self._ProcessUndef(val, token, vsub_state)
1755 val = self._JoinArray(val, quoted, vsub_state)
1756
1757 v = _ValueToPartValue(val, quoted, part)
1758 part_vals.append(v)
1759
1760 def EvalSimpleVarSubToString(self, node):
1761 # type: (SimpleVarSub) -> str
1762 """For double quoted strings in YSH expressions.
1763
1764 Example: var x = "$foo-${foo}"
1765 """
1766 part_vals = [] # type: List[part_value_t]
1767 self._EvalSimpleVarSub(node, part_vals, False)
1768 return self._ConcatPartVals(part_vals, node.tok)
1769
1770 def _EvalExtGlob(self, part, part_vals):
1771 # type: (word_part.ExtGlob, List[part_value_t]) -> None
1772 """Evaluate @($x|'foo'|$(hostname)) and flatten it."""
1773 op = part.op
1774 if op.id == Id.ExtGlob_Comma:
1775 op_str = '@('
1776 else:
1777 op_str = lexer.LazyStr(op)
1778 # Do NOT split these.
1779 part_vals.append(Piece(op_str, False, False))
1780
1781 for i, w in enumerate(part.arms):
1782 if i != 0:
1783 part_vals.append(Piece('|', False, False)) # separator
1784 # FLATTEN the tree of extglob "arms".
1785 self._EvalWordToParts(w, part_vals, EXTGLOB_NESTED)
1786 part_vals.append(Piece(')', False, False)) # closing )
1787
1788 def _TranslateExtGlob(self, part_vals, w, glob_parts, fnmatch_parts):
1789 # type: (List[part_value_t], CompoundWord, List[str], List[str]) -> None
1790 """Translate a flattened WORD with an ExtGlob part to string patterns.
1791
1792 We need both glob and fnmatch patterns. _EvalExtGlob does the
1793 flattening.
1794 """
1795 for i, part_val in enumerate(part_vals):
1796 UP_part_val = part_val
1797 with tagswitch(part_val) as case:
1798 if case(part_value_e.String):
1799 part_val = cast(Piece, UP_part_val)
1800 if part_val.quoted and not self.exec_opts.noglob():
1801 s = glob_.GlobEscape(part_val.s)
1802 else:
1803 # e.g. the @( and | in @(foo|bar) aren't quoted
1804 s = part_val.s
1805 glob_parts.append(s)
1806 fnmatch_parts.append(s) # from _EvalExtGlob()
1807
1808 elif case(part_value_e.Array):
1809 # Disallow array
1810 e_die(
1811 "Extended globs and arrays can't appear in the same word",
1812 w)
1813
1814 elif case(part_value_e.ExtGlob):
1815 part_val = cast(part_value.ExtGlob, UP_part_val)
1816 # keep appending fnmatch_parts, but repplace glob_parts with '*'
1817 self._TranslateExtGlob(part_val.part_vals, w, [],
1818 fnmatch_parts)
1819 glob_parts.append('*')
1820
1821 else:
1822 raise AssertionError()
1823
1824 def _EvalWordPart(self, part, part_vals, flags):
1825 # type: (word_part_t, List[part_value_t], int) -> None
1826 """Evaluate a word part, appending to part_vals
1827
1828 Called by _EvalWordToParts, EvalWordToString, and _EvalDoubleQuoted.
1829 """
1830 quoted = bool(flags & QUOTED)
1831 is_subst = bool(flags & IS_SUBST)
1832
1833 UP_part = part
1834 with tagswitch(part) as case:
1835 if case(word_part_e.ShArrayLiteral):
1836 part = cast(ShArrayLiteral, UP_part)
1837 e_die("Unexpected array literal", loc.WordPart(part))
1838 elif case(word_part_e.BashAssocLiteral):
1839 part = cast(word_part.BashAssocLiteral, UP_part)
1840 e_die("Unexpected associative array literal",
1841 loc.WordPart(part))
1842
1843 elif case(word_part_e.Literal):
1844 part = cast(Token, UP_part)
1845 # Split if it's in a substitution.
1846 # That is: echo is not split, but ${foo:-echo} is split
1847 v = Piece(lexer.LazyStr(part), quoted, is_subst)
1848 part_vals.append(v)
1849
1850 elif case(word_part_e.EscapedLiteral):
1851 part = cast(word_part.EscapedLiteral, UP_part)
1852 v = Piece(part.ch, True, False)
1853 part_vals.append(v)
1854
1855 elif case(word_part_e.SingleQuoted):
1856 part = cast(SingleQuoted, UP_part)
1857 v = Piece(part.sval, True, False)
1858 part_vals.append(v)
1859
1860 elif case(word_part_e.DoubleQuoted):
1861 part = cast(DoubleQuoted, UP_part)
1862 self._EvalDoubleQuoted(part.parts, part_vals)
1863
1864 elif case(word_part_e.CommandSub):
1865 part = cast(CommandSub, UP_part)
1866 id_ = part.left_token.id
1867 if id_ in (Id.Left_DollarParen, Id.Left_AtParen,
1868 Id.Left_Backtick):
1869 sv = self._EvalCommandSub(part,
1870 quoted) # type: part_value_t
1871
1872 elif id_ in (Id.Left_ProcSubIn, Id.Left_ProcSubOut):
1873 sv = self._EvalProcessSub(part)
1874
1875 else:
1876 raise AssertionError(id_)
1877
1878 part_vals.append(sv)
1879
1880 elif case(word_part_e.SimpleVarSub):
1881 part = cast(SimpleVarSub, UP_part)
1882 self._EvalSimpleVarSub(part, part_vals, quoted)
1883
1884 elif case(word_part_e.BracedVarSub):
1885 part = cast(BracedVarSub, UP_part)
1886 self._EvalBracedVarSub(part, part_vals, quoted)
1887
1888 elif case(word_part_e.TildeSub):
1889 part = cast(word_part.TildeSub, UP_part)
1890 # We never parse a quoted string into a TildeSub.
1891 assert not quoted
1892 s = self.tilde_ev.Eval(part)
1893 v = Piece(s, True, False) # NOT split even when unquoted!
1894 part_vals.append(v)
1895
1896 elif case(word_part_e.ArithSub):
1897 part = cast(word_part.ArithSub, UP_part)
1898 num = self.arith_ev.EvalToBigInt(part.anode)
1899 v = Piece(mops.ToStr(num), quoted, not quoted)
1900 part_vals.append(v)
1901
1902 elif case(word_part_e.ExtGlob):
1903 part = cast(word_part.ExtGlob, UP_part)
1904 #if not self.exec_opts.extglob():
1905 # die() # disallow at runtime? Don't just decay
1906
1907 # Create a node to hold the flattened tree. The caller decides whether
1908 # to pass it to fnmatch() or replace it with '*' and pass it to glob().
1909 part_vals2 = [] # type: List[part_value_t]
1910 self._EvalExtGlob(part, part_vals2) # flattens tree
1911 part_vals.append(part_value.ExtGlob(part_vals2))
1912
1913 elif case(word_part_e.BashRegexGroup):
1914 part = cast(word_part.BashRegexGroup, UP_part)
1915
1916 part_vals.append(Piece('(', False, False)) # not quoted
1917 if part.child:
1918 self._EvalWordToParts(part.child, part_vals, 0)
1919 part_vals.append(Piece(')', False, False))
1920
1921 elif case(word_part_e.Splice):
1922 part = cast(word_part.Splice, UP_part)
1923 val = self.mem.GetValue(part.var_name)
1924
1925 strs = self.expr_ev.SpliceValue(val, part)
1926 part_vals.append(part_value.Array(strs, True))
1927
1928 elif case(word_part_e.ExprSub):
1929 part = cast(word_part.ExprSub, UP_part)
1930 part_val = self.expr_ev.EvalExprSub(part)
1931 part_vals.append(part_val)
1932
1933 elif case(word_part_e.ZshVarSub):
1934 part = cast(word_part.ZshVarSub, UP_part)
1935 e_die("ZSH var subs are parsed, but can't be evaluated",
1936 part.left)
1937
1938 else:
1939 raise AssertionError(part.tag())
1940
1941 def _EvalRhsWordToParts(self, w, part_vals, eval_flags=0):
1942 # type: (rhs_word_t, List[part_value_t], int) -> None
1943 quoted = bool(eval_flags & QUOTED)
1944
1945 UP_w = w
1946 with tagswitch(w) as case:
1947 if case(rhs_word_e.Empty):
1948 part_vals.append(Piece('', quoted, not quoted))
1949
1950 elif case(rhs_word_e.Compound):
1951 w = cast(CompoundWord, UP_w)
1952 self._EvalWordToParts(w, part_vals, eval_flags=eval_flags)
1953
1954 else:
1955 raise AssertionError()
1956
1957 def _EvalWordToParts(self, w, part_vals, eval_flags=0):
1958 # type: (CompoundWord, List[part_value_t], int) -> None
1959 """Helper for EvalRhsWord, EvalWordSequence, etc.
1960
1961 Returns:
1962 Appends to part_vals. Note that this is a TREE.
1963 """
1964 # Does the word have an extended glob? This is a special case because
1965 # of the way we use glob() and then fnmatch(..., FNM_EXTMATCH) to
1966 # implement extended globs. It's hard to carry that extra information
1967 # all the way past the word splitting stage.
1968
1969 # OSH semantic limitations: If a word has an extended glob part, then
1970 # 1. It can't have an array
1971 # 2. Word splitting of unquoted words isn't respected
1972
1973 word_part_vals = [] # type: List[part_value_t]
1974 has_extglob = False
1975 for p in w.parts:
1976 if p.tag() == word_part_e.ExtGlob:
1977 has_extglob = True
1978 self._EvalWordPart(p, word_part_vals, eval_flags)
1979
1980 # Caller REQUESTED extglob evaluation, AND we parsed word_part.ExtGlob()
1981 if has_extglob:
1982 if bool(eval_flags & EXTGLOB_FILES):
1983 # Treat the WHOLE word as a pattern. We need to TWO VARIANTS of the
1984 # word because of the way we use libc:
1985 # 1. With '*' for extglob parts
1986 # 2. With _EvalExtGlob() for extglob parts
1987
1988 glob_parts = [] # type: List[str]
1989 fnmatch_parts = [] # type: List[str]
1990 self._TranslateExtGlob(word_part_vals, w, glob_parts,
1991 fnmatch_parts)
1992
1993 #log('word_part_vals %s', word_part_vals)
1994 glob_pat = ''.join(glob_parts)
1995 fnmatch_pat = ''.join(fnmatch_parts)
1996 #log("glob %s fnmatch %s", glob_pat, fnmatch_pat)
1997
1998 results = [] # type: List[str]
1999 n = self.globber.ExpandExtended(glob_pat, fnmatch_pat, results)
2000 if n < 0:
2001 raise error.FailGlob(
2002 'Extended glob %r matched no files' % fnmatch_pat, w)
2003
2004 part_vals.append(part_value.Array(results, True))
2005 elif bool(eval_flags & EXTGLOB_NESTED):
2006 # We only glob at the TOP level of @(nested|@(pattern))
2007 part_vals.extend(word_part_vals)
2008 else:
2009 # e.g. simple_word_eval, assignment builtin
2010 e_die('Extended glob not allowed in this word', w)
2011 else:
2012 part_vals.extend(word_part_vals)
2013
2014 def _PartValsToString(self, part_vals, w, eval_flags, strs):
2015 # type: (List[part_value_t], CompoundWord, int, List[str]) -> None
2016 """Helper for EvalWordToString, similar to _ConcatPartVals() above.
2017
2018 Note: arg 'w' could just be a span ID
2019 """
2020 for part_val in part_vals:
2021 UP_part_val = part_val
2022 with tagswitch(part_val) as case:
2023 if case(part_value_e.String):
2024 part_val = cast(Piece, UP_part_val)
2025 s = part_val.s
2026 if part_val.quoted:
2027 if eval_flags & QUOTE_FNMATCH:
2028 # [[ foo == */"*".py ]] or case (*.py) or ${x%*.py} or ${x//*.py/}
2029 s = glob_.GlobEscape(s)
2030 elif eval_flags & QUOTE_ERE:
2031 s = glob_.ExtendedRegexEscape(s)
2032 strs.append(s)
2033
2034 elif case(part_value_e.Array):
2035 part_val = cast(part_value.Array, UP_part_val)
2036 if self.exec_opts.strict_array():
2037 # Examples: echo f > "$@"; local foo="$@"
2038
2039 # TODO: This attributes too coarsely, to the word rather than the
2040 # parts. Problem: the word is a TREE of parts, but we only have a
2041 # flat list of part_vals. The only case where we really get arrays
2042 # is "$@", "${a[@]}", "${a[@]//pat/replace}", etc.
2043 e_die(
2044 "This word should yield a string, but it contains an array",
2045 w)
2046
2047 # TODO: Maybe add detail like this.
2048 #e_die('RHS of assignment should only have strings. '
2049 # 'To assign arrays, use b=( "${a[@]}" )')
2050 else:
2051 # It appears to not respect IFS
2052 tmp = [s for s in part_val.strs if s is not None]
2053 s = ' '.join(tmp) # TODO: eliminate double join()?
2054 strs.append(s)
2055
2056 elif case(part_value_e.ExtGlob):
2057 part_val = cast(part_value.ExtGlob, UP_part_val)
2058
2059 # Extended globs are only allowed where we expect them!
2060 if not bool(eval_flags & QUOTE_FNMATCH):
2061 e_die('extended glob not allowed in this word', w)
2062
2063 # recursive call
2064 self._PartValsToString(part_val.part_vals, w, eval_flags,
2065 strs)
2066
2067 else:
2068 raise AssertionError()
2069
2070 def EvalWordToString(self, UP_w, eval_flags=0):
2071 # type: (word_t, int) -> value.Str
2072 """Given a word, return a string.
2073
2074 Flags can contain a quoting algorithm.
2075 """
2076 assert UP_w.tag() == word_e.Compound, UP_w
2077 w = cast(CompoundWord, UP_w)
2078
2079 if eval_flags == 0: # QUOTE_FNMATCH etc. breaks optimization
2080 fast_str = word_.FastStrEval(w)
2081 if fast_str is not None:
2082 return value.Str(fast_str)
2083
2084 # Could we additionally optimize a=$b, if we know $b isn't an array
2085 # etc.?
2086
2087 # Note: these empty lists are hot in fib benchmark
2088
2089 part_vals = [] # type: List[part_value_t]
2090 for p in w.parts:
2091 # this doesn't use eval_flags, which is slightly confusing
2092 self._EvalWordPart(p, part_vals, 0)
2093
2094 strs = [] # type: List[str]
2095 self._PartValsToString(part_vals, w, eval_flags, strs)
2096 return value.Str(''.join(strs))
2097
2098 def EvalWordToPattern(self, UP_w):
2099 # type: (rhs_word_t) -> Tuple[value.Str, bool]
2100 """Like EvalWordToString, but returns whether we got ExtGlob."""
2101 if UP_w.tag() == rhs_word_e.Empty:
2102 return value.Str(''), False
2103
2104 assert UP_w.tag() == rhs_word_e.Compound, UP_w
2105 w = cast(CompoundWord, UP_w)
2106
2107 has_extglob = False
2108 part_vals = [] # type: List[part_value_t]
2109 for p in w.parts:
2110 # this doesn't use eval_flags, which is slightly confusing
2111 self._EvalWordPart(p, part_vals, 0)
2112 if p.tag() == word_part_e.ExtGlob:
2113 has_extglob = True
2114
2115 strs = [] # type: List[str]
2116 self._PartValsToString(part_vals, w, QUOTE_FNMATCH, strs)
2117 return value.Str(''.join(strs)), has_extglob
2118
2119 def EvalForPlugin(self, w):
2120 # type: (CompoundWord) -> value.Str
2121 """Wrapper around EvalWordToString that prevents errors.
2122
2123 Runtime errors like $(( 1 / 0 )) and mutating $? like $(exit 42)
2124 are handled here.
2125
2126 Similar to ExprEvaluator.PluginCall().
2127 """
2128 with state.ctx_Registers(self.mem): # to "sandbox" $? and $PIPESTATUS
2129 try:
2130 val = self.EvalWordToString(w)
2131 except error.FatalRuntime as e:
2132 val = value.Str('<Runtime error: %s>' % e.UserErrorString())
2133
2134 except (IOError, OSError) as e:
2135 val = value.Str('<I/O error: %s>' % pyutil.strerror(e))
2136
2137 except KeyboardInterrupt:
2138 val = value.Str('<Ctrl-C>')
2139
2140 return val
2141
2142 def EvalRhsWord(self, UP_w):
2143 # type: (rhs_word_t) -> value_t
2144 """Used for RHS of assignment.
2145
2146 There is no splitting.
2147 """
2148 if UP_w.tag() == rhs_word_e.Empty:
2149 return value.Str('')
2150
2151 assert UP_w.tag() == word_e.Compound, UP_w
2152 w = cast(CompoundWord, UP_w)
2153
2154 if len(w.parts) == 1:
2155 part0 = w.parts[0]
2156 UP_part0 = part0
2157 tag = part0.tag()
2158 # Special case for a=(1 2). ShArrayLiteral won't appear in words that
2159 # don't look like assignments.
2160 if tag == word_part_e.ShArrayLiteral:
2161 part0 = cast(ShArrayLiteral, UP_part0)
2162 array_words = part0.words
2163 words = braces.BraceExpandWords(array_words)
2164 strs = self.EvalWordSequence(words)
2165 return value.BashArray(strs)
2166
2167 if tag == word_part_e.BashAssocLiteral:
2168 part0 = cast(word_part.BashAssocLiteral, UP_part0)
2169 d = NewDict() # type: Dict[str, str]
2170 for pair in part0.pairs:
2171 k = self.EvalWordToString(pair.key)
2172 v = self.EvalWordToString(pair.value)
2173 d[k.s] = v.s
2174 return value.BashAssoc(d)
2175
2176 # If RHS doesn't look like a=( ... ), then it must be a string.
2177 return self.EvalWordToString(w)
2178
2179 def _EvalWordFrame(self, frame, argv):
2180 # type: (List[Piece], List[str]) -> None
2181 all_empty = True
2182 all_quoted = True
2183 any_quoted = False
2184
2185 #log('--- frame %s', frame)
2186
2187 for piece in frame:
2188 if len(piece.s):
2189 all_empty = False
2190
2191 if piece.quoted:
2192 any_quoted = True
2193 else:
2194 all_quoted = False
2195
2196 # Elision of ${empty}${empty} but not $empty"$empty" or $empty""
2197 if all_empty and not any_quoted:
2198 return
2199
2200 # If every frag is quoted, e.g. "$a$b" or any part in "${a[@]}"x, then
2201 # don't do word splitting or globbing.
2202 if all_quoted:
2203 tmp = [piece.s for piece in frame]
2204 a = ''.join(tmp)
2205 argv.append(a)
2206 return
2207
2208 will_glob = not self.exec_opts.noglob()
2209
2210 if 0:
2211 log('---')
2212 log('FRAME')
2213 for i, piece in enumerate(frame):
2214 log('(%d) %s', i, piece)
2215 log('')
2216
2217 # Array of strings, some of which are BOTH IFS-escaped and GLOB escaped!
2218 frags = [] # type: List[str]
2219 for piece in frame:
2220 if will_glob and piece.quoted:
2221 frag = glob_.GlobEscape(piece.s)
2222 else:
2223 # If we have a literal \, then we turn it into \\\\.
2224 # Splitting takes \\\\ -> \\
2225 # Globbing takes \\ to \ if it doesn't match
2226 frag = _BackslashEscape(piece.s)
2227
2228 if piece.do_split:
2229 frag = _BackslashEscape(frag)
2230 else:
2231 frag = self.splitter.Escape(frag)
2232
2233 frags.append(frag)
2234
2235 if 0:
2236 log('---')
2237 log('FRAGS')
2238 for i, frag in enumerate(frags):
2239 log('(%d) %s', i, frag)
2240 log('')
2241
2242 flat = ''.join(frags)
2243 #log('flat: %r', flat)
2244
2245 args = self.splitter.SplitForWordEval(flat)
2246
2247 # space=' '; argv $space"". We have a quoted part, but we CANNOT elide.
2248 # Add it back and don't bother globbing.
2249 if len(args) == 0 and any_quoted:
2250 argv.append('')
2251 return
2252
2253 #log('split args: %r', args)
2254 for a in args:
2255 if glob_.LooksLikeGlob(a):
2256 n = self.globber.Expand(a, argv)
2257 if n < 0:
2258 # TODO: location info, with span IDs carried through the frame
2259 raise error.FailGlob('Pattern %r matched no files' % a,
2260 loc.Missing)
2261 else:
2262 argv.append(glob_.GlobUnescape(a))
2263
2264 def _EvalWordToArgv(self, w):
2265 # type: (CompoundWord) -> List[str]
2266 """Helper for _EvalAssignBuiltin.
2267
2268 Splitting and globbing are disabled for assignment builtins.
2269
2270 Example: declare -"${a[@]}" b=(1 2)
2271 where a is [x b=a d=a]
2272 """
2273 part_vals = [] # type: List[part_value_t]
2274 self._EvalWordToParts(w, part_vals, 0) # not double quoted
2275 frames = _MakeWordFrames(part_vals)
2276 argv = [] # type: List[str]
2277 for frame in frames:
2278 if len(frame): # empty array gives empty frame!
2279 tmp = [piece.s for piece in frame]
2280 argv.append(''.join(tmp)) # no split or glob
2281 #log('argv: %s', argv)
2282 return argv
2283
2284 def _EvalAssignBuiltin(self, builtin_id, arg0, words, meta_offset):
2285 # type: (builtin_t, str, List[CompoundWord], int) -> cmd_value.Assign
2286 """Handles both static and dynamic assignment, e.g.
2287
2288 x='foo=bar'
2289 local a=(1 2) $x
2290
2291 Grammar:
2292
2293 ('builtin' | 'command')* keyword flag* pair*
2294 flag = [-+].*
2295
2296 There is also command -p, but we haven't implemented it. Maybe just
2297 punt on it.
2298 """
2299 eval_to_pairs = True # except for -f and -F
2300 started_pairs = False
2301
2302 flags = [arg0] # initial flags like -p, and -f -F name1 name2
2303 flag_locs = [words[0]]
2304 assign_args = [] # type: List[AssignArg]
2305
2306 n = len(words)
2307 for i in xrange(meta_offset + 1, n): # skip first word
2308 w = words[i]
2309
2310 if word_.IsVarLike(w):
2311 started_pairs = True # Everything from now on is an assign_pair
2312
2313 if started_pairs:
2314 left_token, close_token, part_offset = word_.DetectShAssignment(
2315 w)
2316 if left_token: # Detected statically
2317 if left_token.id != Id.Lit_VarLike:
2318 # (not guaranteed since started_pairs is set twice)
2319 e_die('LHS array not allowed in assignment builtin', w)
2320
2321 if lexer.IsPlusEquals(left_token):
2322 var_name = lexer.TokenSliceRight(left_token, -2)
2323 append = True
2324 else:
2325 var_name = lexer.TokenSliceRight(left_token, -1)
2326 append = False
2327
2328 if part_offset == len(w.parts):
2329 rhs = rhs_word.Empty # type: rhs_word_t
2330 else:
2331 # tmp is for intersection of C++/MyPy type systems
2332 tmp = CompoundWord(w.parts[part_offset:])
2333 word_.TildeDetectAssign(tmp)
2334 rhs = tmp
2335
2336 with state.ctx_AssignBuiltin(self.mutable_opts):
2337 right = self.EvalRhsWord(rhs)
2338
2339 arg2 = AssignArg(var_name, right, append, w)
2340 assign_args.append(arg2)
2341
2342 else: # e.g. export $dynamic
2343 argv = self._EvalWordToArgv(w)
2344 for arg in argv:
2345 arg2 = _SplitAssignArg(arg, w)
2346 assign_args.append(arg2)
2347
2348 else:
2349 argv = self._EvalWordToArgv(w)
2350 for arg in argv:
2351 if arg.startswith('-') or arg.startswith('+'):
2352 # e.g. declare -r +r
2353 flags.append(arg)
2354 flag_locs.append(w)
2355
2356 # Shortcut that relies on -f and -F always meaning "function" for
2357 # all assignment builtins
2358 if 'f' in arg or 'F' in arg:
2359 eval_to_pairs = False
2360
2361 else: # e.g. export $dynamic
2362 if eval_to_pairs:
2363 arg2 = _SplitAssignArg(arg, w)
2364 assign_args.append(arg2)
2365 started_pairs = True
2366 else:
2367 flags.append(arg)
2368
2369 return cmd_value.Assign(builtin_id, flags, flag_locs, assign_args)
2370
2371 def _DetectAssignBuiltinStr(self, arg0, words, meta_offset):
2372 # type: (str, List[CompoundWord], int) -> Optional[cmd_value.Assign]
2373 builtin_id = consts.LookupAssignBuiltin(arg0)
2374 if builtin_id != consts.NO_INDEX:
2375 return self._EvalAssignBuiltin(builtin_id, arg0, words,
2376 meta_offset)
2377 return None
2378
2379 def _DetectAssignBuiltin(self, val0, words, meta_offset):
2380 # type: (part_value_t, List[CompoundWord], int) -> Optional[cmd_value.Assign]
2381 UP_val0 = val0
2382 if val0.tag() == part_value_e.String:
2383 val0 = cast(Piece, UP_val0)
2384 if not val0.quoted:
2385 return self._DetectAssignBuiltinStr(val0.s, words, meta_offset)
2386 return None
2387
2388 def SimpleEvalWordSequence2(self, words, is_last_cmd, allow_assign):
2389 # type: (List[CompoundWord], bool, bool) -> cmd_value_t
2390 """Simple word evaluation for YSH."""
2391 strs = [] # type: List[str]
2392 locs = [] # type: List[CompoundWord]
2393
2394 meta_offset = 0
2395 for i, w in enumerate(words):
2396 # No globbing in the first arg for command.Simple.
2397 if i == meta_offset and allow_assign:
2398 strs0 = self._EvalWordToArgv(w)
2399 # TODO: Remove this because YSH will disallow assignment
2400 # builtins? (including export?)
2401 if len(strs0) == 1:
2402 cmd_val = self._DetectAssignBuiltinStr(
2403 strs0[0], words, meta_offset)
2404 if cmd_val:
2405 return cmd_val
2406
2407 strs.extend(strs0)
2408 for _ in strs0:
2409 locs.append(w)
2410 continue
2411
2412 if glob_.LooksLikeStaticGlob(w):
2413 val = self.EvalWordToString(w) # respects strict-array
2414 num_appended = self.globber.Expand(val.s, strs)
2415 if num_appended < 0:
2416 raise error.FailGlob('Pattern %r matched no files' % val.s,
2417 w)
2418 for _ in xrange(num_appended):
2419 locs.append(w)
2420 continue
2421
2422 part_vals = [] # type: List[part_value_t]
2423 self._EvalWordToParts(w, part_vals, 0) # not quoted
2424
2425 if 0:
2426 log('')
2427 log('Static: part_vals after _EvalWordToParts:')
2428 for entry in part_vals:
2429 log(' %s', entry)
2430
2431 # Still need to process
2432 frames = _MakeWordFrames(part_vals)
2433
2434 if 0:
2435 log('')
2436 log('Static: frames after _MakeWordFrames:')
2437 for entry in frames:
2438 log(' %s', entry)
2439
2440 # We will still allow x"${a[@]"x, though it's deprecated by @a, which
2441 # disallows such expressions at parse time.
2442 for frame in frames:
2443 if len(frame): # empty array gives empty frame!
2444 tmp = [piece.s for piece in frame]
2445 strs.append(''.join(tmp)) # no split or glob
2446 locs.append(w)
2447
2448 assert len(strs) == len(locs), '%s vs. %d' % (strs, len(locs))
2449 return cmd_value.Argv(strs, locs, is_last_cmd, None, None)
2450
2451 def EvalWordSequence2(self, words, is_last_cmd, allow_assign=False):
2452 # type: (List[CompoundWord], bool, bool) -> cmd_value_t
2453 """Turns a list of Words into a list of strings.
2454
2455 Unlike the EvalWord*() methods, it does globbing.
2456
2457 Args:
2458 allow_assign: True for command.Simple, False for BashArray a=(1 2 3)
2459 """
2460 if self.exec_opts.simple_word_eval():
2461 return self.SimpleEvalWordSequence2(words, is_last_cmd,
2462 allow_assign)
2463
2464 # Parse time:
2465 # 1. brace expansion. TODO: Do at parse time.
2466 # 2. Tilde detection. DONE at parse time. Only if Id.Lit_Tilde is the
2467 # first WordPart.
2468 #
2469 # Run time:
2470 # 3. tilde sub, var sub, command sub, arith sub. These are all
2471 # "concurrent" on WordParts. (optional process sub with <() )
2472 # 4. word splitting. Can turn this off with a shell option? Definitely
2473 # off for oil.
2474 # 5. globbing -- several exec_opts affect this: nullglob, safeglob, etc.
2475
2476 #log('W %s', words)
2477 strs = [] # type: List[str]
2478 locs = [] # type: List[CompoundWord]
2479
2480 # 0 for declare x
2481 # 1 for builtin declare x
2482 # 2 for command builtin declare x
2483 # etc.
2484 meta_offset = 0
2485
2486 n = 0
2487 for i, w in enumerate(words):
2488 fast_str = word_.FastStrEval(w)
2489 if fast_str is not None:
2490 strs.append(fast_str)
2491 locs.append(w)
2492
2493 # e.g. the 'local' in 'local a=b c=d' will be here
2494 if allow_assign and i == meta_offset:
2495 cmd_val = self._DetectAssignBuiltinStr(
2496 fast_str, words, meta_offset)
2497 if cmd_val:
2498 return cmd_val
2499
2500 if i <= meta_offset and _DetectMetaBuiltinStr(fast_str):
2501 meta_offset += 1
2502
2503 # Bug fix: n must be updated on every loop iteration
2504 n = len(strs)
2505 assert len(strs) == len(locs), strs
2506 continue
2507
2508 part_vals = [] # type: List[part_value_t]
2509 self._EvalWordToParts(w, part_vals, EXTGLOB_FILES)
2510
2511 # DYNAMICALLY detect if we're going to run an assignment builtin, and
2512 # change the rest of the evaluation algorithm if so.
2513 #
2514 # We want to allow:
2515 # e=export
2516 # $e foo=bar
2517 #
2518 # But we don't want to evaluate the first word twice in the case of:
2519 # $(some-command) --flag
2520 if len(part_vals) == 1:
2521 if allow_assign and i == meta_offset:
2522 cmd_val = self._DetectAssignBuiltin(
2523 part_vals[0], words, meta_offset)
2524 if cmd_val:
2525 return cmd_val
2526
2527 if i <= meta_offset and _DetectMetaBuiltin(part_vals[0]):
2528 meta_offset += 1
2529
2530 if 0:
2531 log('')
2532 log('part_vals after _EvalWordToParts:')
2533 for entry in part_vals:
2534 log(' %s', entry)
2535
2536 frames = _MakeWordFrames(part_vals)
2537 if 0:
2538 log('')
2539 log('frames after _MakeWordFrames:')
2540 for entry in frames:
2541 log(' %s', entry)
2542
2543 # Do splitting and globbing. Each frame will append zero or more args.
2544 for frame in frames:
2545 self._EvalWordFrame(frame, strs)
2546
2547 # Fill in locations parallel to strs.
2548 n_next = len(strs)
2549 for _ in xrange(n_next - n):
2550 locs.append(w)
2551 n = n_next
2552
2553 # A non-assignment command.
2554 # NOTE: Can't look up builtins here like we did for assignment, because
2555 # functions can override builtins.
2556 assert len(strs) == len(locs), '%s vs. %d' % (strs, len(locs))
2557 return cmd_value.Argv(strs, locs, is_last_cmd, None, None)
2558
2559 def EvalWordSequence(self, words):
2560 # type: (List[CompoundWord]) -> List[str]
2561 """For arrays and for loops.
2562
2563 They don't allow assignment builtins.
2564 """
2565 # is_last_cmd is irrelevant
2566 cmd_val = self.EvalWordSequence2(words, False)
2567 assert cmd_val.tag() == cmd_value_e.Argv
2568 return cast(cmd_value.Argv, cmd_val).argv
2569
2570
2571class NormalWordEvaluator(AbstractWordEvaluator):
2572
2573 def __init__(
2574 self,
2575 mem, # type: state.Mem
2576 exec_opts, # type: optview.Exec
2577 mutable_opts, # type: state.MutableOpts
2578 tilde_ev, # type: TildeEvaluator
2579 splitter, # type: SplitContext
2580 errfmt, # type: ui.ErrorFormatter
2581 ):
2582 # type: (...) -> None
2583 AbstractWordEvaluator.__init__(self, mem, exec_opts, mutable_opts,
2584 tilde_ev, splitter, errfmt)
2585 self.shell_ex = None # type: _Executor
2586
2587 def CheckCircularDeps(self):
2588 # type: () -> None
2589 assert self.arith_ev is not None
2590 # Disabled for pure OSH
2591 #assert self.expr_ev is not None
2592 assert self.shell_ex is not None
2593 assert self.prompt_ev is not None
2594
2595 def _EvalCommandSub(self, cs_part, quoted):
2596 # type: (CommandSub, bool) -> part_value_t
2597 stdout_str = self.shell_ex.RunCommandSub(cs_part)
2598
2599 if cs_part.left_token.id == Id.Left_AtParen:
2600 # YSH splitting algorithm: does not depend on IFS
2601 try:
2602 strs = j8.SplitJ8Lines(stdout_str)
2603 except error.Decode as e:
2604 # status code 4 is special, for encode/decode errors.
2605 raise error.Structured(4, e.Message(), cs_part.left_token)
2606
2607 #strs = self.splitter.SplitForWordEval(stdout_str)
2608 return part_value.Array(strs, True)
2609 else:
2610 return Piece(stdout_str, quoted, not quoted)
2611
2612 def _EvalProcessSub(self, cs_part):
2613 # type: (CommandSub) -> Piece
2614 dev_path = self.shell_ex.RunProcessSub(cs_part)
2615 # pretend it's quoted; no split or glob
2616 return Piece(dev_path, True, False)
2617
2618
2619_DUMMY = '__NO_COMMAND_SUB__'
2620
2621
2622class CompletionWordEvaluator(AbstractWordEvaluator):
2623 """An evaluator that has no access to an executor.
2624
2625 NOTE: core/completion.py doesn't actually try to use these strings to
2626 complete. If you have something like 'echo $(echo hi)/f<TAB>', it sees the
2627 inner command as the last one, and knows that it is not at the end of the
2628 line.
2629 """
2630
2631 def __init__(
2632 self,
2633 mem, # type: state.Mem
2634 exec_opts, # type: optview.Exec
2635 mutable_opts, # type: state.MutableOpts
2636 tilde_ev, # type: TildeEvaluator
2637 splitter, # type: SplitContext
2638 errfmt, # type: ui.ErrorFormatter
2639 ):
2640 # type: (...) -> None
2641 AbstractWordEvaluator.__init__(self, mem, exec_opts, mutable_opts,
2642 tilde_ev, splitter, errfmt)
2643
2644 def CheckCircularDeps(self):
2645 # type: () -> None
2646 assert self.prompt_ev is not None
2647 assert self.arith_ev is not None
2648 assert self.expr_ev is not None
2649
2650 def _EvalCommandSub(self, cs_part, quoted):
2651 # type: (CommandSub, bool) -> part_value_t
2652 if cs_part.left_token.id == Id.Left_AtParen:
2653 return part_value.Array([_DUMMY], quoted)
2654 else:
2655 return Piece(_DUMMY, quoted, not quoted)
2656
2657 def _EvalProcessSub(self, cs_part):
2658 # type: (CommandSub) -> Piece
2659 # pretend it's quoted; no split or glob
2660 return Piece('__NO_PROCESS_SUB__', True, False)
2661
2662
2663# vim: sw=4