OILS / osh / word_eval.py View on Github | oilshell.org

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