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

1213 lines, 787 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 parse_lib
59from frontend import reader
60from mycpp import mops
61from mycpp import mylib
62from mycpp.mylib import log, tagswitch, switch, str_cmp
63from osh import bool_stat
64from osh import word_eval
65
66import libc # for fnmatch
67# Import these names directly because the C++ translation uses macros literally.
68from libc import FNM_CASEFOLD, REG_ICASE
69
70from typing import Tuple, Optional, cast, TYPE_CHECKING
71if TYPE_CHECKING:
72 from core import optview
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.ArgvWord('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
297class ArithEvaluator(object):
298 """Shared between arith and bool evaluators.
299
300 They both:
301
302 1. Convert strings to integers, respecting shopt -s strict_arith.
303 2. Look up variables and evaluate words.
304 """
305
306 def __init__(
307 self,
308 mem, # type: state.Mem
309 exec_opts, # type: optview.Exec
310 mutable_opts, # type: state.MutableOpts
311 parse_ctx, # type: Optional[parse_lib.ParseContext]
312 errfmt, # type: ui.ErrorFormatter
313 ):
314 # type: (...) -> None
315 self.word_ev = None # type: word_eval.StringWordEvaluator
316 self.mem = mem
317 self.exec_opts = exec_opts
318 self.mutable_opts = mutable_opts
319 self.parse_ctx = parse_ctx
320 self.errfmt = errfmt
321
322 def CheckCircularDeps(self):
323 # type: () -> None
324 assert self.word_ev is not None
325
326 def _WordToBigInt(self, s, blame_loc):
327 # type: (str, loc_t) -> mops.BigInt
328 """Use bash-like rules to coerce a word to an integer.
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 e_die("Invalid integer constant %r" % s, blame_loc)
391
392 return integer
393
394 def _StringToBigInt(self, s, blame_loc):
395 # type: (str, loc_t) -> mops.BigInt
396 """Use bash-like rules to coerce a string to an integer.
397
398 Runtime parsing enables silly stuff like $(( $(echo 1)$(echo 2) + 1 )) => 13
399
400 bare word: variable
401 quoted word: string
402 """
403 # note: 'test' and '[' never evaluate recursively
404 if self.parse_ctx is None:
405 return self._WordToBigInt(s, blame_loc)
406
407 arena = self.parse_ctx.arena
408
409 # Special case so we don't get EOF error
410 if len(s.strip()) == 0:
411 return mops.ZERO
412
413 # For compatibility: Try to parse it as an expression and evaluate it.
414 a_parser = self.parse_ctx.MakeArithParser(s)
415
416 # TODO: Fill in the variable name
417 with alloc.ctx_SourceCode(arena,
418 source.Variable(None, blame_loc)):
419 try:
420 node2 = a_parser.Parse() # may raise error.Parse
421 except error.Parse as e:
422 self.errfmt.PrettyPrintError(e)
423 e_die('Parse error in recursive arithmetic',
424 e.location)
425
426 # Prevent infinite recursion of $(( 1x )) -- it's a word that evaluates
427 # to itself, and you don't want to reparse it as a word.
428 if node2.tag() == arith_expr_e.Word:
429 return self._WordToBigInt(s, blame_loc)
430
431 if self.exec_opts.eval_unsafe_arith():
432 integer = self.EvalToBigInt(node2)
433 else:
434 # BoolEvaluator doesn't have parse_ctx or mutable_opts
435 assert self.mutable_opts is not None
436
437 # We don't need to flip _allow_process_sub, because they can't be
438 # parsed. See spec/bugs.test.sh.
439 with state.ctx_Option(self.mutable_opts,
440 [option_i._allow_command_sub],
441 False):
442 integer = self.EvalToBigInt(node2)
443
444 return integer
445
446 def _ValToIntOrError(self, val, blame):
447 # type: (value_t, arith_expr_t) -> mops.BigInt
448 try:
449 UP_val = val
450 with tagswitch(val) as case:
451 if case(value_e.Undef):
452 # 'nounset' already handled before got here
453 # Happens upon a[undefined]=42, which unfortunately turns into a[0]=42.
454 e_strict('Undefined value in arithmetic context',
455 loc.Arith(blame))
456
457 elif case(value_e.Int):
458 val = cast(value.Int, UP_val)
459 return val.i
460
461 elif case(value_e.Str):
462 val = cast(value.Str, UP_val)
463 # calls e_strict
464 return self._StringToBigInt(val.s, loc.Arith(blame))
465
466 except error.Strict as e:
467 if self.exec_opts.strict_arith():
468 raise
469 else:
470 return mops.ZERO
471
472 # Arrays and associative arrays always fail -- not controlled by
473 # strict_arith.
474 # In bash, (( a )) is like (( a[0] )), but I don't want that.
475 # And returning '0' gives different results.
476 e_die(
477 "Expected a value convertible to integer, got %s" %
478 ui.ValType(val), loc.Arith(blame))
479
480 def _EvalLhsAndLookupArith(self, node):
481 # type: (arith_expr_t) -> Tuple[mops.BigInt, sh_lvalue_t]
482 """ For x = y and x += y and ++x """
483
484 lval = self.EvalArithLhs(node)
485 val = OldValue(lval, self.mem, self.exec_opts)
486
487 # BASH_LINENO, arr (array name without strict_array), etc.
488 if (val.tag() in (value_e.BashArray, value_e.BashAssoc) and
489 lval.tag() == sh_lvalue_e.Var):
490 named_lval = cast(LeftName, lval)
491 if word_eval.ShouldArrayDecay(named_lval.name, self.exec_opts):
492 if val.tag() == value_e.BashArray:
493 lval = sh_lvalue.Indexed(named_lval.name, 0, loc.Missing)
494 elif val.tag() == value_e.BashAssoc:
495 lval = sh_lvalue.Keyed(named_lval.name, '0', loc.Missing)
496 val = word_eval.DecayArray(val)
497
498 # This error message could be better, but we already have one
499 #if val.tag() == value_e.BashArray:
500 # e_die("Can't use assignment like ++ or += on arrays")
501
502 i = self._ValToIntOrError(val, node)
503 return i, lval
504
505 def _Store(self, lval, new_int):
506 # type: (sh_lvalue_t, mops.BigInt) -> None
507 val = value.Str(mops.ToStr(new_int))
508 state.OshLanguageSetValue(self.mem, lval, val)
509
510 def EvalToBigInt(self, node):
511 # type: (arith_expr_t) -> mops.BigInt
512 """Used externally by ${a[i+1]} and ${a:start:len}.
513
514 Also used internally.
515 """
516 val = self.Eval(node)
517
518 # BASH_LINENO, arr (array name without strict_array), etc.
519 if (val.tag() in (value_e.BashArray, value_e.BashAssoc) and
520 node.tag() == arith_expr_e.VarSub):
521 vsub = cast(Token, node)
522 if word_eval.ShouldArrayDecay(lexer.LazyStr(vsub), self.exec_opts):
523 val = word_eval.DecayArray(val)
524
525 i = self._ValToIntOrError(val, node)
526 return i
527
528 def EvalToInt(self, node):
529 # type: (arith_expr_t) -> int
530 return mops.BigTruncate(self.EvalToBigInt(node))
531
532 def Eval(self, node):
533 # type: (arith_expr_t) -> value_t
534 """
535 Returns:
536 None for Undef (e.g. empty cell) TODO: Don't return 0!
537 int for Str
538 List[int] for BashArray
539 Dict[str, str] for BashAssoc (TODO: Should we support this?)
540
541 NOTE: (( A['x'] = 'x' )) and (( x = A['x'] )) are syntactically valid in
542 bash, but don't do what you'd think. 'x' sometimes a variable name and
543 sometimes a key.
544 """
545 # OSH semantics: Variable NAMES cannot be formed dynamically; but INTEGERS
546 # can. ${foo:-3}4 is OK. $? will be a compound word too, so we don't have
547 # to handle that as a special case.
548
549 UP_node = node
550 with tagswitch(node) as case:
551 if case(arith_expr_e.EmptyZero): # $(( ))
552 return value.Int(mops.ZERO) # Weird axiom
553
554 elif case(arith_expr_e.EmptyOne): # for (( ; ; ))
555 return value.Int(mops.ONE)
556
557 elif case(arith_expr_e.VarSub): # $(( x )) (can be array)
558 vsub = cast(Token, UP_node)
559 var_name = lexer.LazyStr(vsub)
560 val = self.mem.GetValue(var_name)
561 if val.tag() == value_e.Undef and self.exec_opts.nounset():
562 e_die('Undefined variable %r' % var_name, vsub)
563 return val
564
565 elif case(arith_expr_e.Word): # $(( $x )) $(( ${x}${y} )), etc.
566 w = cast(CompoundWord, UP_node)
567 return self.word_ev.EvalWordToString(w)
568
569 elif case(arith_expr_e.UnaryAssign): # a++
570 node = cast(arith_expr.UnaryAssign, UP_node)
571
572 op_id = node.op_id
573 old_big, lval = self._EvalLhsAndLookupArith(node.child)
574
575 if op_id == Id.Node_PostDPlus: # post-increment
576 new_big = mops.Add(old_big, mops.ONE)
577 result = old_big
578
579 elif op_id == Id.Node_PostDMinus: # post-decrement
580 new_big = mops.Sub(old_big, mops.ONE)
581 result = old_big
582
583 elif op_id == Id.Arith_DPlus: # pre-increment
584 new_big = mops.Add(old_big, mops.ONE)
585 result = new_big
586
587 elif op_id == Id.Arith_DMinus: # pre-decrement
588 new_big = mops.Sub(old_big, mops.ONE)
589 result = new_big
590
591 else:
592 raise AssertionError(op_id)
593
594 self._Store(lval, new_big)
595 return value.Int(result)
596
597 elif case(arith_expr_e.BinaryAssign): # a=1, a+=5, a[1]+=5
598 node = cast(arith_expr.BinaryAssign, UP_node)
599 op_id = node.op_id
600
601 if op_id == Id.Arith_Equal:
602 # Don't really need a span ID here, because tdop.CheckLhsExpr should
603 # have done all the validation.
604 lval = self.EvalArithLhs(node.left)
605 rhs_big = self.EvalToBigInt(node.right)
606
607 self._Store(lval, rhs_big)
608 return value.Int(rhs_big)
609
610 old_big, lval = self._EvalLhsAndLookupArith(node.left)
611 rhs_big = self.EvalToBigInt(node.right)
612
613 if op_id == Id.Arith_PlusEqual:
614 new_big = mops.Add(old_big, rhs_big)
615 elif op_id == Id.Arith_MinusEqual:
616 new_big = mops.Sub(old_big, rhs_big)
617 elif op_id == Id.Arith_StarEqual:
618 new_big = mops.Mul(old_big, rhs_big)
619
620 elif op_id == Id.Arith_SlashEqual:
621 if mops.Equal(rhs_big, mops.ZERO):
622 e_die('Divide by zero') # TODO: location
623 new_big = mops.Div(old_big, rhs_big)
624
625 elif op_id == Id.Arith_PercentEqual:
626 if mops.Equal(rhs_big, mops.ZERO):
627 e_die('Divide by zero') # TODO: location
628 new_big = mops.Rem(old_big, rhs_big)
629
630 elif op_id == Id.Arith_DGreatEqual:
631 new_big = mops.RShift(old_big, rhs_big)
632 elif op_id == Id.Arith_DLessEqual:
633 new_big = mops.LShift(old_big, rhs_big)
634 elif op_id == Id.Arith_AmpEqual:
635 new_big = mops.BitAnd(old_big, rhs_big)
636 elif op_id == Id.Arith_PipeEqual:
637 new_big = mops.BitOr(old_big, rhs_big)
638 elif op_id == Id.Arith_CaretEqual:
639 new_big = mops.BitXor(old_big, rhs_big)
640 else:
641 raise AssertionError(op_id) # shouldn't get here
642
643 self._Store(lval, new_big)
644 return value.Int(new_big)
645
646 elif case(arith_expr_e.Unary):
647 node = cast(arith_expr.Unary, UP_node)
648 op_id = node.op_id
649
650 i = self.EvalToBigInt(node.child)
651
652 if op_id == Id.Node_UnaryPlus: # +i
653 result = i
654 elif op_id == Id.Node_UnaryMinus: # -i
655 result = mops.Sub(mops.ZERO, i)
656
657 elif op_id == Id.Arith_Bang: # logical negation
658 if mops.Equal(i, mops.ZERO):
659 result = mops.ONE
660 else:
661 result = mops.ZERO
662 elif op_id == Id.Arith_Tilde: # bitwise complement
663 result = mops.BitNot(i)
664 else:
665 raise AssertionError(op_id) # shouldn't get here
666
667 return value.Int(result)
668
669 elif case(arith_expr_e.Binary):
670 node = cast(arith_expr.Binary, UP_node)
671 op_id = node.op.id
672
673 # Short-circuit evaluation for || and &&.
674 if op_id == Id.Arith_DPipe:
675 lhs_big = self.EvalToBigInt(node.left)
676 if mops.Equal(lhs_big, mops.ZERO):
677 rhs_big = self.EvalToBigInt(node.right)
678 if mops.Equal(rhs_big, mops.ZERO):
679 result = mops.ZERO # false
680 else:
681 result = mops.ONE # true
682 else:
683 result = mops.ONE # true
684 return value.Int(result)
685
686 if op_id == Id.Arith_DAmp:
687 lhs_big = self.EvalToBigInt(node.left)
688 if mops.Equal(lhs_big, mops.ZERO):
689 result = mops.ZERO # false
690 else:
691 rhs_big = self.EvalToBigInt(node.right)
692 if mops.Equal(rhs_big, mops.ZERO):
693 result = mops.ZERO # false
694 else:
695 result = mops.ONE # true
696 return value.Int(result)
697
698 if op_id == Id.Arith_LBracket:
699 # NOTE: Similar to bracket_op_e.ArrayIndex in osh/word_eval.py
700
701 left = self.Eval(node.left)
702 UP_left = left
703 with tagswitch(left) as case:
704 if case(value_e.BashArray):
705 array_val = cast(value.BashArray, UP_left)
706 small_i = mops.BigTruncate(
707 self.EvalToBigInt(node.right))
708 s = word_eval.GetArrayItem(array_val.strs, small_i)
709
710 elif case(value_e.BashAssoc):
711 left = cast(value.BashAssoc, UP_left)
712 key = self.EvalWordToString(node.right)
713 s = left.d.get(key)
714
715 elif case(value_e.Str):
716 left = cast(value.Str, UP_left)
717 if self.exec_opts.strict_arith():
718 e_die(
719 "Value of type Str can't be indexed (strict_arith)",
720 node.op)
721 index = self.EvalToBigInt(node.right)
722 # s[0] evaluates to s
723 # s[1] evaluates to Undef
724 s = left.s if mops.Equal(index,
725 mops.ZERO) else None
726
727 elif case(value_e.Undef):
728 if self.exec_opts.strict_arith():
729 e_die(
730 "Value of type Undef can't be indexed (strict_arith)",
731 node.op)
732 s = None # value.Undef
733
734 # There isn't a way to distinguish Undef vs. empty
735 # string, even with set -o nounset?
736 # s = ''
737
738 else:
739 # TODO: Add error context
740 e_die(
741 "Value of type %s can't be indexed" %
742 ui.ValType(left), node.op)
743
744 if s is None:
745 val = value.Undef
746 else:
747 val = value.Str(s)
748
749 return val
750
751 if op_id == Id.Arith_Comma:
752 self.EvalToBigInt(node.left) # throw away result
753 result = self.EvalToBigInt(node.right)
754 return value.Int(result)
755
756 # Rest are integers
757 lhs_big = self.EvalToBigInt(node.left)
758 rhs_big = self.EvalToBigInt(node.right)
759
760 if op_id == Id.Arith_Plus:
761 result = mops.Add(lhs_big, rhs_big)
762 elif op_id == Id.Arith_Minus:
763 result = mops.Sub(lhs_big, rhs_big)
764 elif op_id == Id.Arith_Star:
765 result = mops.Mul(lhs_big, rhs_big)
766 elif op_id == Id.Arith_Slash:
767 if mops.Equal(rhs_big, mops.ZERO):
768 e_die('Divide by zero', node.op)
769 result = mops.Div(lhs_big, rhs_big)
770
771 elif op_id == Id.Arith_Percent:
772 if mops.Equal(rhs_big, mops.ZERO):
773 e_die('Divide by zero', node.op)
774 result = mops.Rem(lhs_big, rhs_big)
775
776 elif op_id == Id.Arith_DStar:
777 if mops.Greater(mops.ZERO, rhs_big):
778 e_die("Exponent can't be a negative number",
779 loc.Arith(node.right))
780 result = num.Exponent(lhs_big, rhs_big)
781
782 elif op_id == Id.Arith_DEqual:
783 result = mops.FromBool(mops.Equal(lhs_big, rhs_big))
784 elif op_id == Id.Arith_NEqual:
785 result = mops.FromBool(not mops.Equal(lhs_big, rhs_big))
786 elif op_id == Id.Arith_Great:
787 result = mops.FromBool(mops.Greater(lhs_big, rhs_big))
788 elif op_id == Id.Arith_GreatEqual:
789 result = mops.FromBool(
790 mops.Greater(lhs_big, rhs_big) or
791 mops.Equal(lhs_big, rhs_big))
792 elif op_id == Id.Arith_Less:
793 result = mops.FromBool(mops.Greater(rhs_big, lhs_big))
794 elif op_id == Id.Arith_LessEqual:
795 result = mops.FromBool(
796 mops.Greater(rhs_big, lhs_big) or
797 mops.Equal(lhs_big, rhs_big))
798
799 elif op_id == Id.Arith_Pipe:
800 result = mops.BitOr(lhs_big, rhs_big)
801 elif op_id == Id.Arith_Amp:
802 result = mops.BitAnd(lhs_big, rhs_big)
803 elif op_id == Id.Arith_Caret:
804 result = mops.BitXor(lhs_big, rhs_big)
805
806 # Note: how to define shift of negative numbers?
807 elif op_id == Id.Arith_DLess:
808 if mops.Greater(mops.ZERO, rhs_big): # rhs_big < 0
809 raise error.Expr("Can't left shift by negative number",
810 node.op)
811 result = mops.LShift(lhs_big, rhs_big)
812 elif op_id == Id.Arith_DGreat:
813 if mops.Greater(mops.ZERO, rhs_big): # rhs_big < 0
814 raise error.Expr(
815 "Can't right shift by negative number", node.op)
816 result = mops.RShift(lhs_big, rhs_big)
817 else:
818 raise AssertionError(op_id)
819
820 return value.Int(result)
821
822 elif case(arith_expr_e.TernaryOp):
823 node = cast(arith_expr.TernaryOp, UP_node)
824
825 cond = self.EvalToBigInt(node.cond)
826 if mops.Equal(cond, mops.ZERO):
827 return self.Eval(node.false_expr)
828 else:
829 return self.Eval(node.true_expr)
830
831 else:
832 raise AssertionError(node.tag())
833
834 raise AssertionError('for -Wreturn-type in C++')
835
836 def EvalWordToString(self, node, blame_loc=loc.Missing):
837 # type: (arith_expr_t, loc_t) -> str
838 """
839 Raises:
840 error.FatalRuntime if the expression isn't a string
841 or if it contains a bare variable like a[x]
842
843 These are allowed because they're unambiguous, unlike a[x]
844
845 a[$x] a["$x"] a["x"] a['x']
846 """
847 UP_node = node
848 if node.tag() == arith_expr_e.Word: # $(( $x )) $(( ${x}${y} )), etc.
849 w = cast(CompoundWord, UP_node)
850 val = self.word_ev.EvalWordToString(w)
851 return val.s
852 else:
853 # A[x] is the "Parsing Bash is Undecidable" problem
854 # It is a string or var name?
855 # (It's parsed as arith_expr.VarSub)
856 e_die(
857 "Assoc array keys must be strings: $x 'x' \"$x\" etc. (OILS-ERR-101)",
858 blame_loc)
859
860 def EvalShellLhs(self, node, which_scopes):
861 # type: (sh_lhs_t, scope_t) -> sh_lvalue_t
862 """Evaluate a shell LHS expression
863
864 For a=b and a[x]=b etc.
865 """
866 assert isinstance(node, sh_lhs_t), node
867
868 UP_node = node
869 lval = None # type: sh_lvalue_t
870 with tagswitch(node) as case:
871 if case(sh_lhs_e.Name): # a=x
872 node = cast(sh_lhs.Name, UP_node)
873 assert node.name is not None
874
875 lval1 = LeftName(node.name, node.left)
876 lval = lval1
877
878 elif case(sh_lhs_e.IndexedName): # a[1+2]=x
879 node = cast(sh_lhs.IndexedName, UP_node)
880 assert node.name is not None
881
882 if self.mem.IsBashAssoc(node.name):
883 key = self.EvalWordToString(node.index,
884 blame_loc=node.left)
885 # node.left points to A[ in A[x]=1
886 lval2 = sh_lvalue.Keyed(node.name, key, node.left)
887 lval = lval2
888 else:
889 index = mops.BigTruncate(self.EvalToBigInt(node.index))
890 lval3 = sh_lvalue.Indexed(node.name, index, node.left)
891 lval = lval3
892
893 else:
894 raise AssertionError(node.tag())
895
896 return lval
897
898 def _VarNameOrWord(self, anode):
899 # type: (arith_expr_t) -> Tuple[Optional[str], loc_t]
900 """
901 Returns a variable name if the arith node can be interpreted that way.
902 """
903 UP_anode = anode
904 with tagswitch(anode) as case:
905 if case(arith_expr_e.VarSub):
906 tok = cast(Token, UP_anode)
907 return (lexer.LazyStr(tok), tok)
908
909 elif case(arith_expr_e.Word):
910 w = cast(CompoundWord, UP_anode)
911 var_name = self.EvalWordToString(w)
912 return (var_name, w)
913
914 no_str = None # type: str
915 return (no_str, loc.Missing)
916
917 def EvalArithLhs(self, anode):
918 # type: (arith_expr_t) -> sh_lvalue_t
919 """
920 For (( a[x] = 1 )) etc.
921 """
922 UP_anode = anode
923 if anode.tag() == arith_expr_e.Binary:
924 anode = cast(arith_expr.Binary, UP_anode)
925 if anode.op.id == Id.Arith_LBracket:
926 var_name, blame_loc = self._VarNameOrWord(anode.left)
927
928 # (( 1[2] = 3 )) isn't valid
929 if not match.IsValidVarName(var_name):
930 e_die('Invalid variable name %r' % var_name, blame_loc)
931
932 if var_name is not None:
933 if self.mem.IsBashAssoc(var_name):
934 arith_loc = location.TokenForArith(anode)
935 key = self.EvalWordToString(anode.right,
936 blame_loc=arith_loc)
937 return sh_lvalue.Keyed(var_name, key, blame_loc)
938 else:
939 index = mops.BigTruncate(self.EvalToBigInt(
940 anode.right))
941 return sh_lvalue.Indexed(var_name, index, blame_loc)
942
943 var_name, blame_loc = self._VarNameOrWord(anode)
944 if var_name is not None:
945 return LeftName(var_name, blame_loc)
946
947 # e.g. unset 'x-y'. status 2 for runtime parse error
948 e_die_status(2, 'Invalid LHS to modify', blame_loc)
949
950
951class BoolEvaluator(ArithEvaluator):
952 """This is also an ArithEvaluator because it has to understand.
953
954 [[ x -eq 3 ]]
955
956 where x='1+2'
957 """
958
959 def __init__(
960 self,
961 mem, # type: state.Mem
962 exec_opts, # type: optview.Exec
963 mutable_opts, # type: Optional[state.MutableOpts]
964 parse_ctx, # type: Optional[parse_lib.ParseContext]
965 errfmt, # type: ui.ErrorFormatter
966 always_strict=False # type: bool
967 ):
968 # type: (...) -> None
969 ArithEvaluator.__init__(self, mem, exec_opts, mutable_opts, parse_ctx,
970 errfmt)
971 self.always_strict = always_strict
972
973 def _IsDefined(self, s, blame_loc):
974 # type: (str, loc_t) -> bool
975
976 m = util.RegexSearch(consts.TEST_V_RE, s)
977 if m is None:
978 if self.exec_opts.strict_word_eval():
979 e_die('-v expected name or name[index]', blame_loc)
980 return False
981
982 var_name = m[1]
983 index_str = m[3]
984
985 val = self.mem.GetValue(var_name)
986 if len(index_str) == 0: # it's just a variable name
987 return val.tag() != value_e.Undef
988
989 UP_val = val
990 with tagswitch(val) as case:
991 if case(value_e.BashArray):
992 val = cast(value.BashArray, UP_val)
993
994 # TODO: use mops.BigStr
995 try:
996 index = int(index_str)
997 except ValueError as e:
998 if self.exec_opts.strict_word_eval():
999 e_die(
1000 '-v got BashArray and invalid index %r' %
1001 index_str, blame_loc)
1002 return False
1003
1004 if index < 0:
1005 if self.exec_opts.strict_word_eval():
1006 e_die('-v got invalid negative index %s' % index_str,
1007 blame_loc)
1008 return False
1009
1010 if index < len(val.strs):
1011 return val.strs[index] is not None
1012
1013 # out of range
1014 return False
1015
1016 elif case(value_e.BashAssoc):
1017 val = cast(value.BashAssoc, UP_val)
1018 return index_str in val.d
1019
1020 else:
1021 # work around mycpp bug! parses as 'elif'
1022 pass
1023
1024 if self.exec_opts.strict_word_eval():
1025 raise error.TypeErr(val, 'Expected BashArray or BashAssoc',
1026 blame_loc)
1027 return False
1028 raise AssertionError()
1029
1030 def _StringToBigIntOrError(self, s, blame_word=None):
1031 # type: (str, Optional[word_t]) -> mops.BigInt
1032 """Used by both [[ $x -gt 3 ]] and (( $x ))."""
1033 if blame_word:
1034 location = loc.Word(blame_word) # type: loc_t
1035 else:
1036 location = loc.Missing
1037
1038 try:
1039 i = self._StringToBigInt(s, location)
1040 except error.Strict as e:
1041 if self.always_strict or self.exec_opts.strict_arith():
1042 raise
1043 else:
1044 i = mops.ZERO
1045 return i
1046
1047 def _EvalCompoundWord(self, word, eval_flags=0):
1048 # type: (word_t, int) -> str
1049 val = self.word_ev.EvalWordToString(word, eval_flags)
1050 return val.s
1051
1052 def EvalB(self, node):
1053 # type: (bool_expr_t) -> bool
1054
1055 UP_node = node
1056 with tagswitch(node) as case:
1057 if case(bool_expr_e.WordTest):
1058 node = cast(bool_expr.WordTest, UP_node)
1059 s = self._EvalCompoundWord(node.w)
1060 return bool(s)
1061
1062 elif case(bool_expr_e.LogicalNot):
1063 node = cast(bool_expr.LogicalNot, UP_node)
1064 b = self.EvalB(node.child)
1065 return not b
1066
1067 elif case(bool_expr_e.LogicalAnd):
1068 node = cast(bool_expr.LogicalAnd, UP_node)
1069 # Short-circuit evaluation
1070 if self.EvalB(node.left):
1071 return self.EvalB(node.right)
1072 else:
1073 return False
1074
1075 elif case(bool_expr_e.LogicalOr):
1076 node = cast(bool_expr.LogicalOr, UP_node)
1077 if self.EvalB(node.left):
1078 return True
1079 else:
1080 return self.EvalB(node.right)
1081
1082 elif case(bool_expr_e.Unary):
1083 node = cast(bool_expr.Unary, UP_node)
1084 op_id = node.op_id
1085 s = self._EvalCompoundWord(node.child)
1086
1087 # Now dispatch on arg type. (arg_type could be static in the
1088 # LST?)
1089 arg_type = consts.BoolArgType(op_id)
1090
1091 if arg_type == bool_arg_type_e.Path:
1092 return bool_stat.DoUnaryOp(op_id, s)
1093
1094 if arg_type == bool_arg_type_e.Str:
1095 if op_id == Id.BoolUnary_z:
1096 return not bool(s)
1097 if op_id == Id.BoolUnary_n:
1098 return bool(s)
1099
1100 raise AssertionError(op_id) # should never happen
1101
1102 if arg_type == bool_arg_type_e.Other:
1103 if op_id == Id.BoolUnary_t:
1104 return bool_stat.isatty(s, node.child)
1105
1106 # See whether 'set -o' options have been set
1107 if op_id == Id.BoolUnary_o:
1108 index = consts.OptionNum(s)
1109 if index == 0:
1110 return False
1111 else:
1112 return self.exec_opts.opt0_array[index]
1113
1114 if op_id == Id.BoolUnary_v:
1115 return self._IsDefined(s, loc.Word(node.child))
1116
1117 e_die("%s isn't implemented" %
1118 ui.PrettyId(op_id)) # implicit location
1119
1120 raise AssertionError(arg_type)
1121
1122 elif case(bool_expr_e.Binary):
1123 node = cast(bool_expr.Binary, UP_node)
1124
1125 op_id = node.op_id
1126 # Whether to glob escape
1127 eval_flags = 0
1128 with switch(op_id) as case2:
1129 if case2(Id.BoolBinary_GlobEqual, Id.BoolBinary_GlobDEqual,
1130 Id.BoolBinary_GlobNEqual):
1131 eval_flags |= word_eval.QUOTE_FNMATCH
1132 elif case2(Id.BoolBinary_EqualTilde):
1133 eval_flags |= word_eval.QUOTE_ERE
1134
1135 s1 = self._EvalCompoundWord(node.left)
1136 s2 = self._EvalCompoundWord(node.right, eval_flags)
1137
1138 # Now dispatch on arg type
1139 arg_type = consts.BoolArgType(op_id)
1140
1141 if arg_type == bool_arg_type_e.Path:
1142 return bool_stat.DoBinaryOp(op_id, s1, s2)
1143
1144 if arg_type == bool_arg_type_e.Int:
1145 # NOTE: We assume they are constants like [[ 3 -eq 3 ]].
1146 # Bash also allows [[ 1+2 -eq 3 ]].
1147 i1 = self._StringToBigIntOrError(s1, blame_word=node.left)
1148 i2 = self._StringToBigIntOrError(s2, blame_word=node.right)
1149
1150 if op_id == Id.BoolBinary_eq:
1151 return mops.Equal(i1, i2)
1152 if op_id == Id.BoolBinary_ne:
1153 return not mops.Equal(i1, i2)
1154 if op_id == Id.BoolBinary_gt:
1155 return mops.Greater(i1, i2)
1156 if op_id == Id.BoolBinary_ge:
1157 return mops.Greater(i1, i2) or mops.Equal(i1, i2)
1158 if op_id == Id.BoolBinary_lt:
1159 return mops.Greater(i2, i1)
1160 if op_id == Id.BoolBinary_le:
1161 return mops.Greater(i2, i1) or mops.Equal(i1, i2)
1162
1163 raise AssertionError(op_id) # should never happen
1164
1165 if arg_type == bool_arg_type_e.Str:
1166 fnmatch_flags = (FNM_CASEFOLD
1167 if self.exec_opts.nocasematch() else 0)
1168
1169 if op_id in (Id.BoolBinary_GlobEqual,
1170 Id.BoolBinary_GlobDEqual):
1171 #log('Matching %s against pattern %s', s1, s2)
1172 return libc.fnmatch(s2, s1, fnmatch_flags)
1173
1174 if op_id == Id.BoolBinary_GlobNEqual:
1175 return not libc.fnmatch(s2, s1, fnmatch_flags)
1176
1177 if op_id in (Id.BoolBinary_Equal, Id.BoolBinary_DEqual):
1178 return s1 == s2
1179
1180 if op_id == Id.BoolBinary_NEqual:
1181 return s1 != s2
1182
1183 if op_id == Id.BoolBinary_EqualTilde:
1184 # TODO: This should go to --debug-file
1185 #log('Matching %r against regex %r', s1, s2)
1186 regex_flags = (REG_ICASE
1187 if self.exec_opts.nocasematch() else 0)
1188
1189 try:
1190 indices = libc.regex_search(s2, regex_flags, s1, 0)
1191 except ValueError as e:
1192 # Status 2 indicates a regex parse error. This is
1193 # fatal in OSH but not in bash, which treats [[
1194 # like a command with an exit code.
1195 e_die_status(2, e.message, loc.Word(node.right))
1196
1197 if indices is not None:
1198 self.mem.SetRegexMatch(
1199 RegexMatch(s1, indices, eggex_ops.No))
1200 return True
1201 else:
1202 self.mem.SetRegexMatch(regex_match.No)
1203 return False
1204
1205 if op_id == Id.Op_Less:
1206 return str_cmp(s1, s2) < 0
1207
1208 if op_id == Id.Op_Great:
1209 return str_cmp(s1, s2) > 0
1210
1211 raise AssertionError(op_id) # should never happen
1212
1213 raise AssertionError(node.tag())