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

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