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

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