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

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