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

2544 lines, 1551 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 _ProcessUndef,
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, vsub_token, vsub_state):
1040 # type: (value_t, Token, Optional[str], Token, VarSubState) -> Tuple[value.Str, bool]
1041
1042 quoted2 = False
1043 op_id = op.id
1044 if op_id == Id.VOp0_P:
1045 val = self._ProcessUndef(val, vsub_token, vsub_state)
1046 UP_val = val
1047 with tagswitch(val) as case:
1048 if case(value_e.Undef):
1049 result = value.Str('')
1050 elif case(value_e.Str):
1051 str_val = cast(value.Str, UP_val)
1052 prompt = self.prompt_ev.EvalPrompt(str_val)
1053 # readline gets rid of these, so we should too.
1054 p = prompt.replace('\x01', '').replace('\x02', '')
1055 result = value.Str(p)
1056 else:
1057 e_die("Can't use @P on %s" % ui.ValType(val), op)
1058
1059 elif op_id == Id.VOp0_Q:
1060 UP_val = val
1061 with tagswitch(val) as case:
1062 if case(value_e.Undef):
1063 # We need to issue an error when "-o nounset" is enabled.
1064 # Although we do not need to check val for value_e.Undef,
1065 # we call _ProcessUndef for consistency in the error
1066 # message.
1067 self._ProcessUndef(val, vsub_token, vsub_state)
1068
1069 # For unset variables, we do not generate any quoted words.
1070 result = value.Str('')
1071
1072 elif case(value_e.Str):
1073 str_val = cast(value.Str, UP_val)
1074 result = value.Str(j8_lite.MaybeShellEncode(str_val.s))
1075 # oddly, 'echo ${x@Q}' is equivalent to 'echo "${x@Q}"' in
1076 # bash
1077 quoted2 = True
1078 elif case(value_e.BashArray, value_e.BashAssoc):
1079 if val.tag() == value_e.BashArray:
1080 val = cast(value.BashArray, UP_val)
1081 values = [s for s in bash_impl.BashArray_GetValues(val) if s is not None]
1082 elif val.tag() == value_e.BashAssoc:
1083 val = cast(value.BashAssoc, UP_val)
1084 values = bash_impl.BashAssoc_GetValues(val)
1085 else:
1086 raise AssertionError()
1087
1088 tmp = [
1089 # TODO: should use fastfunc.ShellEncode
1090 j8_lite.MaybeShellEncode(s) for s in values
1091 ]
1092 result = value.Str(' '.join(tmp))
1093 else:
1094 e_die("Can't use @Q on %s" % ui.ValType(val), op)
1095
1096 elif op_id == Id.VOp0_a:
1097 val = self._ProcessUndef(val, vsub_token, vsub_state)
1098 UP_val = val
1099 # We're ONLY simluating -a and -A, not -r -x -n for now. See
1100 # spec/ble-idioms.test.sh.
1101 chars = [] # type: List[str]
1102 with tagswitch(val) as case:
1103 if case(value_e.BashArray):
1104 chars.append('a')
1105 elif case(value_e.BashAssoc):
1106 chars.append('A')
1107
1108 if var_name is not None: # e.g. ${?@a} is allowed
1109 cell = self.mem.GetCell(var_name)
1110 if cell:
1111 if cell.readonly:
1112 chars.append('r')
1113 if cell.exported:
1114 chars.append('x')
1115 if cell.nameref:
1116 chars.append('n')
1117
1118 result = value.Str(''.join(chars))
1119
1120 else:
1121 e_die('Var op %r not implemented' % lexer.TokenVal(op), op)
1122
1123 return result, quoted2
1124
1125 def _WholeArray(self, val, part, quoted, vsub_state):
1126 # type: (value_t, BracedVarSub, bool, VarSubState) -> value_t
1127 op_id = cast(bracket_op.WholeArray, part.bracket_op).op_id
1128
1129 if op_id == Id.Lit_At:
1130 op_str = '@'
1131 vsub_state.join_array = not quoted # ${a[@]} decays but "${a[@]}" doesn't
1132 elif op_id == Id.Arith_Star:
1133 op_str = '*'
1134 vsub_state.join_array = True # both ${a[*]} and "${a[*]}" decay
1135 else:
1136 raise AssertionError(op_id) # unknown
1137
1138 with tagswitch(val) as case2:
1139 if case2(value_e.Undef):
1140 # For an undefined array, we save the token of the array
1141 # reference for the later error message.
1142 vsub_state.array_ref = part.name_tok
1143 elif case2(value_e.Str):
1144 if self.exec_opts.strict_array():
1145 e_die("Can't index string with %s" % op_str,
1146 loc.WordPart(part))
1147 elif case2(value_e.BashArray, value_e.SparseArray,
1148 value_e.BashAssoc):
1149 pass # no-op
1150 else:
1151 # The other YSH types such as List, Dict, and Float are not
1152 # supported. Error messages will be printed later, so we here
1153 # return the unsupported objects without modification.
1154 pass # no-op
1155
1156 return val
1157
1158 def _ArrayIndex(self, val, part, vtest_place):
1159 # type: (value_t, BracedVarSub, VTestPlace) -> value_t
1160 """Process a numeric array index like ${a[i+1]}"""
1161 anode = cast(bracket_op.ArrayIndex, part.bracket_op).expr
1162
1163 UP_val = val
1164 with tagswitch(val) as case2:
1165 if case2(value_e.Undef):
1166 pass # it will be checked later
1167
1168 elif case2(value_e.Str):
1169 # Bash treats any string as an array, so we can't add our own
1170 # behavior here without making valid OSH invalid bash.
1171 e_die("Can't index string %r with integer" % part.var_name,
1172 part.name_tok)
1173
1174 elif case2(value_e.BashArray):
1175 array_val = cast(value.BashArray, UP_val)
1176 index = self.arith_ev.EvalToInt(anode)
1177 vtest_place.index = a_index.Int(index)
1178
1179 s, error_code = bash_impl.BashArray_GetElement(
1180 array_val, index)
1181 if error_code == error_code_e.IndexOutOfRange:
1182 # Note: Bash outputs warning but does not make it a real
1183 # error. We follow the Bash behavior here.
1184 self.errfmt.Print_(
1185 "Index %d out of bounds for array of length %d" %
1186 (index, bash_impl.BashArray_Length(array_val)),
1187 blame_loc=part.name_tok)
1188
1189 if s is None:
1190 val = value.Undef
1191 else:
1192 val = value.Str(s)
1193
1194 elif case2(value_e.SparseArray):
1195 sparse_val = cast(value.SparseArray, UP_val)
1196 big_index = self.arith_ev.EvalToBigInt(anode)
1197 vtest_place.index = a_index.Int(mops.BigTruncate(big_index))
1198
1199 s, error_code = bash_impl.SparseArray_GetElement(
1200 sparse_val, big_index)
1201 if error_code == error_code_e.IndexOutOfRange:
1202 # Note: Bash outputs warning but does not make it a real
1203 # error. We follow the Bash behavior here.
1204 big_length = bash_impl.SparseArray_Length(sparse_val)
1205 self.errfmt.Print_(
1206 "Index %s out of bounds for array of length %s" %
1207 (mops.ToStr(big_index), mops.ToStr(big_length)),
1208 blame_loc=part.name_tok)
1209
1210 if s is None:
1211 val = value.Undef
1212 else:
1213 val = value.Str(s)
1214
1215 elif case2(value_e.BashAssoc):
1216 assoc_val = cast(value.BashAssoc, UP_val)
1217 # Location could also be attached to bracket_op? But
1218 # arith_expr.VarSub works OK too
1219 key = self.arith_ev.EvalWordToString(
1220 anode, blame_loc=location.TokenForArith(anode))
1221
1222 vtest_place.index = a_index.Str(key) # out param
1223 s = bash_impl.BashAssoc_GetElement(assoc_val, key)
1224
1225 if s is None:
1226 val = value.Undef
1227 else:
1228 val = value.Str(s)
1229
1230 else:
1231 raise error.TypeErr(val,
1232 'Index op expected BashArray, BashAssoc',
1233 loc.WordPart(part))
1234
1235 return val
1236
1237 def _EvalDoubleQuoted(self, parts, part_vals):
1238 # type: (List[word_part_t], List[part_value_t]) -> None
1239 """Evaluate parts of a DoubleQuoted part.
1240
1241 Args:
1242 part_vals: output param to append to.
1243 """
1244 # Example of returning array:
1245 # $ a=(1 2); b=(3); $ c=(4 5)
1246 # $ argv "${a[@]}${b[@]}${c[@]}"
1247 # ['1', '234', '5']
1248 #
1249 # Example of multiple parts
1250 # $ argv "${a[@]}${undef[@]:-${c[@]}}"
1251 # ['1', '24', '5']
1252
1253 # Special case for "". The parser outputs (DoubleQuoted []), instead
1254 # of (DoubleQuoted [Literal '']). This is better but it means we
1255 # have to check for it.
1256 if len(parts) == 0:
1257 v = Piece('', True, False)
1258 part_vals.append(v)
1259 return
1260
1261 for p in parts:
1262 self._EvalWordPart(p, part_vals, QUOTED)
1263
1264 def EvalDoubleQuotedToString(self, dq_part):
1265 # type: (DoubleQuoted) -> str
1266 """For double quoted strings in YSH expressions.
1267
1268 Example: var x = "$foo-${foo}"
1269 """
1270 part_vals = [] # type: List[part_value_t]
1271 self._EvalDoubleQuoted(dq_part.parts, part_vals)
1272 return self._ConcatPartVals(part_vals, dq_part.left)
1273
1274 def _DecayArray(self, val):
1275 # type: (value.BashArray) -> value.Str
1276 """Decay $* to a string."""
1277 assert val.tag() == value_e.BashArray, val
1278 sep = self.splitter.GetJoinChar()
1279 tmp = [s for s in bash_impl.BashArray_GetValues(val) if s is not None]
1280 return value.Str(sep.join(tmp))
1281
1282 def _ProcessUndef(self, val, name_tok, vsub_state):
1283 # type: (value_t, Token, VarSubState) -> value_t
1284 assert name_tok is not None
1285
1286 if val.tag() != value_e.Undef:
1287 return val
1288
1289 if vsub_state.array_ref is not None:
1290 array_tok = vsub_state.array_ref
1291 if self.exec_opts.nounset():
1292 e_die('Undefined array %r' % lexer.TokenVal(array_tok),
1293 array_tok)
1294 else:
1295 return value.BashArray([])
1296 else:
1297 if self.exec_opts.nounset():
1298 tok_str = lexer.TokenVal(name_tok)
1299 name = tok_str[1:] if tok_str.startswith('$') else tok_str
1300 e_die('Undefined variable %r' % name, name_tok)
1301 else:
1302 return value.Str('')
1303
1304 def _EvalBracketOp(self, val, part, quoted, vsub_state, vtest_place):
1305 # type: (value_t, BracedVarSub, bool, VarSubState, VTestPlace) -> value_t
1306
1307 if part.bracket_op:
1308 with tagswitch(part.bracket_op) as case:
1309 if case(bracket_op_e.WholeArray):
1310 val = self._WholeArray(val, part, quoted, vsub_state)
1311
1312 elif case(bracket_op_e.ArrayIndex):
1313 val = self._ArrayIndex(val, part, vtest_place)
1314
1315 else:
1316 raise AssertionError(part.bracket_op.tag())
1317
1318 else: # no bracket op
1319 var_name = vtest_place.name
1320 if (var_name is not None and
1321 val.tag() in (value_e.BashArray, value_e.BashAssoc) and
1322 not vsub_state.is_type_query):
1323 if ShouldArrayDecay(var_name, self.exec_opts,
1324 not (part.prefix_op or part.suffix_op)):
1325 # for ${BASH_SOURCE}, etc.
1326 val = DecayArray(val)
1327 else:
1328 e_die(
1329 "Array %r can't be referred to as a scalar (without @ or *)"
1330 % var_name, loc.WordPart(part))
1331
1332 return val
1333
1334 def _VarRefValue(self, part, quoted, vsub_state, vtest_place):
1335 # type: (BracedVarSub, bool, VarSubState, VTestPlace) -> value_t
1336 """Duplicates some logic from _EvalBracedVarSub, but returns a
1337 value_t."""
1338
1339 # 1. Evaluate from (var_name, var_num, token Id) -> value
1340 if part.name_tok.id == Id.VSub_Name:
1341 vtest_place.name = part.var_name
1342 val = self.mem.GetValue(part.var_name)
1343
1344 elif part.name_tok.id == Id.VSub_Number:
1345 var_num = int(part.var_name)
1346 val = self._EvalVarNum(var_num)
1347
1348 else:
1349 # $* decays
1350 val = self._EvalSpecialVar(part.name_tok.id, quoted, vsub_state)
1351
1352 # We don't need var_index because it's only for L-Values of test ops?
1353 if self.exec_opts.eval_unsafe_arith():
1354 val = self._EvalBracketOp(val, part, quoted, vsub_state,
1355 vtest_place)
1356 else:
1357 with state.ctx_Option(self.mutable_opts,
1358 [option_i._allow_command_sub], False):
1359 val = self._EvalBracketOp(val, part, quoted, vsub_state,
1360 vtest_place)
1361
1362 return val
1363
1364 def _EvalBracedVarSub(self, part, part_vals, quoted):
1365 # type: (BracedVarSub, List[part_value_t], bool) -> None
1366 """
1367 Args:
1368 part_vals: output param to append to.
1369 """
1370 # We have different operators that interact in a non-obvious order.
1371 #
1372 # 1. bracket_op: value -> value, with side effect on vsub_state
1373 #
1374 # 2. prefix_op
1375 # a. length ${#x}: value -> value
1376 # b. var ref ${!ref}: can expand to an array
1377 #
1378 # 3. suffix_op:
1379 # a. no operator: you have a value
1380 # b. Test: value -> part_value[]
1381 # c. Other Suffix: value -> value
1382 #
1383 # 4. Process vsub_state.join_array here before returning.
1384 #
1385 # These cases are hard to distinguish:
1386 # - ${!prefix@} prefix query
1387 # - ${!array[@]} keys
1388 # - ${!ref} named reference
1389 # - ${!ref[0]} named reference
1390 #
1391 # I think we need several stages:
1392 #
1393 # 1. value: name, number, special, prefix query
1394 # 2. bracket_op
1395 # 3. prefix length -- this is TERMINAL
1396 # 4. indirection? Only for some of the ! cases
1397 # 5. string transformation suffix ops like ##
1398 # 6. test op
1399 # 7. vsub_state.join_array
1400
1401 # vsub_state.join_array is for joining "${a[*]}" and unquoted ${a[@]} AFTER
1402 # suffix ops are applied. If we take the length with a prefix op, the
1403 # distinction is ignored.
1404
1405 var_name = None # type: Optional[str] # used throughout the function
1406 vtest_place = VTestPlace(var_name, None) # For ${foo=default}
1407 vsub_state = VarSubState.CreateNull() # for $*, ${a[*]}, etc.
1408
1409 # 1. Evaluate from (var_name, var_num, token Id) -> value
1410 if part.name_tok.id == Id.VSub_Name:
1411 # Handle ${!prefix@} first, since that looks at names and not values
1412 # Do NOT handle ${!A[@]@a} here!
1413 if (part.prefix_op is not None and part.bracket_op is None and
1414 part.suffix_op is not None and
1415 part.suffix_op.tag() == suffix_op_e.Nullary):
1416 nullary_op = cast(Token, part.suffix_op)
1417 # ${!x@} but not ${!x@P}
1418 if consts.GetKind(nullary_op.id) == Kind.VOp3:
1419 names = self.mem.VarNamesStartingWith(part.var_name)
1420 names.sort()
1421
1422 if quoted and nullary_op.id == Id.VOp3_At:
1423 part_vals.append(part_value.Array(names))
1424 else:
1425 sep = self.splitter.GetJoinChar()
1426 part_vals.append(Piece(sep.join(names), quoted, True))
1427 return # EARLY RETURN
1428
1429 var_name = part.var_name
1430 vtest_place.name = var_name # for _ApplyTestOp
1431
1432 val = self.mem.GetValue(var_name)
1433
1434 elif part.name_tok.id == Id.VSub_Number:
1435 var_num = int(part.var_name)
1436 val = self._EvalVarNum(var_num)
1437 else:
1438 # $* decays
1439 val = self._EvalSpecialVar(part.name_tok.id, quoted, vsub_state)
1440
1441 suffix_op_ = part.suffix_op
1442 if suffix_op_:
1443 UP_op = suffix_op_
1444 with tagswitch(suffix_op_) as case:
1445 if case(suffix_op_e.Nullary):
1446 suffix_op_ = cast(Token, UP_op)
1447
1448 # Type query ${array@a} is a STRING, not an array
1449 # NOTE: ${array@Q} is ${array[0]@Q} in bash, which is different than
1450 # ${array[@]@Q}
1451 if suffix_op_.id == Id.VOp0_a:
1452 vsub_state.is_type_query = True
1453
1454 # 2. Bracket Op
1455 val = self._EvalBracketOp(val, part, quoted, vsub_state, vtest_place)
1456
1457 if part.prefix_op:
1458 if part.prefix_op.id == Id.VSub_Pound: # ${#var} for length
1459 # undef -> '' BEFORE length
1460 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1461
1462 n = self._Count(val, part.name_tok)
1463 part_vals.append(Piece(str(n), quoted, False))
1464 return # EARLY EXIT: nothing else can come after length
1465
1466 elif part.prefix_op.id == Id.VSub_Bang:
1467 if (part.bracket_op and
1468 part.bracket_op.tag() == bracket_op_e.WholeArray):
1469 # undef -> empty array
1470 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1471
1472 # ${!a[@]-'default'} is a non-fatal runtime error in bash.
1473 # Here it's fatal.
1474 if suffix_op_ and suffix_op_.tag() == suffix_op_e.Unary:
1475 unary_op = cast(suffix_op.Unary, suffix_op_)
1476 if consts.GetKind(unary_op.op.id) == Kind.VTest:
1477 e_die(
1478 'Test operation not allowed with ${!array[@]}',
1479 unary_op.op)
1480
1481 # ${!array[@]} to get indices/keys
1482 val = self._Keys(val, part.name_tok)
1483 # already set vsub_State.join_array ABOVE
1484 else:
1485 # Process ${!ref}. SURPRISE: ${!a[0]} is an indirect expansion unlike
1486 # ${!a[@]} !
1487 # ${!ref} can expand into an array if ref='array[@]'
1488
1489 # Clear it now that we have a var ref
1490 vtest_place.name = None
1491 vtest_place.index = None
1492
1493 val = self._EvalVarRef(val, part.name_tok, quoted, vsub_state,
1494 vtest_place)
1495
1496 else:
1497 raise AssertionError(part.prefix_op)
1498
1499 quoted2 = False # another bit for @Q
1500 if suffix_op_:
1501 op = suffix_op_ # could get rid of this alias
1502
1503 with tagswitch(suffix_op_) as case:
1504 if case(suffix_op_e.Nullary):
1505 op = cast(Token, UP_op)
1506 val, quoted2 = self._Nullary(val, op, var_name, part.name_tok,
1507 vsub_state)
1508
1509 elif case(suffix_op_e.Unary):
1510 op = cast(suffix_op.Unary, UP_op)
1511 if consts.GetKind(op.op.id) == Kind.VTest:
1512 # Note: _ProcessUndef (i.e., the conversion of undef ->
1513 # '') is not applied to the VTest operators such as
1514 # ${a:-def}, ${a+set}, etc.
1515 if self._ApplyTestOp(val, op, quoted, part_vals,
1516 vtest_place, part.name_tok):
1517 # e.g. to evaluate ${undef:-'default'}, we already appended
1518 # what we need
1519 return
1520
1521 else:
1522 # Other suffix: value -> value
1523 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1524 val = self._ApplyUnarySuffixOp(val, op)
1525
1526 elif case(suffix_op_e.PatSub): # PatSub, vectorized
1527 op = cast(suffix_op.PatSub, UP_op)
1528 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1529 val = self._PatSub(val, op)
1530
1531 elif case(suffix_op_e.Slice):
1532 op = cast(suffix_op.Slice, UP_op)
1533 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1534 val = self._Slice(val, op, var_name, part)
1535
1536 elif case(suffix_op_e.Static):
1537 op = cast(suffix_op.Static, UP_op)
1538 e_die('Not implemented', op.tok)
1539
1540 else:
1541 raise AssertionError()
1542 else:
1543 val = self._ProcessUndef(val, part.name_tok, vsub_state)
1544
1545 # After applying suffixes, process join_array here.
1546 UP_val = val
1547 if val.tag() == value_e.BashArray:
1548 array_val = cast(value.BashArray, UP_val)
1549 if vsub_state.join_array:
1550 val = self._DecayArray(array_val)
1551 else:
1552 val = array_val
1553
1554 # For example, ${a} evaluates to value.Str(), but we want a
1555 # Piece().
1556 part_val = _ValueToPartValue(val, quoted or quoted2, part)
1557 part_vals.append(part_val)
1558
1559 def _ConcatPartVals(self, part_vals, location):
1560 # type: (List[part_value_t], loc_t) -> str
1561
1562 strs = [] # type: List[str]
1563 for part_val in part_vals:
1564 UP_part_val = part_val
1565 with tagswitch(part_val) as case:
1566 if case(part_value_e.String):
1567 part_val = cast(Piece, UP_part_val)
1568 s = part_val.s
1569
1570 elif case(part_value_e.Array):
1571 part_val = cast(part_value.Array, UP_part_val)
1572 if self.exec_opts.strict_array():
1573 # Examples: echo f > "$@"; local foo="$@"
1574 e_die("Illegal array word part (strict_array)",
1575 location)
1576 else:
1577 # It appears to not respect IFS
1578 # TODO: eliminate double join()?
1579 tmp = [s for s in part_val.strs if s is not None]
1580 s = ' '.join(tmp)
1581
1582 else:
1583 raise AssertionError()
1584
1585 strs.append(s)
1586
1587 return ''.join(strs)
1588
1589 def EvalBracedVarSubToString(self, part):
1590 # type: (BracedVarSub) -> str
1591 """For double quoted strings in YSH expressions.
1592
1593 Example: var x = "$foo-${foo}"
1594 """
1595 part_vals = [] # type: List[part_value_t]
1596 self._EvalBracedVarSub(part, part_vals, False)
1597 # blame ${ location
1598 return self._ConcatPartVals(part_vals, part.left)
1599
1600 def _EvalSimpleVarSub(self, part, part_vals, quoted):
1601 # type: (SimpleVarSub, List[part_value_t], bool) -> None
1602
1603 token = part.tok
1604
1605 vsub_state = VarSubState.CreateNull()
1606
1607 # 1. Evaluate from (var_name, var_num, Token) -> defined, value
1608 if token.id == Id.VSub_DollarName:
1609 var_name = lexer.LazyStr(token)
1610 # TODO: Special case for LINENO
1611 val = self.mem.GetValue(var_name)
1612 if val.tag() in (value_e.BashArray, value_e.BashAssoc):
1613 if ShouldArrayDecay(var_name, self.exec_opts):
1614 # for $BASH_SOURCE, etc.
1615 val = DecayArray(val)
1616 else:
1617 e_die(
1618 "Array %r can't be referred to as a scalar (without @ or *)"
1619 % var_name, token)
1620
1621 elif token.id == Id.VSub_Number:
1622 var_num = int(lexer.LazyStr(token))
1623 val = self._EvalVarNum(var_num)
1624
1625 else:
1626 val = self._EvalSpecialVar(token.id, quoted, vsub_state)
1627
1628 #log('SIMPLE %s', part)
1629 val = self._ProcessUndef(val, token, vsub_state)
1630 UP_val = val
1631 if val.tag() == value_e.BashArray:
1632 array_val = cast(value.BashArray, UP_val)
1633 if vsub_state.join_array:
1634 val = self._DecayArray(array_val)
1635 else:
1636 val = array_val
1637
1638 v = _ValueToPartValue(val, quoted, part)
1639 part_vals.append(v)
1640
1641 def EvalSimpleVarSubToString(self, node):
1642 # type: (SimpleVarSub) -> str
1643 """For double quoted strings in YSH expressions.
1644
1645 Example: var x = "$foo-${foo}"
1646 """
1647 part_vals = [] # type: List[part_value_t]
1648 self._EvalSimpleVarSub(node, part_vals, False)
1649 return self._ConcatPartVals(part_vals, node.tok)
1650
1651 def _EvalExtGlob(self, part, part_vals):
1652 # type: (word_part.ExtGlob, List[part_value_t]) -> None
1653 """Evaluate @($x|'foo'|$(hostname)) and flatten it."""
1654 op = part.op
1655 if op.id == Id.ExtGlob_Comma:
1656 op_str = '@('
1657 else:
1658 op_str = lexer.LazyStr(op)
1659 # Do NOT split these.
1660 part_vals.append(Piece(op_str, False, False))
1661
1662 for i, w in enumerate(part.arms):
1663 if i != 0:
1664 part_vals.append(Piece('|', False, False)) # separator
1665 # FLATTEN the tree of extglob "arms".
1666 self._EvalWordToParts(w, part_vals, EXTGLOB_NESTED)
1667 part_vals.append(Piece(')', False, False)) # closing )
1668
1669 def _TranslateExtGlob(self, part_vals, w, glob_parts, fnmatch_parts):
1670 # type: (List[part_value_t], CompoundWord, List[str], List[str]) -> None
1671 """Translate a flattened WORD with an ExtGlob part to string patterns.
1672
1673 We need both glob and fnmatch patterns. _EvalExtGlob does the
1674 flattening.
1675 """
1676 for i, part_val in enumerate(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 if part_val.quoted and not self.exec_opts.noglob():
1682 s = glob_.GlobEscape(part_val.s)
1683 else:
1684 # e.g. the @( and | in @(foo|bar) aren't quoted
1685 s = part_val.s
1686 glob_parts.append(s)
1687 fnmatch_parts.append(s) # from _EvalExtGlob()
1688
1689 elif case(part_value_e.Array):
1690 # Disallow array
1691 e_die(
1692 "Extended globs and arrays can't appear in the same word",
1693 w)
1694
1695 elif case(part_value_e.ExtGlob):
1696 part_val = cast(part_value.ExtGlob, UP_part_val)
1697 # keep appending fnmatch_parts, but repplace glob_parts with '*'
1698 self._TranslateExtGlob(part_val.part_vals, w, [],
1699 fnmatch_parts)
1700 glob_parts.append('*')
1701
1702 else:
1703 raise AssertionError()
1704
1705 def _EvalWordPart(self, part, part_vals, flags):
1706 # type: (word_part_t, List[part_value_t], int) -> None
1707 """Evaluate a word part, appending to part_vals
1708
1709 Called by _EvalWordToParts, EvalWordToString, and _EvalDoubleQuoted.
1710 """
1711 quoted = bool(flags & QUOTED)
1712 is_subst = bool(flags & IS_SUBST)
1713
1714 UP_part = part
1715 with tagswitch(part) as case:
1716 if case(word_part_e.ShArrayLiteral):
1717 part = cast(ShArrayLiteral, UP_part)
1718 e_die("Unexpected array literal", loc.WordPart(part))
1719 elif case(word_part_e.BashAssocLiteral):
1720 part = cast(word_part.BashAssocLiteral, UP_part)
1721 e_die("Unexpected associative array literal",
1722 loc.WordPart(part))
1723
1724 elif case(word_part_e.Literal):
1725 part = cast(Token, UP_part)
1726 # Split if it's in a substitution.
1727 # That is: echo is not split, but ${foo:-echo} is split
1728 v = Piece(lexer.LazyStr(part), quoted, is_subst)
1729 part_vals.append(v)
1730
1731 elif case(word_part_e.EscapedLiteral):
1732 part = cast(word_part.EscapedLiteral, UP_part)
1733 v = Piece(part.ch, True, False)
1734 part_vals.append(v)
1735
1736 elif case(word_part_e.SingleQuoted):
1737 part = cast(SingleQuoted, UP_part)
1738 v = Piece(part.sval, True, False)
1739 part_vals.append(v)
1740
1741 elif case(word_part_e.DoubleQuoted):
1742 part = cast(DoubleQuoted, UP_part)
1743 self._EvalDoubleQuoted(part.parts, part_vals)
1744
1745 elif case(word_part_e.CommandSub):
1746 part = cast(CommandSub, UP_part)
1747 id_ = part.left_token.id
1748 if id_ in (Id.Left_DollarParen, Id.Left_AtParen,
1749 Id.Left_Backtick):
1750 sv = self._EvalCommandSub(part,
1751 quoted) # type: part_value_t
1752
1753 elif id_ in (Id.Left_ProcSubIn, Id.Left_ProcSubOut):
1754 sv = self._EvalProcessSub(part)
1755
1756 else:
1757 raise AssertionError(id_)
1758
1759 part_vals.append(sv)
1760
1761 elif case(word_part_e.SimpleVarSub):
1762 part = cast(SimpleVarSub, UP_part)
1763 self._EvalSimpleVarSub(part, part_vals, quoted)
1764
1765 elif case(word_part_e.BracedVarSub):
1766 part = cast(BracedVarSub, UP_part)
1767 self._EvalBracedVarSub(part, part_vals, quoted)
1768
1769 elif case(word_part_e.TildeSub):
1770 part = cast(word_part.TildeSub, UP_part)
1771 # We never parse a quoted string into a TildeSub.
1772 assert not quoted
1773 s = self.tilde_ev.Eval(part)
1774 v = Piece(s, True, False) # NOT split even when unquoted!
1775 part_vals.append(v)
1776
1777 elif case(word_part_e.ArithSub):
1778 part = cast(word_part.ArithSub, UP_part)
1779 num = self.arith_ev.EvalToBigInt(part.anode)
1780 v = Piece(mops.ToStr(num), quoted, not quoted)
1781 part_vals.append(v)
1782
1783 elif case(word_part_e.ExtGlob):
1784 part = cast(word_part.ExtGlob, UP_part)
1785 #if not self.exec_opts.extglob():
1786 # die() # disallow at runtime? Don't just decay
1787
1788 # Create a node to hold the flattened tree. The caller decides whether
1789 # to pass it to fnmatch() or replace it with '*' and pass it to glob().
1790 part_vals2 = [] # type: List[part_value_t]
1791 self._EvalExtGlob(part, part_vals2) # flattens tree
1792 part_vals.append(part_value.ExtGlob(part_vals2))
1793
1794 elif case(word_part_e.BashRegexGroup):
1795 part = cast(word_part.BashRegexGroup, UP_part)
1796
1797 part_vals.append(Piece('(', False, False)) # not quoted
1798 if part.child:
1799 self._EvalWordToParts(part.child, part_vals, 0)
1800 part_vals.append(Piece(')', False, False))
1801
1802 elif case(word_part_e.Splice):
1803 part = cast(word_part.Splice, UP_part)
1804 val = self.mem.GetValue(part.var_name)
1805
1806 strs = self.expr_ev.SpliceValue(val, part)
1807 part_vals.append(part_value.Array(strs))
1808
1809 elif case(word_part_e.ExprSub):
1810 part = cast(word_part.ExprSub, UP_part)
1811 part_val = self.expr_ev.EvalExprSub(part)
1812 part_vals.append(part_val)
1813
1814 elif case(word_part_e.ZshVarSub):
1815 part = cast(word_part.ZshVarSub, UP_part)
1816 e_die("ZSH var subs are parsed, but can't be evaluated",
1817 part.left)
1818
1819 else:
1820 raise AssertionError(part.tag())
1821
1822 def _EvalRhsWordToParts(self, w, part_vals, eval_flags=0):
1823 # type: (rhs_word_t, List[part_value_t], int) -> None
1824 quoted = bool(eval_flags & QUOTED)
1825
1826 UP_w = w
1827 with tagswitch(w) as case:
1828 if case(rhs_word_e.Empty):
1829 part_vals.append(Piece('', quoted, not quoted))
1830
1831 elif case(rhs_word_e.Compound):
1832 w = cast(CompoundWord, UP_w)
1833 self._EvalWordToParts(w, part_vals, eval_flags=eval_flags)
1834
1835 else:
1836 raise AssertionError()
1837
1838 def _EvalWordToParts(self, w, part_vals, eval_flags=0):
1839 # type: (CompoundWord, List[part_value_t], int) -> None
1840 """Helper for EvalRhsWord, EvalWordSequence, etc.
1841
1842 Returns:
1843 Appends to part_vals. Note that this is a TREE.
1844 """
1845 # Does the word have an extended glob? This is a special case because
1846 # of the way we use glob() and then fnmatch(..., FNM_EXTMATCH) to
1847 # implement extended globs. It's hard to carry that extra information
1848 # all the way past the word splitting stage.
1849
1850 # OSH semantic limitations: If a word has an extended glob part, then
1851 # 1. It can't have an array
1852 # 2. Word splitting of unquoted words isn't respected
1853
1854 word_part_vals = [] # type: List[part_value_t]
1855 has_extglob = False
1856 for p in w.parts:
1857 if p.tag() == word_part_e.ExtGlob:
1858 has_extglob = True
1859 self._EvalWordPart(p, word_part_vals, eval_flags)
1860
1861 # Caller REQUESTED extglob evaluation, AND we parsed word_part.ExtGlob()
1862 if has_extglob:
1863 if bool(eval_flags & EXTGLOB_FILES):
1864 # Treat the WHOLE word as a pattern. We need to TWO VARIANTS of the
1865 # word because of the way we use libc:
1866 # 1. With '*' for extglob parts
1867 # 2. With _EvalExtGlob() for extglob parts
1868
1869 glob_parts = [] # type: List[str]
1870 fnmatch_parts = [] # type: List[str]
1871 self._TranslateExtGlob(word_part_vals, w, glob_parts,
1872 fnmatch_parts)
1873
1874 #log('word_part_vals %s', word_part_vals)
1875 glob_pat = ''.join(glob_parts)
1876 fnmatch_pat = ''.join(fnmatch_parts)
1877 #log("glob %s fnmatch %s", glob_pat, fnmatch_pat)
1878
1879 results = [] # type: List[str]
1880 n = self.globber.ExpandExtended(glob_pat, fnmatch_pat, results)
1881 if n < 0:
1882 raise error.FailGlob(
1883 'Extended glob %r matched no files' % fnmatch_pat, w)
1884
1885 part_vals.append(part_value.Array(results))
1886 elif bool(eval_flags & EXTGLOB_NESTED):
1887 # We only glob at the TOP level of @(nested|@(pattern))
1888 part_vals.extend(word_part_vals)
1889 else:
1890 # e.g. simple_word_eval, assignment builtin
1891 e_die('Extended glob not allowed in this word', w)
1892 else:
1893 part_vals.extend(word_part_vals)
1894
1895 def _PartValsToString(self, part_vals, w, eval_flags, strs):
1896 # type: (List[part_value_t], CompoundWord, int, List[str]) -> None
1897 """Helper for EvalWordToString, similar to _ConcatPartVals() above.
1898
1899 Note: arg 'w' could just be a span ID
1900 """
1901 for part_val in part_vals:
1902 UP_part_val = part_val
1903 with tagswitch(part_val) as case:
1904 if case(part_value_e.String):
1905 part_val = cast(Piece, UP_part_val)
1906 s = part_val.s
1907 if part_val.quoted:
1908 if eval_flags & QUOTE_FNMATCH:
1909 # [[ foo == */"*".py ]] or case (*.py) or ${x%*.py} or ${x//*.py/}
1910 s = glob_.GlobEscape(s)
1911 elif eval_flags & QUOTE_ERE:
1912 s = glob_.ExtendedRegexEscape(s)
1913 strs.append(s)
1914
1915 elif case(part_value_e.Array):
1916 part_val = cast(part_value.Array, UP_part_val)
1917 if self.exec_opts.strict_array():
1918 # Examples: echo f > "$@"; local foo="$@"
1919
1920 # TODO: This attributes too coarsely, to the word rather than the
1921 # parts. Problem: the word is a TREE of parts, but we only have a
1922 # flat list of part_vals. The only case where we really get arrays
1923 # is "$@", "${a[@]}", "${a[@]//pat/replace}", etc.
1924 e_die(
1925 "This word should yield a string, but it contains an array",
1926 w)
1927
1928 # TODO: Maybe add detail like this.
1929 #e_die('RHS of assignment should only have strings. '
1930 # 'To assign arrays, use b=( "${a[@]}" )')
1931 else:
1932 # It appears to not respect IFS
1933 tmp = [s for s in part_val.strs if s is not None]
1934 s = ' '.join(tmp) # TODO: eliminate double join()?
1935 strs.append(s)
1936
1937 elif case(part_value_e.ExtGlob):
1938 part_val = cast(part_value.ExtGlob, UP_part_val)
1939
1940 # Extended globs are only allowed where we expect them!
1941 if not bool(eval_flags & QUOTE_FNMATCH):
1942 e_die('extended glob not allowed in this word', w)
1943
1944 # recursive call
1945 self._PartValsToString(part_val.part_vals, w, eval_flags,
1946 strs)
1947
1948 else:
1949 raise AssertionError()
1950
1951 def EvalWordToString(self, UP_w, eval_flags=0):
1952 # type: (word_t, int) -> value.Str
1953 """Given a word, return a string.
1954
1955 Flags can contain a quoting algorithm.
1956 """
1957 assert UP_w.tag() == word_e.Compound, UP_w
1958 w = cast(CompoundWord, UP_w)
1959
1960 if eval_flags == 0: # QUOTE_FNMATCH etc. breaks optimization
1961 fast_str = word_.FastStrEval(w)
1962 if fast_str is not None:
1963 return value.Str(fast_str)
1964
1965 # Could we additionally optimize a=$b, if we know $b isn't an array
1966 # etc.?
1967
1968 # Note: these empty lists are hot in fib benchmark
1969
1970 part_vals = [] # type: List[part_value_t]
1971 for p in w.parts:
1972 # this doesn't use eval_flags, which is slightly confusing
1973 self._EvalWordPart(p, part_vals, 0)
1974
1975 strs = [] # type: List[str]
1976 self._PartValsToString(part_vals, w, eval_flags, strs)
1977 return value.Str(''.join(strs))
1978
1979 def EvalWordToPattern(self, UP_w):
1980 # type: (rhs_word_t) -> Tuple[value.Str, bool]
1981 """Like EvalWordToString, but returns whether we got ExtGlob."""
1982 if UP_w.tag() == rhs_word_e.Empty:
1983 return value.Str(''), False
1984
1985 assert UP_w.tag() == rhs_word_e.Compound, UP_w
1986 w = cast(CompoundWord, UP_w)
1987
1988 has_extglob = False
1989 part_vals = [] # type: List[part_value_t]
1990 for p in w.parts:
1991 # this doesn't use eval_flags, which is slightly confusing
1992 self._EvalWordPart(p, part_vals, 0)
1993 if p.tag() == word_part_e.ExtGlob:
1994 has_extglob = True
1995
1996 strs = [] # type: List[str]
1997 self._PartValsToString(part_vals, w, QUOTE_FNMATCH, strs)
1998 return value.Str(''.join(strs)), has_extglob
1999
2000 def EvalForPlugin(self, w):
2001 # type: (CompoundWord) -> value.Str
2002 """Wrapper around EvalWordToString that prevents errors.
2003
2004 Runtime errors like $(( 1 / 0 )) and mutating $? like $(exit 42)
2005 are handled here.
2006
2007 Similar to ExprEvaluator.PluginCall().
2008 """
2009 with state.ctx_Registers(self.mem): # to "sandbox" $? and $PIPESTATUS
2010 try:
2011 val = self.EvalWordToString(w)
2012 except error.FatalRuntime as e:
2013 val = value.Str('<Runtime error: %s>' % e.UserErrorString())
2014
2015 except (IOError, OSError) as e:
2016 val = value.Str('<I/O error: %s>' % pyutil.strerror(e))
2017
2018 except KeyboardInterrupt:
2019 val = value.Str('<Ctrl-C>')
2020
2021 return val
2022
2023 def EvalRhsWord(self, UP_w):
2024 # type: (rhs_word_t) -> value_t
2025 """Used for RHS of assignment.
2026
2027 There is no splitting.
2028 """
2029 if UP_w.tag() == rhs_word_e.Empty:
2030 return value.Str('')
2031
2032 assert UP_w.tag() == word_e.Compound, UP_w
2033 w = cast(CompoundWord, UP_w)
2034
2035 if len(w.parts) == 1:
2036 part0 = w.parts[0]
2037 UP_part0 = part0
2038 tag = part0.tag()
2039 # Special case for a=(1 2). ShArrayLiteral won't appear in words that
2040 # don't look like assignments.
2041 if tag == word_part_e.ShArrayLiteral:
2042 part0 = cast(ShArrayLiteral, UP_part0)
2043 array_words = part0.words
2044 words = braces.BraceExpandWords(array_words)
2045 strs = self.EvalWordSequence(words)
2046 return value.BashArray(strs)
2047
2048 if tag == word_part_e.BashAssocLiteral:
2049 part0 = cast(word_part.BashAssocLiteral, UP_part0)
2050 d = NewDict() # type: Dict[str, str]
2051 for pair in part0.pairs:
2052 k = self.EvalWordToString(pair.key)
2053 v = self.EvalWordToString(pair.value)
2054 d[k.s] = v.s
2055 return value.BashAssoc(d)
2056
2057 # If RHS doesn't look like a=( ... ), then it must be a string.
2058 return self.EvalWordToString(w)
2059
2060 def _EvalWordFrame(self, frame, argv):
2061 # type: (List[Piece], List[str]) -> None
2062 all_empty = True
2063 all_quoted = True
2064 any_quoted = False
2065
2066 #log('--- frame %s', frame)
2067
2068 for piece in frame:
2069 if len(piece.s):
2070 all_empty = False
2071
2072 if piece.quoted:
2073 any_quoted = True
2074 else:
2075 all_quoted = False
2076
2077 # Elision of ${empty}${empty} but not $empty"$empty" or $empty""
2078 if all_empty and not any_quoted:
2079 return
2080
2081 # If every frag is quoted, e.g. "$a$b" or any part in "${a[@]}"x, then
2082 # don't do word splitting or globbing.
2083 if all_quoted:
2084 tmp = [piece.s for piece in frame]
2085 a = ''.join(tmp)
2086 argv.append(a)
2087 return
2088
2089 will_glob = not self.exec_opts.noglob()
2090
2091 if 0:
2092 log('---')
2093 log('FRAME')
2094 for i, piece in enumerate(frame):
2095 log('(%d) %s', i, piece)
2096 log('')
2097
2098 # Array of strings, some of which are BOTH IFS-escaped and GLOB escaped!
2099 frags = [] # type: List[str]
2100 for piece in frame:
2101 if will_glob and piece.quoted:
2102 frag = glob_.GlobEscape(piece.s)
2103 else:
2104 # If we have a literal \, then we turn it into \\\\.
2105 # Splitting takes \\\\ -> \\
2106 # Globbing takes \\ to \ if it doesn't match
2107 frag = _BackslashEscape(piece.s)
2108
2109 if piece.do_split:
2110 frag = _BackslashEscape(frag)
2111 else:
2112 frag = self.splitter.Escape(frag)
2113
2114 frags.append(frag)
2115
2116 if 0:
2117 log('---')
2118 log('FRAGS')
2119 for i, frag in enumerate(frags):
2120 log('(%d) %s', i, frag)
2121 log('')
2122
2123 flat = ''.join(frags)
2124 #log('flat: %r', flat)
2125
2126 args = self.splitter.SplitForWordEval(flat)
2127
2128 # space=' '; argv $space"". We have a quoted part, but we CANNOT elide.
2129 # Add it back and don't bother globbing.
2130 if len(args) == 0 and any_quoted:
2131 argv.append('')
2132 return
2133
2134 #log('split args: %r', args)
2135 for a in args:
2136 if glob_.LooksLikeGlob(a):
2137 n = self.globber.Expand(a, argv)
2138 if n < 0:
2139 # TODO: location info, with span IDs carried through the frame
2140 raise error.FailGlob('Pattern %r matched no files' % a,
2141 loc.Missing)
2142 else:
2143 argv.append(glob_.GlobUnescape(a))
2144
2145 def _EvalWordToArgv(self, w):
2146 # type: (CompoundWord) -> List[str]
2147 """Helper for _EvalAssignBuiltin.
2148
2149 Splitting and globbing are disabled for assignment builtins.
2150
2151 Example: declare -"${a[@]}" b=(1 2)
2152 where a is [x b=a d=a]
2153 """
2154 part_vals = [] # type: List[part_value_t]
2155 self._EvalWordToParts(w, part_vals, 0) # not double quoted
2156 frames = _MakeWordFrames(part_vals)
2157 argv = [] # type: List[str]
2158 for frame in frames:
2159 if len(frame): # empty array gives empty frame!
2160 tmp = [piece.s for piece in frame]
2161 argv.append(''.join(tmp)) # no split or glob
2162 #log('argv: %s', argv)
2163 return argv
2164
2165 def _EvalAssignBuiltin(self, builtin_id, arg0, words, meta_offset):
2166 # type: (builtin_t, str, List[CompoundWord], int) -> cmd_value.Assign
2167 """Handles both static and dynamic assignment, e.g.
2168
2169 x='foo=bar'
2170 local a=(1 2) $x
2171
2172 Grammar:
2173
2174 ('builtin' | 'command')* keyword flag* pair*
2175 flag = [-+].*
2176
2177 There is also command -p, but we haven't implemented it. Maybe just
2178 punt on it.
2179 """
2180 eval_to_pairs = True # except for -f and -F
2181 started_pairs = False
2182
2183 flags = [arg0] # initial flags like -p, and -f -F name1 name2
2184 flag_locs = [words[0]]
2185 assign_args = [] # type: List[AssignArg]
2186
2187 n = len(words)
2188 for i in xrange(meta_offset + 1, n): # skip first word
2189 w = words[i]
2190
2191 if word_.IsVarLike(w):
2192 started_pairs = True # Everything from now on is an assign_pair
2193
2194 if started_pairs:
2195 left_token, close_token, part_offset = word_.DetectShAssignment(
2196 w)
2197 if left_token: # Detected statically
2198 if left_token.id != Id.Lit_VarLike:
2199 # (not guaranteed since started_pairs is set twice)
2200 e_die('LHS array not allowed in assignment builtin', w)
2201
2202 if lexer.IsPlusEquals(left_token):
2203 var_name = lexer.TokenSliceRight(left_token, -2)
2204 append = True
2205 else:
2206 var_name = lexer.TokenSliceRight(left_token, -1)
2207 append = False
2208
2209 if part_offset == len(w.parts):
2210 rhs = rhs_word.Empty # type: rhs_word_t
2211 else:
2212 # tmp is for intersection of C++/MyPy type systems
2213 tmp = CompoundWord(w.parts[part_offset:])
2214 word_.TildeDetectAssign(tmp)
2215 rhs = tmp
2216
2217 with state.ctx_AssignBuiltin(self.mutable_opts):
2218 right = self.EvalRhsWord(rhs)
2219
2220 arg2 = AssignArg(var_name, right, append, w)
2221 assign_args.append(arg2)
2222
2223 else: # e.g. export $dynamic
2224 argv = self._EvalWordToArgv(w)
2225 for arg in argv:
2226 arg2 = _SplitAssignArg(arg, w)
2227 assign_args.append(arg2)
2228
2229 else:
2230 argv = self._EvalWordToArgv(w)
2231 for arg in argv:
2232 if arg.startswith('-') or arg.startswith('+'):
2233 # e.g. declare -r +r
2234 flags.append(arg)
2235 flag_locs.append(w)
2236
2237 # Shortcut that relies on -f and -F always meaning "function" for
2238 # all assignment builtins
2239 if 'f' in arg or 'F' in arg:
2240 eval_to_pairs = False
2241
2242 else: # e.g. export $dynamic
2243 if eval_to_pairs:
2244 arg2 = _SplitAssignArg(arg, w)
2245 assign_args.append(arg2)
2246 started_pairs = True
2247 else:
2248 flags.append(arg)
2249
2250 return cmd_value.Assign(builtin_id, flags, flag_locs, assign_args)
2251
2252 def _DetectAssignBuiltinStr(self, arg0, words, meta_offset):
2253 # type: (str, List[CompoundWord], int) -> Optional[cmd_value.Assign]
2254 builtin_id = consts.LookupAssignBuiltin(arg0)
2255 if builtin_id != consts.NO_INDEX:
2256 return self._EvalAssignBuiltin(builtin_id, arg0, words,
2257 meta_offset)
2258 return None
2259
2260 def _DetectAssignBuiltin(self, val0, words, meta_offset):
2261 # type: (part_value_t, List[CompoundWord], int) -> Optional[cmd_value.Assign]
2262 UP_val0 = val0
2263 if val0.tag() == part_value_e.String:
2264 val0 = cast(Piece, UP_val0)
2265 if not val0.quoted:
2266 return self._DetectAssignBuiltinStr(val0.s, words, meta_offset)
2267 return None
2268
2269 def SimpleEvalWordSequence2(self, words, is_last_cmd, allow_assign):
2270 # type: (List[CompoundWord], bool, bool) -> cmd_value_t
2271 """Simple word evaluation for YSH."""
2272 strs = [] # type: List[str]
2273 locs = [] # type: List[CompoundWord]
2274
2275 meta_offset = 0
2276 for i, w in enumerate(words):
2277 # No globbing in the first arg for command.Simple.
2278 if i == meta_offset and allow_assign:
2279 strs0 = self._EvalWordToArgv(w)
2280 # TODO: Remove this because YSH will disallow assignment
2281 # builtins? (including export?)
2282 if len(strs0) == 1:
2283 cmd_val = self._DetectAssignBuiltinStr(
2284 strs0[0], words, meta_offset)
2285 if cmd_val:
2286 return cmd_val
2287
2288 strs.extend(strs0)
2289 for _ in strs0:
2290 locs.append(w)
2291 continue
2292
2293 if glob_.LooksLikeStaticGlob(w):
2294 val = self.EvalWordToString(w) # respects strict-array
2295 num_appended = self.globber.Expand(val.s, strs)
2296 if num_appended < 0:
2297 raise error.FailGlob('Pattern %r matched no files' % val.s,
2298 w)
2299 for _ in xrange(num_appended):
2300 locs.append(w)
2301 continue
2302
2303 part_vals = [] # type: List[part_value_t]
2304 self._EvalWordToParts(w, part_vals, 0) # not quoted
2305
2306 if 0:
2307 log('')
2308 log('Static: part_vals after _EvalWordToParts:')
2309 for entry in part_vals:
2310 log(' %s', entry)
2311
2312 # Still need to process
2313 frames = _MakeWordFrames(part_vals)
2314
2315 if 0:
2316 log('')
2317 log('Static: frames after _MakeWordFrames:')
2318 for entry in frames:
2319 log(' %s', entry)
2320
2321 # We will still allow x"${a[@]"x, though it's deprecated by @a, which
2322 # disallows such expressions at parse time.
2323 for frame in frames:
2324 if len(frame): # empty array gives empty frame!
2325 tmp = [piece.s for piece in frame]
2326 strs.append(''.join(tmp)) # no split or glob
2327 locs.append(w)
2328
2329 assert len(strs) == len(locs), '%s vs. %d' % (strs, len(locs))
2330 return cmd_value.Argv(strs, locs, is_last_cmd, None, None)
2331
2332 def EvalWordSequence2(self, words, is_last_cmd, allow_assign=False):
2333 # type: (List[CompoundWord], bool, bool) -> cmd_value_t
2334 """Turns a list of Words into a list of strings.
2335
2336 Unlike the EvalWord*() methods, it does globbing.
2337
2338 Args:
2339 allow_assign: True for command.Simple, False for BashArray a=(1 2 3)
2340 """
2341 if self.exec_opts.simple_word_eval():
2342 return self.SimpleEvalWordSequence2(words, is_last_cmd,
2343 allow_assign)
2344
2345 # Parse time:
2346 # 1. brace expansion. TODO: Do at parse time.
2347 # 2. Tilde detection. DONE at parse time. Only if Id.Lit_Tilde is the
2348 # first WordPart.
2349 #
2350 # Run time:
2351 # 3. tilde sub, var sub, command sub, arith sub. These are all
2352 # "concurrent" on WordParts. (optional process sub with <() )
2353 # 4. word splitting. Can turn this off with a shell option? Definitely
2354 # off for oil.
2355 # 5. globbing -- several exec_opts affect this: nullglob, safeglob, etc.
2356
2357 #log('W %s', words)
2358 strs = [] # type: List[str]
2359 locs = [] # type: List[CompoundWord]
2360
2361 # 0 for declare x
2362 # 1 for builtin declare x
2363 # 2 for command builtin declare x
2364 # etc.
2365 meta_offset = 0
2366
2367 n = 0
2368 for i, w in enumerate(words):
2369 fast_str = word_.FastStrEval(w)
2370 if fast_str is not None:
2371 strs.append(fast_str)
2372 locs.append(w)
2373
2374 # e.g. the 'local' in 'local a=b c=d' will be here
2375 if allow_assign and i == meta_offset:
2376 cmd_val = self._DetectAssignBuiltinStr(
2377 fast_str, words, meta_offset)
2378 if cmd_val:
2379 return cmd_val
2380
2381 if i <= meta_offset and _DetectMetaBuiltinStr(fast_str):
2382 meta_offset += 1
2383
2384 # Bug fix: n must be updated on every loop iteration
2385 n = len(strs)
2386 assert len(strs) == len(locs), strs
2387 continue
2388
2389 part_vals = [] # type: List[part_value_t]
2390 self._EvalWordToParts(w, part_vals, EXTGLOB_FILES)
2391
2392 # DYNAMICALLY detect if we're going to run an assignment builtin, and
2393 # change the rest of the evaluation algorithm if so.
2394 #
2395 # We want to allow:
2396 # e=export
2397 # $e foo=bar
2398 #
2399 # But we don't want to evaluate the first word twice in the case of:
2400 # $(some-command) --flag
2401 if len(part_vals) == 1:
2402 if allow_assign and i == meta_offset:
2403 cmd_val = self._DetectAssignBuiltin(
2404 part_vals[0], words, meta_offset)
2405 if cmd_val:
2406 return cmd_val
2407
2408 if i <= meta_offset and _DetectMetaBuiltin(part_vals[0]):
2409 meta_offset += 1
2410
2411 if 0:
2412 log('')
2413 log('part_vals after _EvalWordToParts:')
2414 for entry in part_vals:
2415 log(' %s', entry)
2416
2417 frames = _MakeWordFrames(part_vals)
2418 if 0:
2419 log('')
2420 log('frames after _MakeWordFrames:')
2421 for entry in frames:
2422 log(' %s', entry)
2423
2424 # Do splitting and globbing. Each frame will append zero or more args.
2425 for frame in frames:
2426 self._EvalWordFrame(frame, strs)
2427
2428 # Fill in locations parallel to strs.
2429 n_next = len(strs)
2430 for _ in xrange(n_next - n):
2431 locs.append(w)
2432 n = n_next
2433
2434 # A non-assignment command.
2435 # NOTE: Can't look up builtins here like we did for assignment, because
2436 # functions can override builtins.
2437 assert len(strs) == len(locs), '%s vs. %d' % (strs, len(locs))
2438 return cmd_value.Argv(strs, locs, is_last_cmd, None, None)
2439
2440 def EvalWordSequence(self, words):
2441 # type: (List[CompoundWord]) -> List[str]
2442 """For arrays and for loops.
2443
2444 They don't allow assignment builtins.
2445 """
2446 # is_last_cmd is irrelevant
2447 cmd_val = self.EvalWordSequence2(words, False)
2448 assert cmd_val.tag() == cmd_value_e.Argv
2449 return cast(cmd_value.Argv, cmd_val).argv
2450
2451
2452class NormalWordEvaluator(AbstractWordEvaluator):
2453
2454 def __init__(
2455 self,
2456 mem, # type: state.Mem
2457 exec_opts, # type: optview.Exec
2458 mutable_opts, # type: state.MutableOpts
2459 tilde_ev, # type: TildeEvaluator
2460 splitter, # type: SplitContext
2461 errfmt, # type: ui.ErrorFormatter
2462 ):
2463 # type: (...) -> None
2464 AbstractWordEvaluator.__init__(self, mem, exec_opts, mutable_opts,
2465 tilde_ev, splitter, errfmt)
2466 self.shell_ex = None # type: _Executor
2467
2468 def CheckCircularDeps(self):
2469 # type: () -> None
2470 assert self.arith_ev is not None
2471 # Disabled for pure OSH
2472 #assert self.expr_ev is not None
2473 assert self.shell_ex is not None
2474 assert self.prompt_ev is not None
2475
2476 def _EvalCommandSub(self, cs_part, quoted):
2477 # type: (CommandSub, bool) -> part_value_t
2478 stdout_str = self.shell_ex.RunCommandSub(cs_part)
2479
2480 if cs_part.left_token.id == Id.Left_AtParen:
2481 # YSH splitting algorithm: does not depend on IFS
2482 try:
2483 strs = j8.SplitJ8Lines(stdout_str)
2484 except error.Decode as e:
2485 # status code 4 is special, for encode/decode errors.
2486 raise error.Structured(4, e.Message(), cs_part.left_token)
2487
2488 #strs = self.splitter.SplitForWordEval(stdout_str)
2489 return part_value.Array(strs)
2490 else:
2491 return Piece(stdout_str, quoted, not quoted)
2492
2493 def _EvalProcessSub(self, cs_part):
2494 # type: (CommandSub) -> Piece
2495 dev_path = self.shell_ex.RunProcessSub(cs_part)
2496 # pretend it's quoted; no split or glob
2497 return Piece(dev_path, True, False)
2498
2499
2500_DUMMY = '__NO_COMMAND_SUB__'
2501
2502
2503class CompletionWordEvaluator(AbstractWordEvaluator):
2504 """An evaluator that has no access to an executor.
2505
2506 NOTE: core/completion.py doesn't actually try to use these strings to
2507 complete. If you have something like 'echo $(echo hi)/f<TAB>', it sees the
2508 inner command as the last one, and knows that it is not at the end of the
2509 line.
2510 """
2511
2512 def __init__(
2513 self,
2514 mem, # type: state.Mem
2515 exec_opts, # type: optview.Exec
2516 mutable_opts, # type: state.MutableOpts
2517 tilde_ev, # type: TildeEvaluator
2518 splitter, # type: SplitContext
2519 errfmt, # type: ui.ErrorFormatter
2520 ):
2521 # type: (...) -> None
2522 AbstractWordEvaluator.__init__(self, mem, exec_opts, mutable_opts,
2523 tilde_ev, splitter, errfmt)
2524
2525 def CheckCircularDeps(self):
2526 # type: () -> None
2527 assert self.prompt_ev is not None
2528 assert self.arith_ev is not None
2529 assert self.expr_ev is not None
2530
2531 def _EvalCommandSub(self, cs_part, quoted):
2532 # type: (CommandSub, bool) -> part_value_t
2533 if cs_part.left_token.id == Id.Left_AtParen:
2534 return part_value.Array([_DUMMY])
2535 else:
2536 return Piece(_DUMMY, quoted, not quoted)
2537
2538 def _EvalProcessSub(self, cs_part):
2539 # type: (CommandSub) -> Piece
2540 # pretend it's quoted; no split or glob
2541 return Piece('__NO_PROCESS_SUB__', True, False)
2542
2543
2544# vim: sw=4