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

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