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

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