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

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