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

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