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

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