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

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