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

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