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

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