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

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