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

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