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

2666 lines, 1657 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.SparseArray):
1415 val = cast(value.SparseArray, UP_val)
1416 tmp = bash_impl.SparseArray_GetValues(val)
1417 elif case(value_e.BashAssoc):
1418 val = cast(value.BashAssoc, UP_val)
1419 tmp = bash_impl.BashAssoc_GetValues(val)
1420
1421 if tmp is not None:
1422 return value.Str(sep.join(tmp))
1423
1424 return val
1425
1426 def _ProcessUndef(self, val, name_tok, vsub_state):
1427 # type: (value_t, Token, VarSubState) -> value_t
1428 assert name_tok is not None
1429
1430 if val.tag() != value_e.Undef:
1431 return val
1432
1433 if vsub_state.array_ref is not None:
1434 array_tok = vsub_state.array_ref
1435 if self.exec_opts.nounset():
1436 e_die('Undefined array %r' % lexer.TokenVal(array_tok),
1437 array_tok)
1438 else:
1439 return value.BashArray([])
1440 else:
1441 if self.exec_opts.nounset():
1442 tok_str = lexer.TokenVal(name_tok)
1443 name = tok_str[1:] if tok_str.startswith('$') else tok_str
1444 e_die('Undefined variable %r' % name, name_tok)
1445 else:
1446 return value.Str('')
1447
1448 def _EvalBracketOp(self, val, part, quoted, vsub_state, vtest_place):
1449 # type: (value_t, BracedVarSub, bool, VarSubState, VTestPlace) -> value_t
1450
1451 if part.bracket_op:
1452 with tagswitch(part.bracket_op) as case:
1453 if case(bracket_op_e.WholeArray):
1454 val = self._WholeArray(val, part, quoted, vsub_state)
1455
1456 elif case(bracket_op_e.ArrayIndex):
1457 val = self._ArrayIndex(val, part, vtest_place)
1458
1459 else:
1460 raise AssertionError(part.bracket_op.tag())
1461
1462 else: # no bracket op
1463 var_name = vtest_place.name
1464 if (var_name is not None and
1465 val.tag() in (value_e.BashArray, value_e.SparseArray,
1466 value_e.BashAssoc)):
1467 if ShouldArrayDecay(var_name, self.exec_opts,
1468 not (part.prefix_op or part.suffix_op)):
1469 # for ${BASH_SOURCE}, etc.
1470 val = DecayArray(val)
1471 else:
1472 e_die(
1473 "Array %r can't be referred to as a scalar (without @ or *)"
1474 % var_name, loc.WordPart(part))
1475
1476 return val
1477
1478 def _VarRefValue(self, part, quoted, vsub_state, vtest_place):
1479 # type: (BracedVarSub, bool, VarSubState, VTestPlace) -> value_t
1480 """Duplicates some logic from _EvalBracedVarSub, but returns a
1481 value_t."""
1482
1483 # 1. Evaluate from (var_name, var_num, token Id) -> value
1484 if part.name_tok.id == Id.VSub_Name:
1485 vtest_place.name = part.var_name
1486 val = self.mem.GetValue(part.var_name)
1487
1488 elif part.name_tok.id == Id.VSub_Number:
1489 var_num = int(part.var_name)
1490 val = self._EvalVarNum(var_num)
1491
1492 else:
1493 # $* decays
1494 val = self._EvalSpecialVar(part.name_tok.id, quoted, vsub_state)
1495
1496 # update h-value (i.e., the holder of the current value)
1497 vsub_state.h_value = val
1498
1499 # We don't need var_index because it's only for L-Values of test ops?
1500 if self.exec_opts.eval_unsafe_arith():
1501 val = self._EvalBracketOp(val, part, quoted, vsub_state,
1502 vtest_place)
1503 else:
1504 with state.ctx_Option(self.mutable_opts,
1505 [option_i._allow_command_sub], False):
1506 val = self._EvalBracketOp(val, part, quoted, vsub_state,
1507 vtest_place)
1508
1509 return val
1510
1511 def _EvalBracedVarSub(self, part, part_vals, quoted):
1512 # type: (BracedVarSub, List[part_value_t], bool) -> None
1513 """
1514 Args:
1515 part_vals: output param to append to.
1516 """
1517 # We have different operators that interact in a non-obvious order.
1518 #
1519 # 1. bracket_op: value -> value, with side effect on vsub_state
1520 #
1521 # 2. prefix_op
1522 # a. length ${#x}: value -> value
1523 # b. var ref ${!ref}: can expand to an array
1524 #
1525 # 3. suffix_op:
1526 # a. no operator: you have a value
1527 # b. Test: value -> part_value[]
1528 # c. Other Suffix: value -> value
1529 #
1530 # 4. Process vsub_state.join_array here before returning.
1531 #
1532 # These cases are hard to distinguish:
1533 # - ${!prefix@} prefix query
1534 # - ${!array[@]} keys
1535 # - ${!ref} named reference
1536 # - ${!ref[0]} named reference
1537 #
1538 # I think we need several stages:
1539 #
1540 # 1. value: name, number, special, prefix query
1541 # 2. bracket_op
1542 # 3. prefix length -- this is TERMINAL
1543 # 4. indirection? Only for some of the ! cases
1544 # 5. string transformation suffix ops like ##
1545 # 6. test op
1546 # 7. vsub_state.join_array
1547
1548 # vsub_state.join_array is for joining "${a[*]}" and unquoted ${a[@]} AFTER
1549 # suffix ops are applied. If we take the length with a prefix op, the
1550 # distinction is ignored.
1551
1552 var_name = None # type: Optional[str] # used throughout the function
1553 vtest_place = VTestPlace(var_name, None) # For ${foo=default}
1554 vsub_state = VarSubState.CreateNull() # for $*, ${a[*]}, etc.
1555
1556 # 1. Evaluate from (var_name, var_num, token Id) -> value
1557 if part.name_tok.id == Id.VSub_Name:
1558 # Handle ${!prefix@} first, since that looks at names and not values
1559 # Do NOT handle ${!A[@]@a} here!
1560 if (part.prefix_op is not None and part.bracket_op is None and
1561 part.suffix_op is not None and
1562 part.suffix_op.tag() == suffix_op_e.Nullary):
1563 nullary_op = cast(Token, part.suffix_op)
1564 # ${!x@} but not ${!x@P}
1565 if consts.GetKind(nullary_op.id) == Kind.VOp3:
1566 names = self.mem.VarNamesStartingWith(part.var_name)
1567 names.sort()
1568
1569 if quoted and nullary_op.id == Id.VOp3_Star:
1570 sep = self.splitter.GetJoinChar()
1571 part_vals.append(Piece(sep.join(names), quoted, True))
1572 else:
1573 part_vals.append(part_value.Array(names, quoted))
1574 return # EARLY RETURN
1575
1576 var_name = part.var_name
1577 vtest_place.name = var_name # for _ApplyTestOp
1578
1579 val = self.mem.GetValue(var_name)
1580
1581 elif part.name_tok.id == Id.VSub_Number:
1582 var_num = int(part.var_name)
1583 val = self._EvalVarNum(var_num)
1584 else:
1585 # $* decays
1586 val = self._EvalSpecialVar(part.name_tok.id, quoted, vsub_state)
1587
1588 suffix_op_ = part.suffix_op
1589 if suffix_op_:
1590 UP_op = suffix_op_
1591 vsub_state.h_value = val
1592
1593 # 2. Bracket Op
1594 val = self._EvalBracketOp(val, part, quoted, vsub_state, vtest_place)
1595
1596 if part.prefix_op:
1597 if part.prefix_op.id == Id.VSub_Pound: # ${#var} for length
1598 # undef -> '' BEFORE length
1599 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1600
1601 n = self._Count(val, part.name_tok)
1602 part_vals.append(Piece(str(n), quoted, False))
1603 return # EARLY EXIT: nothing else can come after length
1604
1605 elif part.prefix_op.id == Id.VSub_Bang:
1606 if (part.bracket_op and
1607 part.bracket_op.tag() == bracket_op_e.WholeArray and
1608 not suffix_op_):
1609 # undef -> empty array
1610 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1611
1612 # ${!array[@]} to get indices/keys
1613 val = self._Keys(val, part.name_tok)
1614 # already set vsub_State.join_array ABOVE
1615 else:
1616 # Process ${!ref}. SURPRISE: ${!a[0]} is an indirect expansion unlike
1617 # ${!a[@]} !
1618 # ${!ref} can expand into an array if ref='array[@]'
1619
1620 # Clear it now that we have a var ref
1621 vtest_place.name = None
1622 vtest_place.index = None
1623
1624 val = self._EvalVarRef(val, part.name_tok, quoted,
1625 vsub_state, vtest_place)
1626
1627 else:
1628 raise AssertionError(part.prefix_op)
1629
1630 quoted2 = False # another bit for @Q
1631 if suffix_op_:
1632 op = suffix_op_ # could get rid of this alias
1633
1634 with tagswitch(suffix_op_) as case:
1635 if case(suffix_op_e.Nullary):
1636 op = cast(Token, UP_op)
1637 val, quoted2 = self._Nullary(val, op, var_name,
1638 part.name_tok, vsub_state)
1639
1640 elif case(suffix_op_e.Unary):
1641 op = cast(suffix_op.Unary, UP_op)
1642 if consts.GetKind(op.op.id) == Kind.VTest:
1643 # Note: _ProcessUndef (i.e., the conversion of undef ->
1644 # '') is not applied to the VTest operators such as
1645 # ${a:-def}, ${a+set}, etc.
1646 if self._ApplyTestOp(val, op, quoted, part_vals,
1647 vtest_place, part.name_tok,
1648 vsub_state):
1649 # e.g. to evaluate ${undef:-'default'}, we already appended
1650 # what we need
1651 return
1652
1653 else:
1654 # Other suffix: value -> value
1655 val = self._ProcessUndef(val, part.name_tok,
1656 vsub_state)
1657 val = self._ApplyUnarySuffixOp(val, op)
1658
1659 elif case(suffix_op_e.PatSub): # PatSub, vectorized
1660 op = cast(suffix_op.PatSub, UP_op)
1661 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1662 val = self._PatSub(val, op)
1663
1664 elif case(suffix_op_e.Slice):
1665 op = cast(suffix_op.Slice, UP_op)
1666 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1667 val = self._Slice(val, op, var_name, part)
1668
1669 elif case(suffix_op_e.Static):
1670 op = cast(suffix_op.Static, UP_op)
1671 e_die('Not implemented', op.tok)
1672
1673 else:
1674 raise AssertionError()
1675 else:
1676 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1677
1678 # After applying suffixes, process join_array here.
1679 val = self._JoinArray(val, quoted, vsub_state)
1680
1681 # For example, ${a} evaluates to value.Str(), but we want a
1682 # Piece().
1683 part_val = _ValueToPartValue(val, quoted or quoted2, part)
1684 part_vals.append(part_val)
1685
1686 def _ConcatPartVals(self, part_vals, location):
1687 # type: (List[part_value_t], loc_t) -> str
1688
1689 strs = [] # type: List[str]
1690 for part_val in part_vals:
1691 UP_part_val = part_val
1692 with tagswitch(part_val) as case:
1693 if case(part_value_e.String):
1694 part_val = cast(Piece, UP_part_val)
1695 s = part_val.s
1696
1697 elif case(part_value_e.Array):
1698 part_val = cast(part_value.Array, UP_part_val)
1699 if self.exec_opts.strict_array():
1700 # Examples: echo f > "$@"; local foo="$@"
1701 e_die("Illegal array word part (strict_array)",
1702 location)
1703 else:
1704 # It appears to not respect IFS
1705 # TODO: eliminate double join()?
1706 tmp = [s for s in part_val.strs if s is not None]
1707 s = ' '.join(tmp)
1708
1709 else:
1710 raise AssertionError()
1711
1712 strs.append(s)
1713
1714 return ''.join(strs)
1715
1716 def EvalBracedVarSubToString(self, part):
1717 # type: (BracedVarSub) -> str
1718 """For double quoted strings in YSH expressions.
1719
1720 Example: var x = "$foo-${foo}"
1721 """
1722 part_vals = [] # type: List[part_value_t]
1723 self._EvalBracedVarSub(part, part_vals, False)
1724 # blame ${ location
1725 return self._ConcatPartVals(part_vals, part.left)
1726
1727 def _EvalSimpleVarSub(self, part, part_vals, quoted):
1728 # type: (SimpleVarSub, List[part_value_t], bool) -> None
1729
1730 token = part.tok
1731
1732 vsub_state = VarSubState.CreateNull()
1733
1734 # 1. Evaluate from (var_name, var_num, Token) -> defined, value
1735 if token.id == Id.VSub_DollarName:
1736 var_name = lexer.LazyStr(token)
1737 # TODO: Special case for LINENO
1738 val = self.mem.GetValue(var_name)
1739 if val.tag() in (value_e.BashArray, value_e.SparseArray,
1740 value_e.BashAssoc):
1741 if ShouldArrayDecay(var_name, self.exec_opts):
1742 # for $BASH_SOURCE, etc.
1743 val = DecayArray(val)
1744 else:
1745 e_die(
1746 "Array %r can't be referred to as a scalar (without @ or *)"
1747 % var_name, token)
1748
1749 elif token.id == Id.VSub_Number:
1750 var_num = int(lexer.LazyStr(token))
1751 val = self._EvalVarNum(var_num)
1752
1753 else:
1754 val = self._EvalSpecialVar(token.id, quoted, vsub_state)
1755
1756 #log('SIMPLE %s', part)
1757 val = self._ProcessUndef(val, token, vsub_state)
1758 val = self._JoinArray(val, quoted, vsub_state)
1759
1760 v = _ValueToPartValue(val, quoted, part)
1761 part_vals.append(v)
1762
1763 def EvalSimpleVarSubToString(self, node):
1764 # type: (SimpleVarSub) -> str
1765 """For double quoted strings in YSH expressions.
1766
1767 Example: var x = "$foo-${foo}"
1768 """
1769 part_vals = [] # type: List[part_value_t]
1770 self._EvalSimpleVarSub(node, part_vals, False)
1771 return self._ConcatPartVals(part_vals, node.tok)
1772
1773 def _EvalExtGlob(self, part, part_vals):
1774 # type: (word_part.ExtGlob, List[part_value_t]) -> None
1775 """Evaluate @($x|'foo'|$(hostname)) and flatten it."""
1776 op = part.op
1777 if op.id == Id.ExtGlob_Comma:
1778 op_str = '@('
1779 else:
1780 op_str = lexer.LazyStr(op)
1781 # Do NOT split these.
1782 part_vals.append(Piece(op_str, False, False))
1783
1784 for i, w in enumerate(part.arms):
1785 if i != 0:
1786 part_vals.append(Piece('|', False, False)) # separator
1787 # FLATTEN the tree of extglob "arms".
1788 self._EvalWordToParts(w, part_vals, EXTGLOB_NESTED)
1789 part_vals.append(Piece(')', False, False)) # closing )
1790
1791 def _TranslateExtGlob(self, part_vals, w, glob_parts, fnmatch_parts):
1792 # type: (List[part_value_t], CompoundWord, List[str], List[str]) -> None
1793 """Translate a flattened WORD with an ExtGlob part to string patterns.
1794
1795 We need both glob and fnmatch patterns. _EvalExtGlob does the
1796 flattening.
1797 """
1798 for i, part_val in enumerate(part_vals):
1799 UP_part_val = part_val
1800 with tagswitch(part_val) as case:
1801 if case(part_value_e.String):
1802 part_val = cast(Piece, UP_part_val)
1803 if part_val.quoted and not self.exec_opts.noglob():
1804 s = glob_.GlobEscape(part_val.s)
1805 else:
1806 # e.g. the @( and | in @(foo|bar) aren't quoted
1807 s = part_val.s
1808 glob_parts.append(s)
1809 fnmatch_parts.append(s) # from _EvalExtGlob()
1810
1811 elif case(part_value_e.Array):
1812 # Disallow array
1813 e_die(
1814 "Extended globs and arrays can't appear in the same word",
1815 w)
1816
1817 elif case(part_value_e.ExtGlob):
1818 part_val = cast(part_value.ExtGlob, UP_part_val)
1819 # keep appending fnmatch_parts, but repplace glob_parts with '*'
1820 self._TranslateExtGlob(part_val.part_vals, w, [],
1821 fnmatch_parts)
1822 glob_parts.append('*')
1823
1824 else:
1825 raise AssertionError()
1826
1827 def _EvalWordPart(self, part, part_vals, flags):
1828 # type: (word_part_t, List[part_value_t], int) -> None
1829 """Evaluate a word part, appending to part_vals
1830
1831 Called by _EvalWordToParts, EvalWordToString, and _EvalDoubleQuoted.
1832 """
1833 quoted = bool(flags & QUOTED)
1834 is_subst = bool(flags & IS_SUBST)
1835
1836 UP_part = part
1837 with tagswitch(part) as case:
1838 if case(word_part_e.ShArrayLiteral):
1839 part = cast(ShArrayLiteral, UP_part)
1840 e_die("Unexpected array literal", loc.WordPart(part))
1841 elif case(word_part_e.BashAssocLiteral):
1842 part = cast(word_part.BashAssocLiteral, UP_part)
1843 e_die("Unexpected associative array literal",
1844 loc.WordPart(part))
1845
1846 elif case(word_part_e.Literal):
1847 part = cast(Token, UP_part)
1848 # Split if it's in a substitution.
1849 # That is: echo is not split, but ${foo:-echo} is split
1850 v = Piece(lexer.LazyStr(part), quoted, is_subst)
1851 part_vals.append(v)
1852
1853 elif case(word_part_e.EscapedLiteral):
1854 part = cast(word_part.EscapedLiteral, UP_part)
1855 v = Piece(part.ch, True, False)
1856 part_vals.append(v)
1857
1858 elif case(word_part_e.SingleQuoted):
1859 part = cast(SingleQuoted, UP_part)
1860 v = Piece(part.sval, True, False)
1861 part_vals.append(v)
1862
1863 elif case(word_part_e.DoubleQuoted):
1864 part = cast(DoubleQuoted, UP_part)
1865 self._EvalDoubleQuoted(part.parts, part_vals)
1866
1867 elif case(word_part_e.CommandSub):
1868 part = cast(CommandSub, UP_part)
1869 id_ = part.left_token.id
1870 if id_ in (Id.Left_DollarParen, Id.Left_AtParen,
1871 Id.Left_Backtick):
1872 sv = self._EvalCommandSub(part,
1873 quoted) # type: part_value_t
1874
1875 elif id_ in (Id.Left_ProcSubIn, Id.Left_ProcSubOut):
1876 sv = self._EvalProcessSub(part)
1877
1878 else:
1879 raise AssertionError(id_)
1880
1881 part_vals.append(sv)
1882
1883 elif case(word_part_e.SimpleVarSub):
1884 part = cast(SimpleVarSub, UP_part)
1885 self._EvalSimpleVarSub(part, part_vals, quoted)
1886
1887 elif case(word_part_e.BracedVarSub):
1888 part = cast(BracedVarSub, UP_part)
1889 self._EvalBracedVarSub(part, part_vals, quoted)
1890
1891 elif case(word_part_e.TildeSub):
1892 part = cast(word_part.TildeSub, UP_part)
1893 # We never parse a quoted string into a TildeSub.
1894 assert not quoted
1895 s = self.tilde_ev.Eval(part)
1896 v = Piece(s, True, False) # NOT split even when unquoted!
1897 part_vals.append(v)
1898
1899 elif case(word_part_e.ArithSub):
1900 part = cast(word_part.ArithSub, UP_part)
1901 num = self.arith_ev.EvalToBigInt(part.anode)
1902 v = Piece(mops.ToStr(num), quoted, not quoted)
1903 part_vals.append(v)
1904
1905 elif case(word_part_e.ExtGlob):
1906 part = cast(word_part.ExtGlob, UP_part)
1907 #if not self.exec_opts.extglob():
1908 # die() # disallow at runtime? Don't just decay
1909
1910 # Create a node to hold the flattened tree. The caller decides whether
1911 # to pass it to fnmatch() or replace it with '*' and pass it to glob().
1912 part_vals2 = [] # type: List[part_value_t]
1913 self._EvalExtGlob(part, part_vals2) # flattens tree
1914 part_vals.append(part_value.ExtGlob(part_vals2))
1915
1916 elif case(word_part_e.BashRegexGroup):
1917 part = cast(word_part.BashRegexGroup, UP_part)
1918
1919 part_vals.append(Piece('(', False, False)) # not quoted
1920 if part.child:
1921 self._EvalWordToParts(part.child, part_vals, 0)
1922 part_vals.append(Piece(')', False, False))
1923
1924 elif case(word_part_e.Splice):
1925 part = cast(word_part.Splice, UP_part)
1926 val = self.mem.GetValue(part.var_name)
1927
1928 strs = self.expr_ev.SpliceValue(val, part)
1929 part_vals.append(part_value.Array(strs, True))
1930
1931 elif case(word_part_e.ExprSub):
1932 part = cast(word_part.ExprSub, UP_part)
1933 part_val = self.expr_ev.EvalExprSub(part)
1934 part_vals.append(part_val)
1935
1936 elif case(word_part_e.ZshVarSub):
1937 part = cast(word_part.ZshVarSub, UP_part)
1938 e_die("ZSH var subs are parsed, but can't be evaluated",
1939 part.left)
1940
1941 else:
1942 raise AssertionError(part.tag())
1943
1944 def _EvalRhsWordToParts(self, w, part_vals, eval_flags=0):
1945 # type: (rhs_word_t, List[part_value_t], int) -> None
1946 quoted = bool(eval_flags & QUOTED)
1947
1948 UP_w = w
1949 with tagswitch(w) as case:
1950 if case(rhs_word_e.Empty):
1951 part_vals.append(Piece('', quoted, not quoted))
1952
1953 elif case(rhs_word_e.Compound):
1954 w = cast(CompoundWord, UP_w)
1955 self._EvalWordToParts(w, part_vals, eval_flags=eval_flags)
1956
1957 else:
1958 raise AssertionError()
1959
1960 def _EvalWordToParts(self, w, part_vals, eval_flags=0):
1961 # type: (CompoundWord, List[part_value_t], int) -> None
1962 """Helper for EvalRhsWord, EvalWordSequence, etc.
1963
1964 Returns:
1965 Appends to part_vals. Note that this is a TREE.
1966 """
1967 # Does the word have an extended glob? This is a special case because
1968 # of the way we use glob() and then fnmatch(..., FNM_EXTMATCH) to
1969 # implement extended globs. It's hard to carry that extra information
1970 # all the way past the word splitting stage.
1971
1972 # OSH semantic limitations: If a word has an extended glob part, then
1973 # 1. It can't have an array
1974 # 2. Word splitting of unquoted words isn't respected
1975
1976 word_part_vals = [] # type: List[part_value_t]
1977 has_extglob = False
1978 for p in w.parts:
1979 if p.tag() == word_part_e.ExtGlob:
1980 has_extglob = True
1981 self._EvalWordPart(p, word_part_vals, eval_flags)
1982
1983 # Caller REQUESTED extglob evaluation, AND we parsed word_part.ExtGlob()
1984 if has_extglob:
1985 if bool(eval_flags & EXTGLOB_FILES):
1986 # Treat the WHOLE word as a pattern. We need to TWO VARIANTS of the
1987 # word because of the way we use libc:
1988 # 1. With '*' for extglob parts
1989 # 2. With _EvalExtGlob() for extglob parts
1990
1991 glob_parts = [] # type: List[str]
1992 fnmatch_parts = [] # type: List[str]
1993 self._TranslateExtGlob(word_part_vals, w, glob_parts,
1994 fnmatch_parts)
1995
1996 #log('word_part_vals %s', word_part_vals)
1997 glob_pat = ''.join(glob_parts)
1998 fnmatch_pat = ''.join(fnmatch_parts)
1999 #log("glob %s fnmatch %s", glob_pat, fnmatch_pat)
2000
2001 results = [] # type: List[str]
2002 n = self.globber.ExpandExtended(glob_pat, fnmatch_pat, results)
2003 if n < 0:
2004 raise error.FailGlob(
2005 'Extended glob %r matched no files' % fnmatch_pat, w)
2006
2007 part_vals.append(part_value.Array(results, True))
2008 elif bool(eval_flags & EXTGLOB_NESTED):
2009 # We only glob at the TOP level of @(nested|@(pattern))
2010 part_vals.extend(word_part_vals)
2011 else:
2012 # e.g. simple_word_eval, assignment builtin
2013 e_die('Extended glob not allowed in this word', w)
2014 else:
2015 part_vals.extend(word_part_vals)
2016
2017 def _PartValsToString(self, part_vals, w, eval_flags, strs):
2018 # type: (List[part_value_t], CompoundWord, int, List[str]) -> None
2019 """Helper for EvalWordToString, similar to _ConcatPartVals() above.
2020
2021 Note: arg 'w' could just be a span ID
2022 """
2023 for part_val in part_vals:
2024 UP_part_val = part_val
2025 with tagswitch(part_val) as case:
2026 if case(part_value_e.String):
2027 part_val = cast(Piece, UP_part_val)
2028 s = part_val.s
2029 if part_val.quoted:
2030 if eval_flags & QUOTE_FNMATCH:
2031 # [[ foo == */"*".py ]] or case (*.py) or ${x%*.py} or ${x//*.py/}
2032 s = glob_.GlobEscape(s)
2033 elif eval_flags & QUOTE_ERE:
2034 s = glob_.ExtendedRegexEscape(s)
2035 strs.append(s)
2036
2037 elif case(part_value_e.Array):
2038 part_val = cast(part_value.Array, UP_part_val)
2039 if self.exec_opts.strict_array():
2040 # Examples: echo f > "$@"; local foo="$@"
2041
2042 # TODO: This attributes too coarsely, to the word rather than the
2043 # parts. Problem: the word is a TREE of parts, but we only have a
2044 # flat list of part_vals. The only case where we really get arrays
2045 # is "$@", "${a[@]}", "${a[@]//pat/replace}", etc.
2046 e_die(
2047 "This word should yield a string, but it contains an array",
2048 w)
2049
2050 # TODO: Maybe add detail like this.
2051 #e_die('RHS of assignment should only have strings. '
2052 # 'To assign arrays, use b=( "${a[@]}" )')
2053 else:
2054 # It appears to not respect IFS
2055 tmp = [s for s in part_val.strs if s is not None]
2056 s = ' '.join(tmp) # TODO: eliminate double join()?
2057 strs.append(s)
2058
2059 elif case(part_value_e.ExtGlob):
2060 part_val = cast(part_value.ExtGlob, UP_part_val)
2061
2062 # Extended globs are only allowed where we expect them!
2063 if not bool(eval_flags & QUOTE_FNMATCH):
2064 e_die('extended glob not allowed in this word', w)
2065
2066 # recursive call
2067 self._PartValsToString(part_val.part_vals, w, eval_flags,
2068 strs)
2069
2070 else:
2071 raise AssertionError()
2072
2073 def EvalWordToString(self, UP_w, eval_flags=0):
2074 # type: (word_t, int) -> value.Str
2075 """Given a word, return a string.
2076
2077 Flags can contain a quoting algorithm.
2078 """
2079 assert UP_w.tag() == word_e.Compound, UP_w
2080 w = cast(CompoundWord, UP_w)
2081
2082 if eval_flags == 0: # QUOTE_FNMATCH etc. breaks optimization
2083 fast_str = word_.FastStrEval(w)
2084 if fast_str is not None:
2085 return value.Str(fast_str)
2086
2087 # Could we additionally optimize a=$b, if we know $b isn't an array
2088 # etc.?
2089
2090 # Note: these empty lists are hot in fib benchmark
2091
2092 part_vals = [] # type: List[part_value_t]
2093 for p in w.parts:
2094 # this doesn't use eval_flags, which is slightly confusing
2095 self._EvalWordPart(p, part_vals, 0)
2096
2097 strs = [] # type: List[str]
2098 self._PartValsToString(part_vals, w, eval_flags, strs)
2099 return value.Str(''.join(strs))
2100
2101 def EvalWordToPattern(self, UP_w):
2102 # type: (rhs_word_t) -> Tuple[value.Str, bool]
2103 """Like EvalWordToString, but returns whether we got ExtGlob."""
2104 if UP_w.tag() == rhs_word_e.Empty:
2105 return value.Str(''), False
2106
2107 assert UP_w.tag() == rhs_word_e.Compound, UP_w
2108 w = cast(CompoundWord, UP_w)
2109
2110 has_extglob = False
2111 part_vals = [] # type: List[part_value_t]
2112 for p in w.parts:
2113 # this doesn't use eval_flags, which is slightly confusing
2114 self._EvalWordPart(p, part_vals, 0)
2115 if p.tag() == word_part_e.ExtGlob:
2116 has_extglob = True
2117
2118 strs = [] # type: List[str]
2119 self._PartValsToString(part_vals, w, QUOTE_FNMATCH, strs)
2120 return value.Str(''.join(strs)), has_extglob
2121
2122 def EvalForPlugin(self, w):
2123 # type: (CompoundWord) -> value.Str
2124 """Wrapper around EvalWordToString that prevents errors.
2125
2126 Runtime errors like $(( 1 / 0 )) and mutating $? like $(exit 42)
2127 are handled here.
2128
2129 Similar to ExprEvaluator.PluginCall().
2130 """
2131 with state.ctx_Registers(self.mem): # to "sandbox" $? and $PIPESTATUS
2132 try:
2133 val = self.EvalWordToString(w)
2134 except error.FatalRuntime as e:
2135 val = value.Str('<Runtime error: %s>' % e.UserErrorString())
2136
2137 except (IOError, OSError) as e:
2138 val = value.Str('<I/O error: %s>' % pyutil.strerror(e))
2139
2140 except KeyboardInterrupt:
2141 val = value.Str('<Ctrl-C>')
2142
2143 return val
2144
2145 def EvalRhsWord(self, UP_w):
2146 # type: (rhs_word_t) -> value_t
2147 """Used for RHS of assignment.
2148
2149 There is no splitting.
2150 """
2151 if UP_w.tag() == rhs_word_e.Empty:
2152 return value.Str('')
2153
2154 assert UP_w.tag() == word_e.Compound, UP_w
2155 w = cast(CompoundWord, UP_w)
2156
2157 if len(w.parts) == 1:
2158 part0 = w.parts[0]
2159 UP_part0 = part0
2160 tag = part0.tag()
2161 # Special case for a=(1 2). ShArrayLiteral won't appear in words that
2162 # don't look like assignments.
2163 if tag == word_part_e.ShArrayLiteral:
2164 part0 = cast(ShArrayLiteral, UP_part0)
2165 array_words = part0.words
2166 words = braces.BraceExpandWords(array_words)
2167 strs = self.EvalWordSequence(words)
2168 return value.BashArray(strs)
2169
2170 if tag == word_part_e.BashAssocLiteral:
2171 part0 = cast(word_part.BashAssocLiteral, UP_part0)
2172 d = NewDict() # type: Dict[str, str]
2173 for pair in part0.pairs:
2174 k = self.EvalWordToString(pair.key)
2175 v = self.EvalWordToString(pair.value)
2176 d[k.s] = v.s
2177 return value.BashAssoc(d)
2178
2179 # If RHS doesn't look like a=( ... ), then it must be a string.
2180 return self.EvalWordToString(w)
2181
2182 def _EvalWordFrame(self, frame, argv):
2183 # type: (List[Piece], List[str]) -> None
2184 all_empty = True
2185 all_quoted = True
2186 any_quoted = False
2187
2188 #log('--- frame %s', frame)
2189
2190 for piece in frame:
2191 if len(piece.s):
2192 all_empty = False
2193
2194 if piece.quoted:
2195 any_quoted = True
2196 else:
2197 all_quoted = False
2198
2199 # Elision of ${empty}${empty} but not $empty"$empty" or $empty""
2200 if all_empty and not any_quoted:
2201 return
2202
2203 # If every frag is quoted, e.g. "$a$b" or any part in "${a[@]}"x, then
2204 # don't do word splitting or globbing.
2205 if all_quoted:
2206 tmp = [piece.s for piece in frame]
2207 a = ''.join(tmp)
2208 argv.append(a)
2209 return
2210
2211 will_glob = not self.exec_opts.noglob()
2212
2213 if 0:
2214 log('---')
2215 log('FRAME')
2216 for i, piece in enumerate(frame):
2217 log('(%d) %s', i, piece)
2218 log('')
2219
2220 # Array of strings, some of which are BOTH IFS-escaped and GLOB escaped!
2221 frags = [] # type: List[str]
2222 for piece in frame:
2223 if will_glob and piece.quoted:
2224 frag = glob_.GlobEscape(piece.s)
2225 else:
2226 # If we have a literal \, then we turn it into \\\\.
2227 # Splitting takes \\\\ -> \\
2228 # Globbing takes \\ to \ if it doesn't match
2229 frag = _BackslashEscape(piece.s)
2230
2231 if piece.do_split:
2232 frag = _BackslashEscape(frag)
2233 else:
2234 frag = self.splitter.Escape(frag)
2235
2236 frags.append(frag)
2237
2238 if 0:
2239 log('---')
2240 log('FRAGS')
2241 for i, frag in enumerate(frags):
2242 log('(%d) %s', i, frag)
2243 log('')
2244
2245 flat = ''.join(frags)
2246 #log('flat: %r', flat)
2247
2248 args = self.splitter.SplitForWordEval(flat)
2249
2250 # space=' '; argv $space"". We have a quoted part, but we CANNOT elide.
2251 # Add it back and don't bother globbing.
2252 if len(args) == 0 and any_quoted:
2253 argv.append('')
2254 return
2255
2256 #log('split args: %r', args)
2257 for a in args:
2258 if glob_.LooksLikeGlob(a):
2259 n = self.globber.Expand(a, argv)
2260 if n < 0:
2261 # TODO: location info, with span IDs carried through the frame
2262 raise error.FailGlob('Pattern %r matched no files' % a,
2263 loc.Missing)
2264 else:
2265 argv.append(glob_.GlobUnescape(a))
2266
2267 def _EvalWordToArgv(self, w):
2268 # type: (CompoundWord) -> List[str]
2269 """Helper for _EvalAssignBuiltin.
2270
2271 Splitting and globbing are disabled for assignment builtins.
2272
2273 Example: declare -"${a[@]}" b=(1 2)
2274 where a is [x b=a d=a]
2275 """
2276 part_vals = [] # type: List[part_value_t]
2277 self._EvalWordToParts(w, part_vals, 0) # not double quoted
2278 frames = _MakeWordFrames(part_vals)
2279 argv = [] # type: List[str]
2280 for frame in frames:
2281 if len(frame): # empty array gives empty frame!
2282 tmp = [piece.s for piece in frame]
2283 argv.append(''.join(tmp)) # no split or glob
2284 #log('argv: %s', argv)
2285 return argv
2286
2287 def _EvalAssignBuiltin(self, builtin_id, arg0, words, meta_offset):
2288 # type: (builtin_t, str, List[CompoundWord], int) -> cmd_value.Assign
2289 """Handles both static and dynamic assignment, e.g.
2290
2291 x='foo=bar'
2292 local a=(1 2) $x
2293
2294 Grammar:
2295
2296 ('builtin' | 'command')* keyword flag* pair*
2297 flag = [-+].*
2298
2299 There is also command -p, but we haven't implemented it. Maybe just
2300 punt on it.
2301 """
2302 eval_to_pairs = True # except for -f and -F
2303 started_pairs = False
2304
2305 flags = [arg0] # initial flags like -p, and -f -F name1 name2
2306 flag_locs = [words[0]]
2307 assign_args = [] # type: List[AssignArg]
2308
2309 n = len(words)
2310 for i in xrange(meta_offset + 1, n): # skip first word
2311 w = words[i]
2312
2313 if word_.IsVarLike(w):
2314 started_pairs = True # Everything from now on is an assign_pair
2315
2316 if started_pairs:
2317 left_token, close_token, part_offset = word_.DetectShAssignment(
2318 w)
2319 if left_token: # Detected statically
2320 if left_token.id != Id.Lit_VarLike:
2321 # (not guaranteed since started_pairs is set twice)
2322 e_die('LHS array not allowed in assignment builtin', w)
2323
2324 if lexer.IsPlusEquals(left_token):
2325 var_name = lexer.TokenSliceRight(left_token, -2)
2326 append = True
2327 else:
2328 var_name = lexer.TokenSliceRight(left_token, -1)
2329 append = False
2330
2331 if part_offset == len(w.parts):
2332 rhs = rhs_word.Empty # type: rhs_word_t
2333 else:
2334 # tmp is for intersection of C++/MyPy type systems
2335 tmp = CompoundWord(w.parts[part_offset:])
2336 word_.TildeDetectAssign(tmp)
2337 rhs = tmp
2338
2339 with state.ctx_AssignBuiltin(self.mutable_opts):
2340 right = self.EvalRhsWord(rhs)
2341
2342 arg2 = AssignArg(var_name, right, append, w)
2343 assign_args.append(arg2)
2344
2345 else: # e.g. export $dynamic
2346 argv = self._EvalWordToArgv(w)
2347 for arg in argv:
2348 arg2 = _SplitAssignArg(arg, w)
2349 assign_args.append(arg2)
2350
2351 else:
2352 argv = self._EvalWordToArgv(w)
2353 for arg in argv:
2354 if arg.startswith('-') or arg.startswith('+'):
2355 # e.g. declare -r +r
2356 flags.append(arg)
2357 flag_locs.append(w)
2358
2359 # Shortcut that relies on -f and -F always meaning "function" for
2360 # all assignment builtins
2361 if 'f' in arg or 'F' in arg:
2362 eval_to_pairs = False
2363
2364 else: # e.g. export $dynamic
2365 if eval_to_pairs:
2366 arg2 = _SplitAssignArg(arg, w)
2367 assign_args.append(arg2)
2368 started_pairs = True
2369 else:
2370 flags.append(arg)
2371
2372 return cmd_value.Assign(builtin_id, flags, flag_locs, assign_args)
2373
2374 def _DetectAssignBuiltinStr(self, arg0, words, meta_offset):
2375 # type: (str, List[CompoundWord], int) -> Optional[cmd_value.Assign]
2376 builtin_id = consts.LookupAssignBuiltin(arg0)
2377 if builtin_id != consts.NO_INDEX:
2378 return self._EvalAssignBuiltin(builtin_id, arg0, words,
2379 meta_offset)
2380 return None
2381
2382 def _DetectAssignBuiltin(self, val0, words, meta_offset):
2383 # type: (part_value_t, List[CompoundWord], int) -> Optional[cmd_value.Assign]
2384 UP_val0 = val0
2385 if val0.tag() == part_value_e.String:
2386 val0 = cast(Piece, UP_val0)
2387 if not val0.quoted:
2388 return self._DetectAssignBuiltinStr(val0.s, words, meta_offset)
2389 return None
2390
2391 def SimpleEvalWordSequence2(self, words, is_last_cmd, allow_assign):
2392 # type: (List[CompoundWord], bool, bool) -> cmd_value_t
2393 """Simple word evaluation for YSH."""
2394 strs = [] # type: List[str]
2395 locs = [] # type: List[CompoundWord]
2396
2397 meta_offset = 0
2398 for i, w in enumerate(words):
2399 # No globbing in the first arg for command.Simple.
2400 if i == meta_offset and allow_assign:
2401 strs0 = self._EvalWordToArgv(w)
2402 # TODO: Remove this because YSH will disallow assignment
2403 # builtins? (including export?)
2404 if len(strs0) == 1:
2405 cmd_val = self._DetectAssignBuiltinStr(
2406 strs0[0], words, meta_offset)
2407 if cmd_val:
2408 return cmd_val
2409
2410 strs.extend(strs0)
2411 for _ in strs0:
2412 locs.append(w)
2413 continue
2414
2415 if glob_.LooksLikeStaticGlob(w):
2416 val = self.EvalWordToString(w) # respects strict-array
2417 num_appended = self.globber.Expand(val.s, strs)
2418 if num_appended < 0:
2419 raise error.FailGlob('Pattern %r matched no files' % val.s,
2420 w)
2421 for _ in xrange(num_appended):
2422 locs.append(w)
2423 continue
2424
2425 part_vals = [] # type: List[part_value_t]
2426 self._EvalWordToParts(w, part_vals, 0) # not quoted
2427
2428 if 0:
2429 log('')
2430 log('Static: part_vals after _EvalWordToParts:')
2431 for entry in part_vals:
2432 log(' %s', entry)
2433
2434 # Still need to process
2435 frames = _MakeWordFrames(part_vals)
2436
2437 if 0:
2438 log('')
2439 log('Static: frames after _MakeWordFrames:')
2440 for entry in frames:
2441 log(' %s', entry)
2442
2443 # We will still allow x"${a[@]"x, though it's deprecated by @a, which
2444 # disallows such expressions at parse time.
2445 for frame in frames:
2446 if len(frame): # empty array gives empty frame!
2447 tmp = [piece.s for piece in frame]
2448 strs.append(''.join(tmp)) # no split or glob
2449 locs.append(w)
2450
2451 assert len(strs) == len(locs), '%s vs. %d' % (strs, len(locs))
2452 return cmd_value.Argv(strs, locs, is_last_cmd, None, None)
2453
2454 def EvalWordSequence2(self, words, is_last_cmd, allow_assign=False):
2455 # type: (List[CompoundWord], bool, bool) -> cmd_value_t
2456 """Turns a list of Words into a list of strings.
2457
2458 Unlike the EvalWord*() methods, it does globbing.
2459
2460 Args:
2461 allow_assign: True for command.Simple, False for BashArray a=(1 2 3)
2462 """
2463 if self.exec_opts.simple_word_eval():
2464 return self.SimpleEvalWordSequence2(words, is_last_cmd,
2465 allow_assign)
2466
2467 # Parse time:
2468 # 1. brace expansion. TODO: Do at parse time.
2469 # 2. Tilde detection. DONE at parse time. Only if Id.Lit_Tilde is the
2470 # first WordPart.
2471 #
2472 # Run time:
2473 # 3. tilde sub, var sub, command sub, arith sub. These are all
2474 # "concurrent" on WordParts. (optional process sub with <() )
2475 # 4. word splitting. Can turn this off with a shell option? Definitely
2476 # off for oil.
2477 # 5. globbing -- several exec_opts affect this: nullglob, safeglob, etc.
2478
2479 #log('W %s', words)
2480 strs = [] # type: List[str]
2481 locs = [] # type: List[CompoundWord]
2482
2483 # 0 for declare x
2484 # 1 for builtin declare x
2485 # 2 for command builtin declare x
2486 # etc.
2487 meta_offset = 0
2488
2489 n = 0
2490 for i, w in enumerate(words):
2491 fast_str = word_.FastStrEval(w)
2492 if fast_str is not None:
2493 strs.append(fast_str)
2494 locs.append(w)
2495
2496 # e.g. the 'local' in 'local a=b c=d' will be here
2497 if allow_assign and i == meta_offset:
2498 cmd_val = self._DetectAssignBuiltinStr(
2499 fast_str, words, meta_offset)
2500 if cmd_val:
2501 return cmd_val
2502
2503 if i <= meta_offset and _DetectMetaBuiltinStr(fast_str):
2504 meta_offset += 1
2505
2506 # Bug fix: n must be updated on every loop iteration
2507 n = len(strs)
2508 assert len(strs) == len(locs), strs
2509 continue
2510
2511 part_vals = [] # type: List[part_value_t]
2512 self._EvalWordToParts(w, part_vals, EXTGLOB_FILES)
2513
2514 # DYNAMICALLY detect if we're going to run an assignment builtin, and
2515 # change the rest of the evaluation algorithm if so.
2516 #
2517 # We want to allow:
2518 # e=export
2519 # $e foo=bar
2520 #
2521 # But we don't want to evaluate the first word twice in the case of:
2522 # $(some-command) --flag
2523 if len(part_vals) == 1:
2524 if allow_assign and i == meta_offset:
2525 cmd_val = self._DetectAssignBuiltin(
2526 part_vals[0], words, meta_offset)
2527 if cmd_val:
2528 return cmd_val
2529
2530 if i <= meta_offset and _DetectMetaBuiltin(part_vals[0]):
2531 meta_offset += 1
2532
2533 if 0:
2534 log('')
2535 log('part_vals after _EvalWordToParts:')
2536 for entry in part_vals:
2537 log(' %s', entry)
2538
2539 frames = _MakeWordFrames(part_vals)
2540 if 0:
2541 log('')
2542 log('frames after _MakeWordFrames:')
2543 for entry in frames:
2544 log(' %s', entry)
2545
2546 # Do splitting and globbing. Each frame will append zero or more args.
2547 for frame in frames:
2548 self._EvalWordFrame(frame, strs)
2549
2550 # Fill in locations parallel to strs.
2551 n_next = len(strs)
2552 for _ in xrange(n_next - n):
2553 locs.append(w)
2554 n = n_next
2555
2556 # A non-assignment command.
2557 # NOTE: Can't look up builtins here like we did for assignment, because
2558 # functions can override builtins.
2559 assert len(strs) == len(locs), '%s vs. %d' % (strs, len(locs))
2560 return cmd_value.Argv(strs, locs, is_last_cmd, None, None)
2561
2562 def EvalWordSequence(self, words):
2563 # type: (List[CompoundWord]) -> List[str]
2564 """For arrays and for loops.
2565
2566 They don't allow assignment builtins.
2567 """
2568 # is_last_cmd is irrelevant
2569 cmd_val = self.EvalWordSequence2(words, False)
2570 assert cmd_val.tag() == cmd_value_e.Argv
2571 return cast(cmd_value.Argv, cmd_val).argv
2572
2573
2574class NormalWordEvaluator(AbstractWordEvaluator):
2575
2576 def __init__(
2577 self,
2578 mem, # type: state.Mem
2579 exec_opts, # type: optview.Exec
2580 mutable_opts, # type: state.MutableOpts
2581 tilde_ev, # type: TildeEvaluator
2582 splitter, # type: SplitContext
2583 errfmt, # type: ui.ErrorFormatter
2584 ):
2585 # type: (...) -> None
2586 AbstractWordEvaluator.__init__(self, mem, exec_opts, mutable_opts,
2587 tilde_ev, splitter, errfmt)
2588 self.shell_ex = None # type: _Executor
2589
2590 def CheckCircularDeps(self):
2591 # type: () -> None
2592 assert self.arith_ev is not None
2593 # Disabled for pure OSH
2594 #assert self.expr_ev is not None
2595 assert self.shell_ex is not None
2596 assert self.prompt_ev is not None
2597
2598 def _EvalCommandSub(self, cs_part, quoted):
2599 # type: (CommandSub, bool) -> part_value_t
2600 stdout_str = self.shell_ex.RunCommandSub(cs_part)
2601
2602 if cs_part.left_token.id == Id.Left_AtParen:
2603 # YSH splitting algorithm: does not depend on IFS
2604 try:
2605 strs = j8.SplitJ8Lines(stdout_str)
2606 except error.Decode as e:
2607 # status code 4 is special, for encode/decode errors.
2608 raise error.Structured(4, e.Message(), cs_part.left_token)
2609
2610 #strs = self.splitter.SplitForWordEval(stdout_str)
2611 return part_value.Array(strs, True)
2612 else:
2613 return Piece(stdout_str, quoted, not quoted)
2614
2615 def _EvalProcessSub(self, cs_part):
2616 # type: (CommandSub) -> Piece
2617 dev_path = self.shell_ex.RunProcessSub(cs_part)
2618 # pretend it's quoted; no split or glob
2619 return Piece(dev_path, True, False)
2620
2621
2622_DUMMY = '__NO_COMMAND_SUB__'
2623
2624
2625class CompletionWordEvaluator(AbstractWordEvaluator):
2626 """An evaluator that has no access to an executor.
2627
2628 NOTE: core/completion.py doesn't actually try to use these strings to
2629 complete. If you have something like 'echo $(echo hi)/f<TAB>', it sees the
2630 inner command as the last one, and knows that it is not at the end of the
2631 line.
2632 """
2633
2634 def __init__(
2635 self,
2636 mem, # type: state.Mem
2637 exec_opts, # type: optview.Exec
2638 mutable_opts, # type: state.MutableOpts
2639 tilde_ev, # type: TildeEvaluator
2640 splitter, # type: SplitContext
2641 errfmt, # type: ui.ErrorFormatter
2642 ):
2643 # type: (...) -> None
2644 AbstractWordEvaluator.__init__(self, mem, exec_opts, mutable_opts,
2645 tilde_ev, splitter, errfmt)
2646
2647 def CheckCircularDeps(self):
2648 # type: () -> None
2649 assert self.prompt_ev is not None
2650 assert self.arith_ev is not None
2651 assert self.expr_ev is not None
2652
2653 def _EvalCommandSub(self, cs_part, quoted):
2654 # type: (CommandSub, bool) -> part_value_t
2655 if cs_part.left_token.id == Id.Left_AtParen:
2656 return part_value.Array([_DUMMY], quoted)
2657 else:
2658 return Piece(_DUMMY, quoted, not quoted)
2659
2660 def _EvalProcessSub(self, cs_part):
2661 # type: (CommandSub) -> Piece
2662 # pretend it's quoted; no split or glob
2663 return Piece('__NO_PROCESS_SUB__', True, False)
2664
2665
2666# vim: sw=4