OILS / ysh / expr_eval.py View on Github | oilshell.org

1355 lines, 914 significant
1#!/usr/bin/env python2
2"""expr_eval.py."""
3from __future__ import print_function
4
5from _devbuild.gen.id_kind_asdl import Id
6from _devbuild.gen.syntax_asdl import (
7 loc,
8 loc_t,
9 re,
10 re_e,
11 re_t,
12 Token,
13 SimpleVarSub,
14 word_part,
15 SingleQuoted,
16 DoubleQuoted,
17 BracedVarSub,
18 ShArrayLiteral,
19 CommandSub,
20 expr,
21 expr_e,
22 expr_t,
23 y_lhs_e,
24 y_lhs_t,
25 Attribute,
26 Subscript,
27 class_literal_term,
28 class_literal_term_e,
29 class_literal_term_t,
30 char_class_term_t,
31 PosixClass,
32 PerlClass,
33 CharCode,
34 CharRange,
35 ArgList,
36 Eggex,
37)
38from _devbuild.gen.runtime_asdl import (
39 coerced_e,
40 coerced_t,
41 scope_e,
42 scope_t,
43 part_value,
44 part_value_t,
45 Piece,
46)
47from _devbuild.gen.value_asdl import (value, value_e, value_t, y_lvalue,
48 y_lvalue_e, y_lvalue_t, IntBox, LeftName)
49from core import error
50from core.error import e_die, e_die_status
51from core import num
52from core import pyutil
53from core import state
54from core import ui
55from core import vm
56from frontend import lexer
57from frontend import match
58from frontend import typed_args
59from osh import braces
60from mycpp import mops
61from mycpp.mylib import log, NewDict, switch, tagswitch, print_stderr
62from ysh import func_proc
63from ysh import val_ops
64
65import libc
66
67from typing import cast, Optional, Dict, List, Tuple, TYPE_CHECKING
68
69if TYPE_CHECKING:
70 from osh import cmd_eval
71 from osh import word_eval
72 from osh import split
73
74_ = log
75
76
77def LookupVar(mem, var_name, which_scopes, var_loc):
78 # type: (state.Mem, str, scope_t, loc_t) -> value_t
79
80 # Lookup WITHOUT dynamic scope.
81 val = mem.GetValue(var_name, which_scopes=which_scopes)
82 if val.tag() == value_e.Undef:
83 e_die('Undefined variable %r' % var_name, var_loc)
84
85 return val
86
87
88def _ConvertToInt(val, msg, blame_loc):
89 # type: (value_t, str, loc_t) -> mops.BigInt
90 UP_val = val
91 with tagswitch(val) as case:
92 if case(value_e.Int):
93 val = cast(value.Int, UP_val)
94 return val.i
95
96 elif case(value_e.Str):
97 val = cast(value.Str, UP_val)
98 if match.LooksLikeInteger(val.s):
99 # TODO: Handle ValueError
100 return mops.FromStr(val.s)
101
102 raise error.TypeErr(val, msg, blame_loc)
103
104
105def _ConvertToNumber(val):
106 # type: (value_t) -> Tuple[coerced_t, mops.BigInt, float]
107 UP_val = val
108 with tagswitch(val) as case:
109 if case(value_e.Int):
110 val = cast(value.Int, UP_val)
111 return coerced_e.Int, val.i, -1.0
112
113 elif case(value_e.Float):
114 val = cast(value.Float, UP_val)
115 return coerced_e.Float, mops.MINUS_ONE, val.f
116
117 elif case(value_e.Str):
118 val = cast(value.Str, UP_val)
119 if match.LooksLikeInteger(val.s):
120 # TODO: Handle ValueError
121 return coerced_e.Int, mops.FromStr(val.s), -1.0
122
123 if match.LooksLikeFloat(val.s):
124 return coerced_e.Float, mops.MINUS_ONE, float(val.s)
125
126 return coerced_e.Neither, mops.MINUS_ONE, -1.0
127
128
129def _ConvertForBinaryOp(left, right):
130 # type: (value_t, value_t) -> Tuple[coerced_t, mops.BigInt, mops.BigInt, float, float]
131 """
132 Returns one of
133 value_e.Int or value_e.Float
134 2 ints or 2 floats
135
136 To indicate which values the operation should be done on
137 """
138 c1, i1, f1 = _ConvertToNumber(left)
139 c2, i2, f2 = _ConvertToNumber(right)
140
141 nope = mops.MINUS_ONE
142
143 if c1 == coerced_e.Int and c2 == coerced_e.Int:
144 return coerced_e.Int, i1, i2, -1.0, -1.0
145
146 elif c1 == coerced_e.Int and c2 == coerced_e.Float:
147 return coerced_e.Float, nope, nope, mops.ToFloat(i1), f2
148
149 elif c1 == coerced_e.Float and c2 == coerced_e.Int:
150 return coerced_e.Float, nope, nope, f1, mops.ToFloat(i2)
151
152 elif c1 == coerced_e.Float and c2 == coerced_e.Float:
153 return coerced_e.Float, nope, nope, f1, f2
154
155 else:
156 # No operation is valid
157 return coerced_e.Neither, nope, nope, -1.0, -1.0
158
159
160class ExprEvaluator(object):
161 """Shared between arith and bool evaluators.
162
163 They both:
164
165 1. Convert strings to integers, respecting shopt -s strict_arith.
166 2. Look up variables and evaluate words.
167 """
168
169 def __init__(
170 self,
171 mem, # type: state.Mem
172 mutable_opts, # type: state.MutableOpts
173 methods, # type: Dict[int, Dict[str, vm._Callable]]
174 splitter, # type: split.SplitContext
175 errfmt, # type: ui.ErrorFormatter
176 ):
177 # type: (...) -> None
178 self.shell_ex = None # type: vm._Executor
179 self.cmd_ev = None # type: cmd_eval.CommandEvaluator
180 self.word_ev = None # type: word_eval.AbstractWordEvaluator
181
182 self.mem = mem
183 self.mutable_opts = mutable_opts
184 self.methods = methods
185 self.splitter = splitter
186 self.errfmt = errfmt
187
188 def CheckCircularDeps(self):
189 # type: () -> None
190 assert self.shell_ex is not None
191 assert self.word_ev is not None
192
193 def _LookupVar(self, name, var_loc):
194 # type: (str, loc_t) -> value_t
195 return LookupVar(self.mem, name, scope_e.LocalOrGlobal, var_loc)
196
197 def EvalAugmented(self, lval, rhs_val, op, which_scopes):
198 # type: (y_lvalue_t, value_t, Token, scope_t) -> None
199 """ setvar x +=1, setvar L[0] -= 1
200
201 Called by CommandEvaluator
202 """
203 # TODO: It might be nice to do auto d[x] += 1 too
204
205 UP_lval = lval
206 with tagswitch(lval) as case:
207 if case(y_lvalue_e.Local): # setvar x += 1
208 lval = cast(LeftName, UP_lval)
209 lhs_val = self._LookupVar(lval.name, lval.blame_loc)
210 if op.id in (Id.Arith_PlusEqual, Id.Arith_MinusEqual,
211 Id.Arith_StarEqual, Id.Arith_SlashEqual):
212 new_val = self._ArithIntFloat(lhs_val, rhs_val, op)
213 else:
214 new_val = self._ArithIntOnly(lhs_val, rhs_val, op)
215
216 self.mem.SetNamed(lval, new_val, which_scopes)
217
218 elif case(y_lvalue_e.Container): # setvar d.key += 1
219 lval = cast(y_lvalue.Container, UP_lval)
220
221 obj = lval.obj
222 UP_obj = obj
223
224 lhs_val_ = None # type: value_t
225 # Similar to command_e.Mutation
226 with tagswitch(obj) as case:
227 if case(value_e.List):
228 obj = cast(value.List, UP_obj)
229 index = val_ops.ToInt(lval.index,
230 'List index should be Int',
231 loc.Missing)
232 lhs_val_ = obj.items[index]
233
234 elif case(value_e.Dict):
235 obj = cast(value.Dict, UP_obj)
236 index = -1 # silence C++ warning
237 key = val_ops.ToStr(lval.index,
238 'Dict index should be Str',
239 loc.Missing)
240 lhs_val_ = obj.d[key]
241
242 else:
243 raise error.TypeErr(
244 obj, "obj[index] expected List or Dict",
245 loc.Missing)
246
247 if op.id in (Id.Arith_PlusEqual, Id.Arith_MinusEqual,
248 Id.Arith_StarEqual, Id.Arith_SlashEqual):
249 new_val_ = self._ArithIntFloat(lhs_val_, rhs_val, op)
250 else:
251 new_val_ = self._ArithIntOnly(lhs_val_, rhs_val, op)
252
253 with tagswitch(obj) as case:
254 if case(value_e.List):
255 obj = cast(value.List, UP_obj)
256 assert index != -1, 'Should have been initialized'
257 obj.items[index] = new_val_
258
259 elif case(value_e.Dict):
260 obj = cast(value.Dict, UP_obj)
261 obj.d[key] = new_val_
262
263 else:
264 raise AssertionError()
265
266 def _EvalLhsExpr(self, lhs):
267 # type: (y_lhs_t) -> y_lvalue_t
268
269 UP_lhs = lhs
270 with tagswitch(lhs) as case:
271 if case(y_lhs_e.Var):
272 lhs = cast(Token, UP_lhs)
273 return LeftName(lexer.LazyStr(lhs), lhs)
274
275 elif case(y_lhs_e.Subscript):
276 lhs = cast(Subscript, UP_lhs)
277 # setvar mylist[0] = 42
278 # setvar mydict['key'] = 42
279
280 lval = self._EvalExpr(lhs.obj)
281 index = self._EvalExpr(lhs.index)
282 #log('index %s', index)
283 return y_lvalue.Container(lval, index)
284
285 elif case(y_lhs_e.Attribute):
286 lhs = cast(Attribute, UP_lhs)
287 assert lhs.op.id == Id.Expr_Dot
288
289 # setvar mydict.key = 42
290 lval = self._EvalExpr(lhs.obj)
291
292 attr = value.Str(lhs.attr_name)
293 return y_lvalue.Container(lval, attr)
294
295 else:
296 raise AssertionError()
297
298 def EvalExpr(self, node, blame_loc):
299 # type: (expr_t, loc_t) -> value_t
300 """Public API for _EvalExpr to ensure command_sub_errexit"""
301 self.mem.SetLocationForExpr(blame_loc)
302 # Pure C++ won't need to catch exceptions
303 with state.ctx_YshExpr(self.mutable_opts):
304 val = self._EvalExpr(node)
305 return val
306
307 def EvalLhsExpr(self, lhs):
308 # type: (y_lhs_t) -> y_lvalue_t
309 """Public API for _EvalLhsExpr to ensure command_sub_errexit"""
310 with state.ctx_YshExpr(self.mutable_opts):
311 lval = self._EvalLhsExpr(lhs)
312 return lval
313
314 def EvalExprSub(self, part):
315 # type: (word_part.ExprSub) -> part_value_t
316
317 val = self.EvalExpr(part.child, part.left)
318
319 with switch(part.left.id) as case:
320 if case(Id.Left_DollarBracket): # $[join(x)]
321 s = val_ops.Stringify(val, loc.WordPart(part))
322 return Piece(s, False, False)
323
324 elif case(Id.Lit_AtLBracket): # @[split(x)]
325 strs = val_ops.ToShellArray(val,
326 loc.WordPart(part),
327 prefix='Expr splice ')
328 return part_value.Array(strs)
329
330 else:
331 raise AssertionError(part.left)
332
333 def PluginCall(self, func_val, pos_args):
334 # type: (value.Func, List[value_t]) -> value_t
335 """For renderPrompt()
336
337 Similar to
338 - WordEvaluator.EvalForPlugin(), which evaluates $PS1 outside main loop
339 - ReadlineCallback.__call__, which executes shell outside main loop
340 """
341 with state.ctx_YshExpr(self.mutable_opts):
342 with state.ctx_Registers(self.mem): # to sandbox globals
343 named_args = {} # type: Dict[str, value_t]
344 arg_list = ArgList.CreateNull() # There's no call site
345 rd = typed_args.Reader(pos_args, named_args, arg_list)
346
347 try:
348 val = func_proc.CallUserFunc(func_val, rd, self.mem,
349 self.cmd_ev)
350 except error.FatalRuntime as e:
351 val = value.Str('<Runtime error: %s>' %
352 e.UserErrorString())
353
354 except (IOError, OSError) as e:
355 val = value.Str('<I/O error: %s>' % pyutil.strerror(e))
356
357 except KeyboardInterrupt:
358 val = value.Str('<Ctrl-C>')
359
360 return val
361
362 def CallConvertFunc(self, func_val, arg, convert_tok, call_loc):
363 # type: (value_t, value_t, Token, loc_t) -> value_t
364 """ For Eggex captures """
365 with state.ctx_YshExpr(self.mutable_opts):
366 pos_args = [arg]
367 named_args = {} # type: Dict[str, value_t]
368 arg_list = ArgList.CreateNull() # There's no call site
369 rd = typed_args.Reader(pos_args, named_args, arg_list)
370 rd.SetFallbackLocation(convert_tok)
371 try:
372 val = self._CallFunc(func_val, rd)
373 except error.FatalRuntime as e:
374 func_name = lexer.TokenVal(convert_tok)
375 self.errfmt.Print_(
376 'Fatal error calling Eggex conversion func %r from this Match accessor'
377 % func_name, call_loc)
378 print_stderr('')
379 raise
380
381 return val
382
383 def SpliceValue(self, val, part):
384 # type: (value_t, word_part.Splice) -> List[str]
385 """ write -- @myvar """
386 return val_ops.ToShellArray(val, loc.WordPart(part), prefix='Splice ')
387
388 def _EvalConst(self, node):
389 # type: (expr.Const) -> value_t
390 return node.val
391
392 def _EvalUnary(self, node):
393 # type: (expr.Unary) -> value_t
394
395 val = self._EvalExpr(node.child)
396
397 with switch(node.op.id) as case:
398 if case(Id.Arith_Minus):
399 c1, i1, f1 = _ConvertToNumber(val)
400 if c1 == coerced_e.Int:
401 return value.Int(mops.Negate(i1))
402 if c1 == coerced_e.Float:
403 return value.Float(-f1)
404 raise error.TypeErr(val, 'Negation expected Int or Float',
405 node.op)
406
407 elif case(Id.Arith_Tilde):
408 i = _ConvertToInt(val, '~ expected Int', node.op)
409 return value.Int(mops.BitNot(i))
410
411 elif case(Id.Expr_Not):
412 b = val_ops.ToBool(val)
413 return value.Bool(False if b else True)
414
415 # &s &a[0] &d.key &d.nested.other
416 elif case(Id.Arith_Amp):
417 # Only 3 possibilities:
418 # - expr.Var
419 # - expr.Attribute with `.` operator (d.key)
420 # - expr.SubScript
421 #
422 # See _EvalLhsExpr, which gives you y_lvalue
423
424 # TODO: &x, &a[0], &d.key, creates a value.Place?
425 # If it's Attribute or SubScript, you don't evaluate them.
426 # y_lvalue_t -> place_t
427
428 raise NotImplementedError(node.op)
429
430 else:
431 raise AssertionError(node.op)
432
433 raise AssertionError('for C++ compiler')
434
435 def _ArithIntFloat(self, left, right, op):
436 # type: (value_t, value_t, Token) -> value_t
437 """
438 Note: may be replaced with arithmetic on tagged integers, e.g. 60 bit
439 with overflow detection
440 """
441 c, i1, i2, f1, f2 = _ConvertForBinaryOp(left, right)
442
443 op_id = op.id
444
445 if c == coerced_e.Int:
446 with switch(op_id) as case:
447 if case(Id.Arith_Plus, Id.Arith_PlusEqual):
448 return value.Int(mops.Add(i1, i2))
449 elif case(Id.Arith_Minus, Id.Arith_MinusEqual):
450 return value.Int(mops.Sub(i1, i2))
451 elif case(Id.Arith_Star, Id.Arith_StarEqual):
452 return value.Int(mops.Mul(i1, i2))
453 elif case(Id.Arith_Slash, Id.Arith_SlashEqual):
454 if mops.Equal(i2, mops.ZERO):
455 raise error.Expr('Divide by zero', op)
456 return value.Float(mops.ToFloat(i1) / mops.ToFloat(i2))
457 else:
458 raise AssertionError()
459
460 elif c == coerced_e.Float:
461 with switch(op_id) as case:
462 if case(Id.Arith_Plus, Id.Arith_PlusEqual):
463 return value.Float(f1 + f2)
464 elif case(Id.Arith_Minus, Id.Arith_MinusEqual):
465 return value.Float(f1 - f2)
466 elif case(Id.Arith_Star, Id.Arith_StarEqual):
467 return value.Float(f1 * f2)
468 elif case(Id.Arith_Slash, Id.Arith_SlashEqual):
469 if f2 == 0.0:
470 raise error.Expr('Divide by zero', op)
471 return value.Float(f1 / f2)
472 else:
473 raise AssertionError()
474
475 else:
476 raise error.TypeErrVerbose(
477 'Binary operator expected numbers, got %s and %s' %
478 (ui.ValType(left), ui.ValType(right)), op)
479
480 def _ArithIntOnly(self, left, right, op):
481 # type: (value_t, value_t, Token) -> value_t
482
483 i1 = _ConvertToInt(left, 'Left operand should be Int', op)
484 i2 = _ConvertToInt(right, 'Right operand should be Int', op)
485
486 with switch(op.id) as case:
487
488 # a % b setvar a %= b
489 if case(Id.Arith_Percent, Id.Arith_PercentEqual):
490 if mops.Equal(i2, mops.ZERO):
491 raise error.Expr('Divide by zero', op)
492 if mops.Greater(mops.ZERO, i2):
493 # Disallow this to remove confusion between modulus and remainder
494 raise error.Expr("Divisor can't be negative", op)
495
496 return value.Int(num.IntRemainder(i1, i2))
497
498 # a // b setvar a //= b
499 elif case(Id.Expr_DSlash, Id.Expr_DSlashEqual):
500 if mops.Equal(i2, mops.ZERO):
501 raise error.Expr('Divide by zero', op)
502 return value.Int(num.IntDivide(i1, i2))
503
504 # a ** b setvar a **= b (ysh only)
505 elif case(Id.Arith_DStar, Id.Expr_DStarEqual):
506 # Same as sh_expr_eval.py
507 if mops.Greater(mops.ZERO, i2):
508 raise error.Expr("Exponent can't be a negative number", op)
509 return value.Int(num.Exponent(i1, i2))
510
511 # Bitwise
512 elif case(Id.Arith_Amp, Id.Arith_AmpEqual): # &
513 return value.Int(mops.BitAnd(i1, i2))
514
515 elif case(Id.Arith_Pipe, Id.Arith_PipeEqual): # |
516 return value.Int(mops.BitOr(i1, i2))
517
518 elif case(Id.Arith_Caret, Id.Arith_CaretEqual): # ^
519 return value.Int(mops.BitXor(i1, i2))
520
521 elif case(Id.Arith_DGreat, Id.Arith_DGreatEqual): # >>
522 return value.Int(mops.RShift(i1, i2))
523
524 elif case(Id.Arith_DLess, Id.Arith_DLessEqual): # <<
525 return value.Int(mops.LShift(i1, i2))
526
527 else:
528 raise AssertionError(op.id)
529
530 def _Concat(self, left, right, op):
531 # type: (value_t, value_t, Token) -> value_t
532 UP_left = left
533 UP_right = right
534
535 if left.tag() == value_e.Str and right.tag() == value_e.Str:
536 left = cast(value.Str, UP_left)
537 right = cast(value.Str, UP_right)
538
539 return value.Str(left.s + right.s)
540
541 elif left.tag() == value_e.List and right.tag() == value_e.List:
542 left = cast(value.List, UP_left)
543 right = cast(value.List, UP_right)
544
545 c = list(left.items) # mycpp rewrite of L1 + L2
546 c.extend(right.items)
547 return value.List(c)
548
549 else:
550 raise error.TypeErrVerbose(
551 'Expected Str ++ Str or List ++ List, got %s ++ %s' %
552 (ui.ValType(left), ui.ValType(right)), op)
553
554 def _EvalBinary(self, node):
555 # type: (expr.Binary) -> value_t
556
557 left = self._EvalExpr(node.left)
558
559 # Logical and/or lazily evaluate
560 with switch(node.op.id) as case:
561 if case(Id.Expr_And):
562 if val_ops.ToBool(left): # no errors
563 return self._EvalExpr(node.right)
564 else:
565 return left
566
567 elif case(Id.Expr_Or):
568 if val_ops.ToBool(left):
569 return left
570 else:
571 return self._EvalExpr(node.right)
572
573 # These operators all eagerly evaluate
574 right = self._EvalExpr(node.right)
575
576 with switch(node.op.id) as case:
577 if case(Id.Arith_DPlus): # a ++ b to concat Str or List
578 return self._Concat(left, right, node.op)
579
580 elif case(Id.Arith_Plus, Id.Arith_Minus, Id.Arith_Star,
581 Id.Arith_Slash):
582 return self._ArithIntFloat(left, right, node.op)
583
584 else:
585 return self._ArithIntOnly(left, right, node.op)
586
587 def _CompareNumeric(self, left, right, op):
588 # type: (value_t, value_t, Token) -> bool
589 c, i1, i2, f1, f2 = _ConvertForBinaryOp(left, right)
590
591 if c == coerced_e.Int:
592 with switch(op.id) as case:
593 if case(Id.Arith_Less):
594 return mops.Greater(i2, i1)
595 elif case(Id.Arith_Great):
596 return mops.Greater(i1, i2)
597 elif case(Id.Arith_LessEqual):
598 return mops.Greater(i2, i1) or mops.Equal(i1, i2)
599 elif case(Id.Arith_GreatEqual):
600 return mops.Greater(i1, i2) or mops.Equal(i1, i2)
601 else:
602 raise AssertionError()
603
604 elif c == coerced_e.Float:
605 with switch(op.id) as case:
606 if case(Id.Arith_Less):
607 return f1 < f2
608 elif case(Id.Arith_Great):
609 return f1 > f2
610 elif case(Id.Arith_LessEqual):
611 return f1 <= f2
612 elif case(Id.Arith_GreatEqual):
613 return f1 >= f2
614 else:
615 raise AssertionError()
616
617 else:
618 raise error.TypeErrVerbose(
619 'Comparison operator expected numbers, got %s and %s' %
620 (ui.ValType(left), ui.ValType(right)), op)
621
622 def _EvalCompare(self, node):
623 # type: (expr.Compare) -> value_t
624
625 left = self._EvalExpr(node.left)
626 result = True # Implicit and
627 for i, op in enumerate(node.ops):
628 right_expr = node.comparators[i]
629
630 right = self._EvalExpr(right_expr)
631
632 if op.id in (Id.Arith_Less, Id.Arith_Great, Id.Arith_LessEqual,
633 Id.Arith_GreatEqual):
634 result = self._CompareNumeric(left, right, op)
635
636 elif op.id == Id.Expr_TEqual:
637 if left.tag() != right.tag():
638 result = False
639 else:
640 result = val_ops.ExactlyEqual(left, right, op)
641 elif op.id == Id.Expr_NotDEqual:
642 if left.tag() != right.tag():
643 result = True
644 else:
645 result = not val_ops.ExactlyEqual(left, right, op)
646
647 elif op.id == Id.Expr_In:
648 result = val_ops.Contains(left, right)
649 elif op.id == Id.Node_NotIn:
650 result = not val_ops.Contains(left, right)
651
652 elif op.id == Id.Expr_Is:
653 if left.tag() != right.tag():
654 raise error.TypeErrVerbose('Mismatched types', op)
655 result = left is right
656
657 elif op.id == Id.Node_IsNot:
658 if left.tag() != right.tag():
659 raise error.TypeErrVerbose('Mismatched types', op)
660 result = left is not right
661
662 elif op.id == Id.Expr_DTilde:
663 # no extglob in Oil language; use eggex
664 if left.tag() != value_e.Str:
665 raise error.TypeErrVerbose('LHS must be Str', op)
666
667 if right.tag() != value_e.Str:
668 raise error.TypeErrVerbose('RHS must be Str', op)
669
670 UP_left = left
671 UP_right = right
672 left = cast(value.Str, UP_left)
673 right = cast(value.Str, UP_right)
674 return value.Bool(libc.fnmatch(right.s, left.s))
675
676 elif op.id == Id.Expr_NotDTilde:
677 if left.tag() != value_e.Str:
678 raise error.TypeErrVerbose('LHS must be Str', op)
679
680 if right.tag() != value_e.Str:
681 raise error.TypeErrVerbose('RHS must be Str', op)
682
683 UP_left = left
684 UP_right = right
685 left = cast(value.Str, UP_left)
686 right = cast(value.Str, UP_right)
687 return value.Bool(not libc.fnmatch(right.s, left.s))
688
689 elif op.id == Id.Expr_TildeDEqual:
690 # Approximate equality
691 UP_left = left
692 if left.tag() != value_e.Str:
693 e_die('~== expects a string on the left', op)
694
695 left = cast(value.Str, UP_left)
696 left2 = left.s.strip()
697
698 UP_right = right
699 with tagswitch(right) as case:
700 if case(value_e.Str):
701 right = cast(value.Str, UP_right)
702 return value.Bool(left2 == right.s)
703
704 elif case(value_e.Bool):
705 right = cast(value.Bool, UP_right)
706 left2 = left2.lower()
707 lb = False
708 if left2 == 'true':
709 lb = True
710 elif left2 == 'false':
711 lb = False
712 else:
713 return value.Bool(False)
714
715 #log('left %r left2 %r', left, left2)
716 return value.Bool(lb == right.b)
717
718 elif case(value_e.Int):
719 right = cast(value.Int, UP_right)
720 if not left2.isdigit():
721 return value.Bool(False)
722
723 eq = mops.Equal(mops.FromStr(left2), right.i)
724 return value.Bool(eq)
725
726 e_die('~== expects Str, Int, or Bool on the right', op)
727
728 else:
729 try:
730 if op.id == Id.Arith_Tilde:
731 result = val_ops.MatchRegex(left, right, self.mem)
732
733 elif op.id == Id.Expr_NotTilde:
734 # don't pass self.mem to not set a match
735 result = not val_ops.MatchRegex(left, right, None)
736
737 else:
738 raise AssertionError(op)
739 except ValueError as e:
740 # Status 2 indicates a regex parse error, as with [[ in OSH
741 e_die_status(2, e.message, op)
742
743 if not result:
744 return value.Bool(result)
745
746 left = right
747
748 return value.Bool(result)
749
750 def _CallFunc(self, to_call, rd):
751 # type: (value_t, typed_args.Reader) -> value_t
752
753 # Now apply args to either builtin or user-defined function
754 UP_to_call = to_call
755 with tagswitch(to_call) as case:
756 if case(value_e.Func):
757 to_call = cast(value.Func, UP_to_call)
758
759 return func_proc.CallUserFunc(to_call, rd, self.mem,
760 self.cmd_ev)
761
762 elif case(value_e.BuiltinFunc):
763 to_call = cast(value.BuiltinFunc, UP_to_call)
764
765 # C++ cast to work around ASDL 'any'
766 f = cast(vm._Callable, to_call.callable)
767 return f.Call(rd)
768 else:
769 raise AssertionError("Shouldn't have been bound")
770
771 def _EvalFuncCall(self, node):
772 # type: (expr.FuncCall) -> value_t
773
774 func = self._EvalExpr(node.func)
775 UP_func = func
776
777 # The () operator has a 2x2 matrix of
778 # (free, bound) x (builtin, user-defined)
779
780 # Eval args first
781 with tagswitch(func) as case:
782 if case(value_e.Func, value_e.BuiltinFunc):
783 to_call = func
784 pos_args, named_args = func_proc._EvalArgList(self, node.args)
785 rd = typed_args.Reader(pos_args, named_args, node.args)
786
787 elif case(value_e.BoundFunc):
788 func = cast(value.BoundFunc, UP_func)
789
790 to_call = func.func
791 pos_args, named_args = func_proc._EvalArgList(self,
792 node.args,
793 me=func.me)
794 rd = typed_args.Reader(pos_args,
795 named_args,
796 node.args,
797 is_bound=True)
798 else:
799 raise error.TypeErr(func, 'Expected a function or method',
800 node.args.left)
801
802 return self._CallFunc(to_call, rd)
803
804 def _EvalSubscript(self, node):
805 # type: (Subscript) -> value_t
806
807 obj = self._EvalExpr(node.obj)
808 index = self._EvalExpr(node.index)
809
810 UP_obj = obj
811 UP_index = index
812
813 with tagswitch(obj) as case:
814 if case(value_e.Str):
815 # Note: s[i] and s[i:j] are like Go, on bytes. We may provide
816 # s->numBytes(), s->countRunes(), and iteration over runes.
817 obj = cast(value.Str, UP_obj)
818 with tagswitch(index) as case2:
819 if case2(value_e.Slice):
820 index = cast(value.Slice, UP_index)
821
822 lower = index.lower.i if index.lower else 0
823 upper = index.upper.i if index.upper else len(obj.s)
824 return value.Str(obj.s[lower:upper])
825
826 elif case2(value_e.Int):
827 index = cast(value.Int, UP_index)
828 i = mops.BigTruncate(index.i)
829 try:
830 return value.Str(obj.s[i])
831 except IndexError:
832 # TODO: expr.Subscript has no error location
833 raise error.Expr('index out of range', loc.Missing)
834
835 else:
836 raise error.TypeErr(index,
837 'Str index expected Int or Slice',
838 loc.Missing)
839
840 elif case(value_e.List):
841 obj = cast(value.List, UP_obj)
842 with tagswitch(index) as case2:
843 if case2(value_e.Slice):
844 index = cast(value.Slice, UP_index)
845
846 lower = index.lower.i if index.lower else 0
847 upper = index.upper.i if index.upper else len(
848 obj.items)
849 return value.List(obj.items[lower:upper])
850
851 elif case2(value_e.Int):
852 index = cast(value.Int, UP_index)
853 i = mops.BigTruncate(index.i)
854 try:
855 return obj.items[i]
856 except IndexError:
857 # TODO: expr.Subscript has no error location
858 raise error.Expr('index out of range', loc.Missing)
859
860 else:
861 raise error.TypeErr(
862 index, 'List index expected Int or Slice',
863 loc.Missing)
864
865 elif case(value_e.Dict):
866 obj = cast(value.Dict, UP_obj)
867 if index.tag() != value_e.Str:
868 raise error.TypeErr(index, 'Dict index expected Str',
869 loc.Missing)
870
871 index = cast(value.Str, UP_index)
872 try:
873 return obj.d[index.s]
874 except KeyError:
875 # TODO: expr.Subscript has no error location
876 raise error.Expr('Dict entry %r not found' % index.s,
877 loc.Missing)
878
879 raise error.TypeErr(obj, 'Subscript expected Str, List, or Dict',
880 loc.Missing)
881
882 def _EvalAttribute(self, node):
883 # type: (Attribute) -> value_t
884
885 o = self._EvalExpr(node.obj)
886 UP_o = o
887
888 with switch(node.op.id) as case:
889 # Right now => is a synonym for ->
890 # Later we may enforce that => is pure, and -> is for mutation and
891 # I/O.
892 if case(Id.Expr_RArrow, Id.Expr_RDArrow):
893 name = node.attr_name
894 # Look up builtin methods
895 type_methods = self.methods.get(o.tag())
896 vm_callable = (type_methods.get(name)
897 if type_methods is not None else None)
898 if vm_callable:
899 func_val = value.BuiltinFunc(vm_callable)
900 return value.BoundFunc(o, func_val)
901
902 # If the operator is ->, fail because we don't have any
903 # user-defined methods
904 if node.op.id == Id.Expr_RArrow:
905 raise error.TypeErrVerbose(
906 'Method %r does not exist on type %s' %
907 (name, ui.ValType(o)), node.attr)
908
909 # Operator is =>, so try function chaining.
910
911 # Instead of str(f()) => upper()
912 # or str(f()).upper() as in Pythohn
913 #
914 # It's more natural to write
915 # f() => str() => upper()
916
917 # Could improve error message: may give "Undefined variable"
918 val = self._LookupVar(name, node.attr)
919
920 with tagswitch(val) as case2:
921 if case2(value_e.Func, value_e.BuiltinFunc):
922 return value.BoundFunc(o, val)
923 else:
924 raise error.TypeErr(
925 val, 'Fat arrow => expects method or function',
926 node.attr)
927
928 elif case(Id.Expr_Dot): # d.key is like d['key']
929 name = node.attr_name
930 with tagswitch(o) as case2:
931 if case2(value_e.Dict):
932 o = cast(value.Dict, UP_o)
933 try:
934 result = o.d[name]
935 except KeyError:
936 raise error.Expr('Dict entry %r not found' % name,
937 node.op)
938
939 else:
940 raise error.TypeErr(o, 'Dot operator expected Dict',
941 node.op)
942
943 return result
944
945 else:
946 raise AssertionError(node.op)
947
948 def _EvalExpr(self, node):
949 # type: (expr_t) -> value_t
950 """Turn an expression into a value."""
951 if 0:
952 print('_EvalExpr()')
953 node.PrettyPrint()
954 print('')
955
956 UP_node = node
957 with tagswitch(node) as case:
958 if case(expr_e.Const):
959 node = cast(expr.Const, UP_node)
960 return self._EvalConst(node)
961
962 elif case(expr_e.Var):
963 node = cast(expr.Var, UP_node)
964 return self._LookupVar(node.name, node.left)
965
966 elif case(expr_e.Place):
967 node = cast(expr.Place, UP_node)
968 frame = self.mem.TopNamespace()
969 return value.Place(LeftName(node.var_name, node.blame_tok),
970 frame)
971
972 elif case(expr_e.CommandSub):
973 node = cast(CommandSub, UP_node)
974
975 id_ = node.left_token.id
976 if id_ == Id.Left_CaretParen: # ^(echo block literal)
977 # TODO: Propgate location info?
978 return value.Command(node.child)
979 else:
980 stdout_str = self.shell_ex.RunCommandSub(node)
981 if id_ == Id.Left_AtParen: # @(seq 3)
982 # TODO: Should use J8 lines
983 strs = self.splitter.SplitForWordEval(stdout_str)
984 items = [value.Str(s)
985 for s in strs] # type: List[value_t]
986 return value.List(items)
987 else:
988 return value.Str(stdout_str)
989
990 elif case(expr_e.ShArrayLiteral): # var x = :| foo *.py |
991 node = cast(ShArrayLiteral, UP_node)
992 words = braces.BraceExpandWords(node.words)
993 strs = self.word_ev.EvalWordSequence(words)
994 #log('ARRAY LITERAL EVALUATED TO -> %s', strs)
995 #return value.BashArray(strs)
996
997 # It's equivalent to ['foo', 'bar']
998 items = [value.Str(s) for s in strs]
999 return value.List(items)
1000
1001 elif case(expr_e.DoubleQuoted):
1002 node = cast(DoubleQuoted, UP_node)
1003 # In an ideal world, YSH would *statically* disallow:
1004 #
1005 # - "$@" and "${array[@]}"
1006 # - backticks like `echo hi`
1007 # - $(( 1+2 )) and $[] -- although useful for refactoring
1008 # - not sure: ${x%%} -- could disallow this
1009 # - these enters the ArgDQ state: "${a:-foo bar}" ?
1010 #
1011 # But that would complicate the parser/evaluator. So just rely
1012 # on runtime strict_array to disallow the bad parts.
1013 return value.Str(self.word_ev.EvalDoubleQuotedToString(node))
1014
1015 elif case(expr_e.SingleQuoted):
1016 node = cast(SingleQuoted, UP_node)
1017 return value.Str(node.sval)
1018
1019 elif case(expr_e.BracedVarSub):
1020 node = cast(BracedVarSub, UP_node)
1021 return value.Str(self.word_ev.EvalBracedVarSubToString(node))
1022
1023 elif case(expr_e.SimpleVarSub):
1024 node = cast(SimpleVarSub, UP_node)
1025 return value.Str(self.word_ev.EvalSimpleVarSubToString(node))
1026
1027 elif case(expr_e.Unary):
1028 node = cast(expr.Unary, UP_node)
1029 return self._EvalUnary(node)
1030
1031 elif case(expr_e.Binary):
1032 node = cast(expr.Binary, UP_node)
1033 return self._EvalBinary(node)
1034
1035 elif case(expr_e.Slice): # a[:0]
1036 node = cast(expr.Slice, UP_node)
1037
1038 lower = None # type: Optional[IntBox]
1039 upper = None # type: Optional[IntBox]
1040
1041 if node.lower:
1042 msg = 'Slice begin should be Int'
1043 i = val_ops.ToInt(self._EvalExpr(node.lower), msg,
1044 loc.Missing)
1045 lower = IntBox(i)
1046
1047 if node.upper:
1048 msg = 'Slice end should be Int'
1049 i = val_ops.ToInt(self._EvalExpr(node.upper), msg,
1050 loc.Missing)
1051 upper = IntBox(i)
1052
1053 return value.Slice(lower, upper)
1054
1055 elif case(expr_e.Range):
1056 node = cast(expr.Range, UP_node)
1057
1058 assert node.lower is not None
1059 assert node.upper is not None
1060
1061 msg = 'Range begin should be Int'
1062 i = val_ops.ToInt(self._EvalExpr(node.lower), msg, loc.Missing)
1063
1064 msg = 'Range end should be Int'
1065 j = val_ops.ToInt(self._EvalExpr(node.upper), msg, loc.Missing)
1066
1067 return value.Range(i, j)
1068
1069 elif case(expr_e.Compare):
1070 node = cast(expr.Compare, UP_node)
1071 return self._EvalCompare(node)
1072
1073 elif case(expr_e.IfExp):
1074 node = cast(expr.IfExp, UP_node)
1075 b = val_ops.ToBool(self._EvalExpr(node.test))
1076 if b:
1077 return self._EvalExpr(node.body)
1078 else:
1079 return self._EvalExpr(node.orelse)
1080
1081 elif case(expr_e.List):
1082 node = cast(expr.List, UP_node)
1083 items = [self._EvalExpr(e) for e in node.elts]
1084 return value.List(items)
1085
1086 elif case(expr_e.Tuple):
1087 node = cast(expr.Tuple, UP_node)
1088 # YSH language: Tuple syntax evaluates to LIST !
1089 items = [self._EvalExpr(e) for e in node.elts]
1090 return value.List(items)
1091
1092 elif case(expr_e.Dict):
1093 node = cast(expr.Dict, UP_node)
1094
1095 kvals = [self._EvalExpr(e) for e in node.keys]
1096 values = [] # type: List[value_t]
1097
1098 for i, value_expr in enumerate(node.values):
1099 if value_expr.tag() == expr_e.Implicit: # {key}
1100 # Enforced by parser. Key is expr.Const
1101 assert kvals[i].tag() == value_e.Str, kvals[i]
1102 key = cast(value.Str, kvals[i])
1103 v = self._LookupVar(key.s, loc.Missing)
1104 else:
1105 v = self._EvalExpr(value_expr)
1106
1107 values.append(v)
1108
1109 d = NewDict() # type: Dict[str, value_t]
1110 for i, kval in enumerate(kvals):
1111 k = val_ops.ToStr(kval, 'Dict keys must be strings',
1112 loc.Missing)
1113 d[k] = values[i]
1114
1115 return value.Dict(d)
1116
1117 elif case(expr_e.ListComp):
1118 e_die_status(
1119 2, 'List comprehension reserved but not implemented')
1120
1121 elif case(expr_e.GeneratorExp):
1122 e_die_status(
1123 2, 'Generator expression reserved but not implemented')
1124
1125 elif case(expr_e.Literal): # ^[1 + 2]
1126 node = cast(expr.Literal, UP_node)
1127 return value.Expr(node.inner)
1128
1129 elif case(expr_e.Lambda): # |x| x+1 syntax is reserved
1130 # TODO: Location information for |, or func
1131 # Note: anonymous functions also evaluate to a Lambda, but they shouldn't
1132 e_die_status(2, 'Lambda reserved but not implemented')
1133
1134 elif case(expr_e.FuncCall):
1135 node = cast(expr.FuncCall, UP_node)
1136 return self._EvalFuncCall(node)
1137
1138 elif case(expr_e.Subscript):
1139 node = cast(Subscript, UP_node)
1140 return self._EvalSubscript(node)
1141
1142 elif case(expr_e.Attribute): # obj->method or mydict.key
1143 node = cast(Attribute, UP_node)
1144 return self._EvalAttribute(node)
1145
1146 elif case(expr_e.Eggex):
1147 node = cast(Eggex, UP_node)
1148 return self.EvalEggex(node)
1149
1150 else:
1151 raise NotImplementedError(node.__class__.__name__)
1152
1153 def EvalEggex(self, node):
1154 # type: (Eggex) -> value.Eggex
1155
1156 # Splice, check flags consistency, and accumulate convert_funcs indexed
1157 # by capture group
1158 ev = EggexEvaluator(self.mem, node.canonical_flags)
1159 spliced = ev.EvalE(node.regex)
1160
1161 # as_ere and capture_names filled by ~ operator or Str method
1162 return value.Eggex(spliced, node.canonical_flags, ev.convert_funcs,
1163 ev.convert_toks, None, [])
1164
1165
1166class EggexEvaluator(object):
1167
1168 def __init__(self, mem, canonical_flags):
1169 # type: (state.Mem, str) -> None
1170 self.mem = mem
1171 self.canonical_flags = canonical_flags
1172 self.convert_funcs = [] # type: List[Optional[value_t]]
1173 self.convert_toks = [] # type: List[Optional[Token]]
1174
1175 def _LookupVar(self, name, var_loc):
1176 # type: (str, loc_t) -> value_t
1177 """
1178 Duplicated from ExprEvaluator
1179 """
1180 return LookupVar(self.mem, name, scope_e.LocalOrGlobal, var_loc)
1181
1182 def _EvalClassLiteralTerm(self, term, out):
1183 # type: (class_literal_term_t, List[char_class_term_t]) -> None
1184 UP_term = term
1185
1186 # These 2 vars will be initialized if we don't return early
1187 s = None # type: str
1188 char_code_tok = None # type: Token
1189
1190 with tagswitch(term) as case:
1191
1192 if case(class_literal_term_e.CharCode):
1193 term = cast(CharCode, UP_term)
1194
1195 # What about \0? At runtime, ERE should disallow it. But we
1196 # can also disallow it here.
1197 out.append(term)
1198 return
1199
1200 elif case(class_literal_term_e.CharRange):
1201 term = cast(CharRange, UP_term)
1202 out.append(term)
1203 return
1204
1205 elif case(class_literal_term_e.PosixClass):
1206 term = cast(PosixClass, UP_term)
1207 out.append(term)
1208 return
1209
1210 elif case(class_literal_term_e.PerlClass):
1211 term = cast(PerlClass, UP_term)
1212 out.append(term)
1213 return
1214
1215 elif case(class_literal_term_e.SingleQuoted):
1216 term = cast(SingleQuoted, UP_term)
1217
1218 s = term.sval
1219 char_code_tok = term.left
1220
1221 elif case(class_literal_term_e.Splice):
1222 term = cast(class_literal_term.Splice, UP_term)
1223
1224 val = self._LookupVar(term.var_name, term.name)
1225 s = val_ops.ToStr(val, 'Eggex char class splice expected Str',
1226 term.name)
1227 char_code_tok = term.name
1228
1229 assert s is not None, term
1230 for ch in s:
1231 char_int = ord(ch)
1232 if char_int >= 128:
1233 # / [ '\x7f\xff' ] / is better written as / [ \x7f \xff ] /
1234 e_die(
1235 "Use unquoted char literal for byte %d, which is >= 128"
1236 " (avoid confusing a set of bytes with a sequence)" %
1237 char_int, char_code_tok)
1238 out.append(CharCode(char_code_tok, char_int, False))
1239
1240 def EvalE(self, node):
1241 # type: (re_t) -> re_t
1242 """Resolve references and eval constants in an Eggex
1243
1244 Rules:
1245 Splice => re_t # like Hex and @const in / Hex '.' @const /
1246 Speck/Token (syntax) => Primitive (logical)
1247 Chars and Strings => LiteralChars
1248 """
1249 UP_node = node
1250
1251 with tagswitch(node) as case:
1252 if case(re_e.Seq):
1253 node = cast(re.Seq, UP_node)
1254 new_children = [self.EvalE(child) for child in node.children]
1255 return re.Seq(new_children)
1256
1257 elif case(re_e.Alt):
1258 node = cast(re.Alt, UP_node)
1259 new_children = [self.EvalE(child) for child in node.children]
1260 return re.Alt(new_children)
1261
1262 elif case(re_e.Repeat):
1263 node = cast(re.Repeat, UP_node)
1264 return re.Repeat(self.EvalE(node.child), node.op)
1265
1266 elif case(re_e.Group):
1267 node = cast(re.Group, UP_node)
1268
1269 # placeholder for non-capturing group
1270 self.convert_funcs.append(None)
1271 self.convert_toks.append(None)
1272 return re.Group(self.EvalE(node.child))
1273
1274 elif case(re_e.Capture): # Identical to Group
1275 node = cast(re.Capture, UP_node)
1276 convert_func = None # type: Optional[value_t]
1277 convert_tok = None # type: Optional[Token]
1278 if node.func_name:
1279 func_name = lexer.LazyStr(node.func_name)
1280 func_val = self.mem.GetValue(func_name)
1281 with tagswitch(func_val) as case:
1282 if case(value_e.Func, value_e.BuiltinFunc):
1283 convert_func = func_val
1284 convert_tok = node.func_name
1285 else:
1286 raise error.TypeErr(
1287 func_val,
1288 "Expected %r to be a func" % func_name,
1289 node.func_name)
1290
1291 self.convert_funcs.append(convert_func)
1292 self.convert_toks.append(convert_tok)
1293 return re.Capture(self.EvalE(node.child), node.name,
1294 node.func_name)
1295
1296 elif case(re_e.CharClassLiteral):
1297 node = cast(re.CharClassLiteral, UP_node)
1298
1299 new_terms = [] # type: List[char_class_term_t]
1300 for t in node.terms:
1301 # can get multiple char_class_term.CharCode for a
1302 # class_literal_term_t
1303 self._EvalClassLiteralTerm(t, new_terms)
1304 return re.CharClass(node.negated, new_terms)
1305
1306 elif case(re_e.SingleQuoted):
1307 node = cast(SingleQuoted, UP_node)
1308
1309 s = node.sval
1310 return re.LiteralChars(node.left, s)
1311
1312 elif case(re_e.Splice):
1313 node = cast(re.Splice, UP_node)
1314
1315 val = self._LookupVar(node.var_name, node.name)
1316 UP_val = val
1317 with tagswitch(val) as case:
1318 if case(value_e.Str):
1319 val = cast(value.Str, UP_val)
1320 to_splice = re.LiteralChars(node.name,
1321 val.s) # type: re_t
1322
1323 elif case(value_e.Eggex):
1324 val = cast(value.Eggex, UP_val)
1325
1326 # Splicing means we get the conversion funcs too.
1327 self.convert_funcs.extend(val.convert_funcs)
1328 self.convert_toks.extend(val.convert_toks)
1329
1330 # Splicing requires flags to match. This check is
1331 # transitive.
1332 to_splice = val.spliced
1333
1334 if val.canonical_flags != self.canonical_flags:
1335 e_die(
1336 "Expected eggex flags %r, but got %r" %
1337 (self.canonical_flags, val.canonical_flags),
1338 node.name)
1339
1340 else:
1341 raise error.TypeErr(
1342 val, 'Eggex splice expected Str or Eggex',
1343 node.name)
1344 return to_splice
1345
1346 else:
1347 # These are evaluated at translation time
1348
1349 # case(re_e.Primitive)
1350 # case(re_e.PosixClass)
1351 # case(re_e.PerlClass)
1352 return node
1353
1354
1355# vim: sw=4