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

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