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

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