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

1106 lines, 715 significant
1#!/usr/bin/env python2
2# Copyright 2016 Andy Chu. All rights reserved.
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8"""
9sh_expr_eval.py -- Shell boolean and arithmetic expressions.
10"""
11
12from _devbuild.gen.id_kind_asdl import Id
13from _devbuild.gen.runtime_asdl import scope_t
14from _devbuild.gen.syntax_asdl import (
15 word_t,
16 CompoundWord,
17 Token,
18 NameTok,
19 loc,
20 loc_t,
21 source,
22 arith_expr,
23 arith_expr_e,
24 arith_expr_t,
25 bool_expr,
26 bool_expr_e,
27 bool_expr_t,
28 sh_lhs,
29 sh_lhs_e,
30 sh_lhs_t,
31 BracedVarSub,
32)
33from _devbuild.gen.option_asdl import option_i
34from _devbuild.gen.types_asdl import bool_arg_type_e
35from _devbuild.gen.value_asdl import (
36 value,
37 value_e,
38 value_t,
39 sh_lvalue,
40 sh_lvalue_e,
41 sh_lvalue_t,
42 LeftName,
43 eggex_ops,
44 regex_match,
45 RegexMatch,
46)
47from core import alloc
48from core import error
49from core.error import e_die, e_die_status, e_strict, e_usage
50from core import num
51from core import state
52from core import ui
53from frontend import consts
54from frontend import match
55from frontend import parse_lib
56from frontend import reader
57from mycpp import mops
58from mycpp import mylib
59from mycpp.mylib import log, tagswitch, switch, str_cmp
60from osh import bool_stat
61from osh import word_eval
62
63import libc # for fnmatch
64# Import these names directly because the C++ translation uses macros literally.
65from libc import FNM_CASEFOLD, REG_ICASE
66
67from typing import Tuple, Optional, cast, TYPE_CHECKING
68if TYPE_CHECKING:
69 from core.ui import ErrorFormatter
70 from core import optview
71
72_ = log
73
74#
75# Arith and Command/Word variants of assignment
76#
77# Calls EvalShellLhs()
78# a[$key]=$val # osh/cmd_eval.py:814 (command_e.ShAssignment)
79# Calls EvalArithLhs()
80# (( a[key] = val )) # osh/sh_expr_eval.py:326 (_EvalLhsArith)
81#
82# Calls OldValue()
83# a[$key]+=$val # osh/cmd_eval.py:795 (assign_op_e.PlusEqual)
84# (( a[key] += val )) # osh/sh_expr_eval.py:308 (_EvalLhsAndLookupArith)
85#
86# RHS Indexing
87# val=${a[$key]} # osh/word_eval.py:639 (bracket_op_e.ArrayIndex)
88# (( val = a[key] )) # osh/sh_expr_eval.py:509 (Id.Arith_LBracket)
89#
90
91
92def OldValue(lval, mem, exec_opts):
93 # type: (sh_lvalue_t, state.Mem, Optional[optview.Exec]) -> value_t
94 """Look up for augmented assignment.
95
96 For s+=val and (( i += 1 ))
97
98 Args:
99 lval: value we need to
100 exec_opts: can be None if we don't want to check set -u!
101 Because s+=val doesn't check it.
102
103 TODO: A stricter and less ambiguous version for YSH.
104 - Problem: why does sh_lvalue have Indexed and Keyed, while sh_lhs only has
105 IndexedName?
106 - should I have location.LName and sh_lvalue.Indexed only?
107 - and Indexed uses the index_t type?
108 - well that might be Str or Int
109 """
110 assert isinstance(lval, sh_lvalue_t), lval
111
112 # TODO: refactor sh_lvalue_t to make this simpler
113 UP_lval = lval
114 with tagswitch(lval) as case:
115 if case(sh_lvalue_e.Var): # (( i++ ))
116 lval = cast(LeftName, UP_lval)
117 var_name = lval.name
118 elif case(sh_lvalue_e.Indexed): # (( a[i]++ ))
119 lval = cast(sh_lvalue.Indexed, UP_lval)
120 var_name = lval.name
121 elif case(sh_lvalue_e.Keyed): # (( A['K']++ )) ? I think this works
122 lval = cast(sh_lvalue.Keyed, UP_lval)
123 var_name = lval.name
124 else:
125 raise AssertionError()
126
127 val = mem.GetValue(var_name)
128 if exec_opts and exec_opts.nounset() and val.tag() == value_e.Undef:
129 e_die('Undefined variable %r' % var_name) # TODO: location info
130
131 UP_val = val
132 with tagswitch(lval) as case:
133 if case(sh_lvalue_e.Var):
134 return val
135
136 elif case(sh_lvalue_e.Indexed):
137 lval = cast(sh_lvalue.Indexed, UP_lval)
138
139 array_val = None # type: value.BashArray
140 with tagswitch(val) as case2:
141 if case2(value_e.Undef):
142 array_val = value.BashArray([])
143 elif case2(value_e.BashArray):
144 tmp = cast(value.BashArray, UP_val)
145 # mycpp rewrite: add tmp. cast() creates a new var in inner scope
146 array_val = tmp
147 else:
148 e_die("Can't use [] on value of type %s" % ui.ValType(val))
149
150 s = word_eval.GetArrayItem(array_val.strs, lval.index)
151
152 if s is None:
153 val = value.Str('') # NOTE: Other logic is value.Undef? 0?
154 else:
155 assert isinstance(s, str), s
156 val = value.Str(s)
157
158 elif case(sh_lvalue_e.Keyed):
159 lval = cast(sh_lvalue.Keyed, UP_lval)
160
161 assoc_val = None # type: value.BashAssoc
162 with tagswitch(val) as case2:
163 if case2(value_e.Undef):
164 # This never happens, because undef[x]+= is assumed to
165 raise AssertionError()
166 elif case2(value_e.BashAssoc):
167 tmp2 = cast(value.BashAssoc, UP_val)
168 # mycpp rewrite: add tmp. cast() creates a new var in inner scope
169 assoc_val = tmp2
170 else:
171 e_die("Can't use [] on value of type %s" % ui.ValType(val))
172
173 s = assoc_val.d.get(lval.key)
174 if s is None:
175 val = value.Str('')
176 else:
177 val = value.Str(s)
178
179 else:
180 raise AssertionError()
181
182 return val
183
184
185# TODO: Should refactor for int/char-based processing
186if mylib.PYTHON:
187
188 def IsLower(ch):
189 # type: (str) -> bool
190 return 'a' <= ch and ch <= 'z'
191
192 def IsUpper(ch):
193 # type: (str) -> bool
194 return 'A' <= ch and ch <= 'Z'
195
196
197class UnsafeArith(object):
198 """For parsing a[i] at RUNTIME."""
199
200 def __init__(
201 self,
202 mem, # type: state.Mem
203 exec_opts, # type: optview.Exec
204 mutable_opts, # type: state.MutableOpts
205 parse_ctx, # type: parse_lib.ParseContext
206 arith_ev, # type: ArithEvaluator
207 errfmt, # type: ui.ErrorFormatter
208 ):
209 # type: (...) -> None
210 self.mem = mem
211 self.exec_opts = exec_opts
212 self.mutable_opts = mutable_opts
213 self.parse_ctx = parse_ctx
214 self.arith_ev = arith_ev
215 self.errfmt = errfmt
216
217 self.arena = self.parse_ctx.arena
218
219 def ParseLValue(self, s, location):
220 # type: (str, loc_t) -> sh_lvalue_t
221 """Parse sh_lvalue for 'unset' and 'printf -v'.
222
223 It uses the arith parser, so it behaves like the LHS of (( a[i] = x ))
224 """
225 if not self.parse_ctx.parse_opts.parse_sh_arith():
226 # Do something simpler for YSH
227 if not match.IsValidVarName(s):
228 e_die('Invalid variable name %r (parse_sh_arith is off)' % s,
229 location)
230 return LeftName(s, location)
231
232 a_parser = self.parse_ctx.MakeArithParser(s)
233
234 with alloc.ctx_SourceCode(self.arena,
235 source.ArgvWord('dynamic LHS', location)):
236 try:
237 anode = a_parser.Parse()
238 except error.Parse as e:
239 self.errfmt.PrettyPrintError(e)
240 # Exception for builtins 'unset' and 'printf'
241 e_usage('got invalid LHS expression', location)
242
243 # Note: we parse '1+2', and then it becomes a runtime error because
244 # it's not a valid LHS. Could be a parse error.
245
246 if self.exec_opts.eval_unsafe_arith():
247 lval = self.arith_ev.EvalArithLhs(anode)
248 else:
249 # Prevent attacks like these by default:
250 #
251 # unset -v 'A["$(echo K; rm *)"]'
252 with state.ctx_Option(self.mutable_opts,
253 [option_i._allow_command_sub], False):
254 lval = self.arith_ev.EvalArithLhs(anode)
255
256 return lval
257
258 def ParseVarRef(self, ref_str, blame_tok):
259 # type: (str, Token) -> BracedVarSub
260 """Parse and evaluate value for ${!ref}
261
262 This supports:
263 - 0 to 9 for $0 to $9
264 - @ for "$@" etc.
265
266 See grammar in osh/word_parse.py, which is related to grammar in
267 osh/word_parse.py _ReadBracedVarSub
268
269 Note: declare -n allows 'varname' and 'varname[i]' and 'varname[@]', but it
270 does NOT allow 0 to 9, @, *
271
272 NamerefExpr = NAME Subscript? # this allows @ and * too
273
274 _ResolveNameOrRef currently gives you a 'cell'. So it might not support
275 sh_lvalue.Indexed?
276 """
277 line_reader = reader.StringLineReader(ref_str, self.arena)
278 lexer = self.parse_ctx.MakeLexer(line_reader)
279 w_parser = self.parse_ctx.MakeWordParser(lexer, line_reader)
280
281 src = source.VarRef(blame_tok)
282 with alloc.ctx_SourceCode(self.arena, src):
283 try:
284 bvs_part = w_parser.ParseVarRef()
285 except error.Parse as e:
286 # This prints the inner location
287 self.errfmt.PrettyPrintError(e)
288
289 # this affects builtins 'unset' and 'printf'
290 e_die("Invalid var ref expression", blame_tok)
291
292 return bvs_part
293
294
295class ArithEvaluator(object):
296 """Shared between arith and bool evaluators.
297
298 They both:
299
300 1. Convert strings to integers, respecting shopt -s strict_arith.
301 2. Look up variables and evaluate words.
302 """
303
304 def __init__(
305 self,
306 mem, # type: state.Mem
307 exec_opts, # type: optview.Exec
308 mutable_opts, # type: state.MutableOpts
309 parse_ctx, # type: Optional[parse_lib.ParseContext]
310 errfmt, # type: ErrorFormatter
311 ):
312 # type: (...) -> None
313 self.word_ev = None # type: word_eval.StringWordEvaluator
314 self.mem = mem
315 self.exec_opts = exec_opts
316 self.mutable_opts = mutable_opts
317 self.parse_ctx = parse_ctx
318 self.errfmt = errfmt
319
320 def CheckCircularDeps(self):
321 # type: () -> None
322 assert self.word_ev is not None
323
324 def _StringToBigInt(self, s, blame_loc):
325 # type: (str, loc_t) -> mops.BigInt
326 """Use bash-like rules to coerce a string to an integer.
327
328 Runtime parsing enables silly stuff like $(( $(echo 1)$(echo 2) + 1 )) => 13
329
330 0xAB -- hex constant
331 042 -- octal constant
332 42 -- decimal constant
333 64#z -- arbitrary base constant
334
335 bare word: variable
336 quoted word: string (not done?)
337 """
338 if s.startswith('0x'):
339 try:
340 integer = mops.FromStr(s, 16)
341 except ValueError:
342 e_strict('Invalid hex constant %r' % s, blame_loc)
343 # TODO: don't truncate
344 return integer
345
346 if s.startswith('0'):
347 try:
348 integer = mops.FromStr(s, 8)
349 except ValueError:
350 e_strict('Invalid octal constant %r' % s, blame_loc)
351 return integer
352
353 b, digits = mylib.split_once(s, '#') # see if it has #
354 if digits is not None:
355 try:
356 base = int(b) # machine integer, not BigInt
357 except ValueError:
358 e_strict('Invalid base for numeric constant %r' % b, blame_loc)
359
360 integer = mops.ZERO
361 for ch in digits:
362 if IsLower(ch):
363 digit = ord(ch) - ord('a') + 10
364 elif IsUpper(ch):
365 digit = ord(ch) - ord('A') + 36
366 elif ch == '@': # horrible syntax
367 digit = 62
368 elif ch == '_':
369 digit = 63
370 elif ch.isdigit():
371 digit = int(ch)
372 else:
373 e_strict('Invalid digits for numeric constant %r' % digits,
374 blame_loc)
375
376 if digit >= base:
377 e_strict(
378 'Digits %r out of range for base %d' % (digits, base),
379 blame_loc)
380
381 #integer = integer * base + digit
382 integer = mops.Add(mops.Mul(integer, mops.BigInt(base)),
383 mops.BigInt(digit))
384 return integer
385
386 try:
387 # Normal base 10 integer. This includes negative numbers like '-42'.
388 integer = mops.FromStr(s)
389 except ValueError:
390 # doesn't look like an integer
391
392 # note: 'test' and '[' never evaluate recursively
393 if self.parse_ctx:
394 arena = self.parse_ctx.arena
395
396 # Special case so we don't get EOF error
397 if len(s.strip()) == 0:
398 return mops.ZERO
399
400 # For compatibility: Try to parse it as an expression and evaluate it.
401 a_parser = self.parse_ctx.MakeArithParser(s)
402
403 # TODO: Fill in the variable name
404 with alloc.ctx_SourceCode(arena,
405 source.Variable(None, blame_loc)):
406 try:
407 node2 = a_parser.Parse() # may raise error.Parse
408 except error.Parse as e:
409 self.errfmt.PrettyPrintError(e)
410 e_die('Parse error in recursive arithmetic',
411 e.location)
412
413 # Prevent infinite recursion of $(( 1x )) -- it's a word that evaluates
414 # to itself, and you don't want to reparse it as a word.
415 if node2.tag() == arith_expr_e.Word:
416 e_die("Invalid integer constant %r" % s, blame_loc)
417
418 if self.exec_opts.eval_unsafe_arith():
419 integer = self.EvalToBigInt(node2)
420 else:
421 # BoolEvaluator doesn't have parse_ctx or mutable_opts
422 assert self.mutable_opts is not None
423
424 # We don't need to flip _allow_process_sub, because they can't be
425 # parsed. See spec/bugs.test.sh.
426 with state.ctx_Option(self.mutable_opts,
427 [option_i._allow_command_sub],
428 False):
429 integer = self.EvalToBigInt(node2)
430
431 else:
432 if len(s.strip()) == 0 or match.IsValidVarName(s):
433 # x42 could evaluate to 0
434 e_strict("Invalid integer constant %r" % s, blame_loc)
435 else:
436 # 42x is always fatal!
437 e_die("Invalid integer constant %r" % s, blame_loc)
438
439 return integer
440
441 def _ValToIntOrError(self, val, blame):
442 # type: (value_t, arith_expr_t) -> mops.BigInt
443 try:
444 UP_val = val
445 with tagswitch(val) as case:
446 if case(value_e.Undef):
447 # 'nounset' already handled before got here
448 # Happens upon a[undefined]=42, which unfortunately turns into a[0]=42.
449 e_strict('Undefined value in arithmetic context',
450 loc.Arith(blame))
451
452 elif case(value_e.Int):
453 val = cast(value.Int, UP_val)
454 return val.i
455
456 elif case(value_e.Str):
457 val = cast(value.Str, UP_val)
458 # calls e_strict
459 return self._StringToBigInt(val.s, loc.Arith(blame))
460
461 except error.Strict as e:
462 if self.exec_opts.strict_arith():
463 raise
464 else:
465 return mops.ZERO
466
467 # Arrays and associative arrays always fail -- not controlled by
468 # strict_arith.
469 # In bash, (( a )) is like (( a[0] )), but I don't want that.
470 # And returning '0' gives different results.
471 e_die(
472 "Expected a value convertible to integer, got %s" %
473 ui.ValType(val), loc.Arith(blame))
474
475 def _EvalLhsAndLookupArith(self, node):
476 # type: (arith_expr_t) -> Tuple[mops.BigInt, sh_lvalue_t]
477 """ For x = y and x += y and ++x """
478
479 lval = self.EvalArithLhs(node)
480 val = OldValue(lval, self.mem, self.exec_opts)
481
482 # BASH_LINENO, arr (array name without strict_array), etc.
483 if (val.tag() in (value_e.BashArray, value_e.BashAssoc) and
484 lval.tag() == sh_lvalue_e.Var):
485 named_lval = cast(LeftName, lval)
486 if word_eval.ShouldArrayDecay(named_lval.name, self.exec_opts):
487 if val.tag() == value_e.BashArray:
488 lval = sh_lvalue.Indexed(named_lval.name, 0, loc.Missing)
489 elif val.tag() == value_e.BashAssoc:
490 lval = sh_lvalue.Keyed(named_lval.name, '0', loc.Missing)
491 val = word_eval.DecayArray(val)
492
493 # This error message could be better, but we already have one
494 #if val.tag() == value_e.BashArray:
495 # e_die("Can't use assignment like ++ or += on arrays")
496
497 i = self._ValToIntOrError(val, node)
498 return i, lval
499
500 def _Store(self, lval, new_int):
501 # type: (sh_lvalue_t, mops.BigInt) -> None
502 val = value.Str(mops.ToStr(new_int))
503 state.OshLanguageSetValue(self.mem, lval, val)
504
505 def EvalToBigInt(self, node):
506 # type: (arith_expr_t) -> mops.BigInt
507 """Used externally by ${a[i+1]} and ${a:start:len}.
508
509 Also used internally.
510 """
511 val = self.Eval(node)
512
513 # BASH_LINENO, arr (array name without strict_array), etc.
514 if (val.tag() in (value_e.BashArray, value_e.BashAssoc) and
515 node.tag() == arith_expr_e.VarSub):
516 vsub = cast(NameTok, node)
517 if word_eval.ShouldArrayDecay(vsub.var_name, self.exec_opts):
518 val = word_eval.DecayArray(val)
519
520 i = self._ValToIntOrError(val, node)
521 return i
522
523 def EvalToInt(self, node):
524 # type: (arith_expr_t) -> int
525 return mops.BigTruncate(self.EvalToBigInt(node))
526
527 def Eval(self, node):
528 # type: (arith_expr_t) -> value_t
529 """
530 Returns:
531 None for Undef (e.g. empty cell) TODO: Don't return 0!
532 int for Str
533 List[int] for BashArray
534 Dict[str, str] for BashAssoc (TODO: Should we support this?)
535
536 NOTE: (( A['x'] = 'x' )) and (( x = A['x'] )) are syntactically valid in
537 bash, but don't do what you'd think. 'x' sometimes a variable name and
538 sometimes a key.
539 """
540 # OSH semantics: Variable NAMES cannot be formed dynamically; but INTEGERS
541 # can. ${foo:-3}4 is OK. $? will be a compound word too, so we don't have
542 # to handle that as a special case.
543
544 UP_node = node
545 with tagswitch(node) as case:
546 if case(arith_expr_e.VarSub): # $(( x )) (can be array)
547 vsub = cast(NameTok, UP_node)
548 val = self.mem.GetValue(vsub.var_name)
549 if val.tag() == value_e.Undef and self.exec_opts.nounset():
550 e_die('Undefined variable %r' % vsub.var_name, vsub.left)
551 return val
552
553 elif case(arith_expr_e.Word): # $(( $x )) $(( ${x}${y} )), etc.
554 w = cast(CompoundWord, UP_node)
555 return self.word_ev.EvalWordToString(w)
556
557 elif case(arith_expr_e.UnaryAssign): # a++
558 node = cast(arith_expr.UnaryAssign, UP_node)
559
560 op_id = node.op_id
561 old_big, lval = self._EvalLhsAndLookupArith(node.child)
562
563 if op_id == Id.Node_PostDPlus: # post-increment
564 new_big = mops.Add(old_big, mops.ONE)
565 result = old_big
566
567 elif op_id == Id.Node_PostDMinus: # post-decrement
568 new_big = mops.Sub(old_big, mops.ONE)
569 result = old_big
570
571 elif op_id == Id.Arith_DPlus: # pre-increment
572 new_big = mops.Add(old_big, mops.ONE)
573 result = new_big
574
575 elif op_id == Id.Arith_DMinus: # pre-decrement
576 new_big = mops.Sub(old_big, mops.ONE)
577 result = new_big
578
579 else:
580 raise AssertionError(op_id)
581
582 self._Store(lval, new_big)
583 return value.Int(result)
584
585 elif case(arith_expr_e.BinaryAssign): # a=1, a+=5, a[1]+=5
586 node = cast(arith_expr.BinaryAssign, UP_node)
587 op_id = node.op_id
588
589 if op_id == Id.Arith_Equal:
590 # Don't really need a span ID here, because tdop.CheckLhsExpr should
591 # have done all the validation.
592 lval = self.EvalArithLhs(node.left)
593 rhs_big = self.EvalToBigInt(node.right)
594
595 self._Store(lval, rhs_big)
596 return value.Int(rhs_big)
597
598 old_big, lval = self._EvalLhsAndLookupArith(node.left)
599 rhs_big = self.EvalToBigInt(node.right)
600
601 if op_id == Id.Arith_PlusEqual:
602 new_big = mops.Add(old_big, rhs_big)
603 elif op_id == Id.Arith_MinusEqual:
604 new_big = mops.Sub(old_big, rhs_big)
605 elif op_id == Id.Arith_StarEqual:
606 new_big = mops.Mul(old_big, rhs_big)
607
608 elif op_id == Id.Arith_SlashEqual:
609 if mops.Equal(rhs_big, mops.ZERO):
610 e_die('Divide by zero') # TODO: location
611 new_big = num.IntDivide(old_big, rhs_big)
612
613 elif op_id == Id.Arith_PercentEqual:
614 if mops.Equal(rhs_big, mops.ZERO):
615 e_die('Divide by zero') # TODO: location
616 new_big = num.IntRemainder(old_big, rhs_big)
617
618 elif op_id == Id.Arith_DGreatEqual:
619 new_big = mops.RShift(old_big, rhs_big)
620 elif op_id == Id.Arith_DLessEqual:
621 new_big = mops.LShift(old_big, rhs_big)
622 elif op_id == Id.Arith_AmpEqual:
623 new_big = mops.BitAnd(old_big, rhs_big)
624 elif op_id == Id.Arith_PipeEqual:
625 new_big = mops.BitOr(old_big, rhs_big)
626 elif op_id == Id.Arith_CaretEqual:
627 new_big = mops.BitXor(old_big, rhs_big)
628 else:
629 raise AssertionError(op_id) # shouldn't get here
630
631 self._Store(lval, new_big)
632 return value.Int(new_big)
633
634 elif case(arith_expr_e.Unary):
635 node = cast(arith_expr.Unary, UP_node)
636 op_id = node.op_id
637
638 i = self.EvalToBigInt(node.child)
639
640 if op_id == Id.Node_UnaryPlus: # +i
641 result = i
642 elif op_id == Id.Node_UnaryMinus: # -i
643 result = mops.Sub(mops.ZERO, i)
644
645 elif op_id == Id.Arith_Bang: # logical negation
646 if mops.Equal(i, mops.ZERO):
647 result = mops.ONE
648 else:
649 result = mops.ZERO
650 elif op_id == Id.Arith_Tilde: # bitwise complement
651 result = mops.BitNot(i)
652 else:
653 raise AssertionError(op_id) # shouldn't get here
654
655 return value.Int(result)
656
657 elif case(arith_expr_e.Binary):
658 node = cast(arith_expr.Binary, UP_node)
659 op_id = node.op_id
660
661 # Short-circuit evaluation for || and &&.
662 if op_id == Id.Arith_DPipe:
663 lhs_big = self.EvalToBigInt(node.left)
664 if mops.Equal(lhs_big, mops.ZERO):
665 rhs_big = self.EvalToBigInt(node.right)
666 if mops.Equal(rhs_big, mops.ZERO):
667 result = mops.ZERO # false
668 else:
669 result = mops.ONE # true
670 else:
671 result = mops.ONE # true
672 return value.Int(result)
673
674 if op_id == Id.Arith_DAmp:
675 lhs_big = self.EvalToBigInt(node.left)
676 if mops.Equal(lhs_big, mops.ZERO):
677 result = mops.ZERO # false
678 else:
679 rhs_big = self.EvalToBigInt(node.right)
680 if mops.Equal(rhs_big, mops.ZERO):
681 result = mops.ZERO # false
682 else:
683 result = mops.ONE # true
684 return value.Int(result)
685
686 if op_id == Id.Arith_LBracket:
687 # NOTE: Similar to bracket_op_e.ArrayIndex in osh/word_eval.py
688
689 left = self.Eval(node.left)
690 UP_left = left
691 with tagswitch(left) as case:
692 if case(value_e.BashArray):
693 array_val = cast(value.BashArray, UP_left)
694 index = mops.BigTruncate(
695 self.EvalToBigInt(node.right))
696 s = word_eval.GetArrayItem(array_val.strs, index)
697
698 elif case(value_e.BashAssoc):
699 left = cast(value.BashAssoc, UP_left)
700 key = self.EvalWordToString(node.right)
701 s = left.d.get(key)
702
703 else:
704 # TODO: Add error context
705 e_die(
706 'Expected array or assoc in index expression, got %s'
707 % ui.ValType(left))
708
709 if s is None:
710 val = value.Undef
711 else:
712 val = value.Str(s)
713
714 return val
715
716 if op_id == Id.Arith_Comma:
717 self.EvalToBigInt(node.left) # throw away result
718 result = self.EvalToBigInt(node.right)
719 return value.Int(result)
720
721 # Rest are integers
722 lhs_big = self.EvalToBigInt(node.left)
723 rhs_big = self.EvalToBigInt(node.right)
724
725 if op_id == Id.Arith_Plus:
726 result = mops.Add(lhs_big, rhs_big)
727 elif op_id == Id.Arith_Minus:
728 result = mops.Sub(lhs_big, rhs_big)
729 elif op_id == Id.Arith_Star:
730 result = mops.Mul(lhs_big, rhs_big)
731 elif op_id == Id.Arith_Slash:
732 if mops.Equal(rhs_big, mops.ZERO):
733 e_die('Divide by zero', loc.Arith(node.right))
734 result = num.IntDivide(lhs_big, rhs_big)
735
736 elif op_id == Id.Arith_Percent:
737 if mops.Equal(rhs_big, mops.ZERO):
738 e_die('Divide by zero', loc.Arith(node.right))
739 result = num.IntRemainder(lhs_big, rhs_big)
740
741 elif op_id == Id.Arith_DStar:
742 if mops.Greater(mops.ZERO, rhs_big):
743 e_die("Exponent can't be a negative number",
744 loc.Arith(node.right))
745 result = num.Exponent(lhs_big, rhs_big)
746
747 elif op_id == Id.Arith_DEqual:
748 result = mops.FromBool(mops.Equal(lhs_big, rhs_big))
749 elif op_id == Id.Arith_NEqual:
750 result = mops.FromBool(not mops.Equal(lhs_big, rhs_big))
751 elif op_id == Id.Arith_Great:
752 result = mops.FromBool(mops.Greater(lhs_big, rhs_big))
753 elif op_id == Id.Arith_GreatEqual:
754 result = mops.FromBool(
755 mops.Greater(lhs_big, rhs_big) or
756 mops.Equal(lhs_big, rhs_big))
757 elif op_id == Id.Arith_Less:
758 result = mops.FromBool(mops.Greater(rhs_big, lhs_big))
759 elif op_id == Id.Arith_LessEqual:
760 result = mops.FromBool(
761 mops.Greater(rhs_big, lhs_big) or
762 mops.Equal(lhs_big, rhs_big))
763
764 elif op_id == Id.Arith_Pipe:
765 result = mops.BitOr(lhs_big, rhs_big)
766 elif op_id == Id.Arith_Amp:
767 result = mops.BitAnd(lhs_big, rhs_big)
768 elif op_id == Id.Arith_Caret:
769 result = mops.BitXor(lhs_big, rhs_big)
770
771 # Note: how to define shift of negative numbers?
772 elif op_id == Id.Arith_DLess:
773 result = mops.LShift(lhs_big, rhs_big)
774 elif op_id == Id.Arith_DGreat:
775 result = mops.RShift(lhs_big, rhs_big)
776 else:
777 raise AssertionError(op_id)
778
779 return value.Int(result)
780
781 elif case(arith_expr_e.TernaryOp):
782 node = cast(arith_expr.TernaryOp, UP_node)
783
784 cond = self.EvalToBigInt(node.cond)
785 if mops.Equal(cond, mops.ZERO):
786 return self.Eval(node.false_expr)
787 else:
788 return self.Eval(node.true_expr)
789
790 else:
791 raise AssertionError(node.tag())
792
793 raise AssertionError('for -Wreturn-type in C++')
794
795 def EvalWordToString(self, node):
796 # type: (arith_expr_t) -> str
797 """
798 Raises:
799 error.FatalRuntime if the expression isn't a string
800 or if it contains a bare variable like a[x]
801
802 These are allowed because they're unambiguous, unlike a[x]
803
804 a[$x] a["$x"] a["x"] a['x']
805 """
806 UP_node = node
807 if node.tag() == arith_expr_e.Word: # $(( $x )) $(( ${x}${y} )), etc.
808 w = cast(CompoundWord, UP_node)
809 val = self.word_ev.EvalWordToString(w)
810 return val.s
811 else:
812 # TODO: location info for original
813 e_die("Associative array keys must be strings: $x 'x' \"$x\" etc.")
814
815 def EvalShellLhs(self, node, which_scopes):
816 # type: (sh_lhs_t, scope_t) -> sh_lvalue_t
817 """Evaluate a shell LHS expression
818
819 For a=b and a[x]=b etc.
820 """
821 assert isinstance(node, sh_lhs_t), node
822
823 UP_node = node
824 lval = None # type: sh_lvalue_t
825 with tagswitch(node) as case:
826 if case(sh_lhs_e.Name): # a=x
827 node = cast(sh_lhs.Name, UP_node)
828 assert node.name is not None
829
830 lval1 = LeftName(node.name, node.left)
831 lval = lval1
832
833 elif case(sh_lhs_e.IndexedName): # a[1+2]=x
834 node = cast(sh_lhs.IndexedName, UP_node)
835 assert node.name is not None
836
837 if self.mem.IsBashAssoc(node.name):
838 key = self.EvalWordToString(node.index)
839 lval2 = sh_lvalue.Keyed(node.name, key, node.left)
840 lval = lval2
841 else:
842 index = mops.BigTruncate(self.EvalToBigInt(node.index))
843 lval3 = sh_lvalue.Indexed(node.name, index, node.left)
844 lval = lval3
845
846 else:
847 raise AssertionError(node.tag())
848
849 return lval
850
851 def _VarNameOrWord(self, anode):
852 # type: (arith_expr_t) -> Tuple[Optional[str], loc_t]
853 """Returns a variable name if the arith node can be interpreted that
854 way."""
855 UP_anode = anode
856 with tagswitch(anode) as case:
857 if case(arith_expr_e.VarSub):
858 tok = cast(NameTok, UP_anode)
859 return (tok.var_name, tok.left)
860
861 elif case(arith_expr_e.Word):
862 w = cast(CompoundWord, UP_anode)
863 var_name = self.EvalWordToString(w)
864 return (var_name, w)
865
866 no_str = None # type: str
867 return (no_str, loc.Missing)
868
869 def EvalArithLhs(self, anode):
870 # type: (arith_expr_t) -> sh_lvalue_t
871 """
872 For (( a[x] = 1 )) etc.
873 """
874 UP_anode = anode
875 if anode.tag() == arith_expr_e.Binary:
876 anode = cast(arith_expr.Binary, UP_anode)
877 if anode.op_id == Id.Arith_LBracket:
878 var_name, location = self._VarNameOrWord(anode.left)
879
880 # (( 1[2] = 3 )) isn't valid
881 if not match.IsValidVarName(var_name):
882 e_die('Invalid variable name %r' % var_name, location)
883
884 if var_name is not None:
885 if self.mem.IsBashAssoc(var_name):
886 key = self.EvalWordToString(anode.right)
887 return sh_lvalue.Keyed(var_name, key, location)
888 else:
889 index = mops.BigTruncate(self.EvalToBigInt(
890 anode.right))
891 return sh_lvalue.Indexed(var_name, index, location)
892
893 var_name, location = self._VarNameOrWord(anode)
894 if var_name is not None:
895 return LeftName(var_name, location)
896
897 # e.g. unset 'x-y'. status 2 for runtime parse error
898 e_die_status(2, 'Invalid LHS to modify', location)
899
900
901class BoolEvaluator(ArithEvaluator):
902 """This is also an ArithEvaluator because it has to understand.
903
904 [[ x -eq 3 ]]
905
906 where x='1+2'
907 """
908
909 def __init__(
910 self,
911 mem, # type: state.Mem
912 exec_opts, # type: optview.Exec
913 mutable_opts, # type: Optional[state.MutableOpts]
914 parse_ctx, # type: Optional[parse_lib.ParseContext]
915 errfmt, # type: ErrorFormatter
916 always_strict=False # type: bool
917 ):
918 # type: (...) -> None
919 ArithEvaluator.__init__(self, mem, exec_opts, mutable_opts, parse_ctx,
920 errfmt)
921 self.always_strict = always_strict
922
923 def _StringToBigIntOrError(self, s, blame_word=None):
924 # type: (str, Optional[word_t]) -> mops.BigInt
925 """Used by both [[ $x -gt 3 ]] and (( $x ))."""
926 if blame_word:
927 location = loc.Word(blame_word) # type: loc_t
928 else:
929 location = loc.Missing
930
931 try:
932 i = self._StringToBigInt(s, location)
933 except error.Strict as e:
934 if self.always_strict or self.exec_opts.strict_arith():
935 raise
936 else:
937 i = mops.ZERO
938 return i
939
940 def _EvalCompoundWord(self, word, eval_flags=0):
941 # type: (word_t, int) -> str
942 val = self.word_ev.EvalWordToString(word, eval_flags)
943 return val.s
944
945 def EvalB(self, node):
946 # type: (bool_expr_t) -> bool
947
948 UP_node = node
949 with tagswitch(node) as case:
950 if case(bool_expr_e.WordTest):
951 node = cast(bool_expr.WordTest, UP_node)
952 s = self._EvalCompoundWord(node.w)
953 return bool(s)
954
955 elif case(bool_expr_e.LogicalNot):
956 node = cast(bool_expr.LogicalNot, UP_node)
957 b = self.EvalB(node.child)
958 return not b
959
960 elif case(bool_expr_e.LogicalAnd):
961 node = cast(bool_expr.LogicalAnd, UP_node)
962 # Short-circuit evaluation
963 if self.EvalB(node.left):
964 return self.EvalB(node.right)
965 else:
966 return False
967
968 elif case(bool_expr_e.LogicalOr):
969 node = cast(bool_expr.LogicalOr, UP_node)
970 if self.EvalB(node.left):
971 return True
972 else:
973 return self.EvalB(node.right)
974
975 elif case(bool_expr_e.Unary):
976 node = cast(bool_expr.Unary, UP_node)
977 op_id = node.op_id
978 s = self._EvalCompoundWord(node.child)
979
980 # Now dispatch on arg type
981 arg_type = consts.BoolArgType(
982 op_id) # could be static in the LST?
983
984 if arg_type == bool_arg_type_e.Path:
985 return bool_stat.DoUnaryOp(op_id, s)
986
987 if arg_type == bool_arg_type_e.Str:
988 if op_id == Id.BoolUnary_z:
989 return not bool(s)
990 if op_id == Id.BoolUnary_n:
991 return bool(s)
992
993 raise AssertionError(op_id) # should never happen
994
995 if arg_type == bool_arg_type_e.Other:
996 if op_id == Id.BoolUnary_t:
997 return bool_stat.isatty(s, node.child)
998
999 # See whether 'set -o' options have been set
1000 if op_id == Id.BoolUnary_o:
1001 index = consts.OptionNum(s)
1002 if index == 0:
1003 return False
1004 else:
1005 return self.exec_opts.opt0_array[index]
1006
1007 if op_id == Id.BoolUnary_v:
1008 val = self.mem.GetValue(s)
1009 return val.tag() != value_e.Undef
1010
1011 e_die("%s isn't implemented" %
1012 ui.PrettyId(op_id)) # implicit location
1013
1014 raise AssertionError(arg_type)
1015
1016 elif case(bool_expr_e.Binary):
1017 node = cast(bool_expr.Binary, UP_node)
1018
1019 op_id = node.op_id
1020 # Whether to glob escape
1021 eval_flags = 0
1022 with switch(op_id) as case2:
1023 if case2(Id.BoolBinary_GlobEqual, Id.BoolBinary_GlobDEqual,
1024 Id.BoolBinary_GlobNEqual):
1025 eval_flags |= word_eval.QUOTE_FNMATCH
1026 elif case2(Id.BoolBinary_EqualTilde):
1027 eval_flags |= word_eval.QUOTE_ERE
1028
1029 s1 = self._EvalCompoundWord(node.left)
1030 s2 = self._EvalCompoundWord(node.right, eval_flags)
1031
1032 # Now dispatch on arg type
1033 arg_type = consts.BoolArgType(op_id)
1034
1035 if arg_type == bool_arg_type_e.Path:
1036 return bool_stat.DoBinaryOp(op_id, s1, s2)
1037
1038 if arg_type == bool_arg_type_e.Int:
1039 # NOTE: We assume they are constants like [[ 3 -eq 3 ]].
1040 # Bash also allows [[ 1+2 -eq 3 ]].
1041 i1 = self._StringToBigIntOrError(s1, blame_word=node.left)
1042 i2 = self._StringToBigIntOrError(s2, blame_word=node.right)
1043
1044 if op_id == Id.BoolBinary_eq:
1045 return mops.Equal(i1, i2)
1046 if op_id == Id.BoolBinary_ne:
1047 return not mops.Equal(i1, i2)
1048 if op_id == Id.BoolBinary_gt:
1049 return mops.Greater(i1, i2)
1050 if op_id == Id.BoolBinary_ge:
1051 return mops.Greater(i1, i2) or mops.Equal(i1, i2)
1052 if op_id == Id.BoolBinary_lt:
1053 return mops.Greater(i2, i1)
1054 if op_id == Id.BoolBinary_le:
1055 return mops.Greater(i2, i1) or mops.Equal(i1, i2)
1056
1057 raise AssertionError(op_id) # should never happen
1058
1059 if arg_type == bool_arg_type_e.Str:
1060 fnmatch_flags = (FNM_CASEFOLD
1061 if self.exec_opts.nocasematch() else 0)
1062
1063 if op_id in (Id.BoolBinary_GlobEqual,
1064 Id.BoolBinary_GlobDEqual):
1065 #log('Matching %s against pattern %s', s1, s2)
1066 return libc.fnmatch(s2, s1, fnmatch_flags)
1067
1068 if op_id == Id.BoolBinary_GlobNEqual:
1069 return not libc.fnmatch(s2, s1, fnmatch_flags)
1070
1071 if op_id in (Id.BoolBinary_Equal, Id.BoolBinary_DEqual):
1072 return s1 == s2
1073
1074 if op_id == Id.BoolBinary_NEqual:
1075 return s1 != s2
1076
1077 if op_id == Id.BoolBinary_EqualTilde:
1078 # TODO: This should go to --debug-file
1079 #log('Matching %r against regex %r', s1, s2)
1080 regex_flags = (REG_ICASE
1081 if self.exec_opts.nocasematch() else 0)
1082
1083 try:
1084 indices = libc.regex_search(s2, regex_flags, s1, 0)
1085 except ValueError as e:
1086 # Status 2 indicates a regex parse error. This is fatal in OSH but
1087 # not in bash, which treats [[ like a command with an exit code.
1088 e_die_status(2, e.message, loc.Word(node.right))
1089
1090 if indices is not None:
1091 self.mem.SetRegexMatch(
1092 RegexMatch(s1, indices, eggex_ops.No))
1093 return True
1094 else:
1095 self.mem.SetRegexMatch(regex_match.No)
1096 return False
1097
1098 if op_id == Id.Op_Less:
1099 return str_cmp(s1, s2) < 0
1100
1101 if op_id == Id.Op_Great:
1102 return str_cmp(s1, s2) > 0
1103
1104 raise AssertionError(op_id) # should never happen
1105
1106 raise AssertionError(node.tag())