def testPushTemp(self): mem = _InitMem() # x=1 mem.SetVar( lvalue.Named('x'), value.Str('1'), scope_e.Dynamic) self.assertEqual('1', mem.var_stack[-1]['x'].val.s) mem.PushTemp() self.assertEqual(2, len(mem.var_stack)) # x=temp E=3 read x <<< 'line' mem.SetVar( lvalue.Named('x'), value.Str('temp'), scope_e.LocalOnly) mem.SetVar( lvalue.Named('E'), value.Str('3'), scope_e.LocalOnly) mem.SetVar( lvalue.Named('x'), value.Str('line'), scope_e.LocalOnly) self.assertEqual('3', mem.var_stack[-1]['E'].val.s) self.assertEqual('line', mem.var_stack[-1]['x'].val.s) self.assertEqual('1', mem.var_stack[-2]['x'].val.s) mem.PopTemp() self.assertEqual(1, len(mem.var_stack)) self.assertEqual('1', mem.var_stack[-1]['x'].val.s)
def testExportThenAssign(self): """Regression Test""" mem = _InitMem() # export U mem.SetVar(lvalue.Named('U'), None, (var_flags_e.Exported, ), scope_e.Dynamic) print(mem) # U=u mem.SetVar(lvalue.Named('U'), value.Str('u'), (), scope_e.Dynamic) print(mem) e = mem.GetExported() self.assertEqual('u', e['U'])
def Run(self, cmd_val): # type: (cmd_value__Assign) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() attrs = flag_spec.Parse('readonly', arg_r) arg = arg_types.readonly(attrs.attrs) if arg.p or len(cmd_val.pairs) == 0: return _PrintVariables(self.mem, cmd_val, attrs, True, builtin=_READONLY) for pair in cmd_val.pairs: if pair.rval is None: if arg.a: rval = value.MaybeStrArray([]) # type: value_t elif arg.A: rval = value.AssocArray({}) else: rval = None else: rval = pair.rval rval = _ReconcileTypes(rval, arg.a, arg.A, pair.spid) # NOTE: # - when rval is None, only flags are changed # - dynamic scope because flags on locals can be changed, etc. self.mem.SetVar(lvalue.Named(pair.var_name), rval, scope_e.Dynamic, flags=state.SetReadOnly) return 0
def Run(self, cmd_val): # type: (cmd_value__Assign) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() attrs = flag_spec.Parse('export_', arg_r) arg = arg_types.export_(attrs.attrs) #arg = attrs if arg.f: e_usage( "doesn't accept -f because it's dangerous. " "(The code can usually be restructured with 'source')") if arg.p or len(cmd_val.pairs) == 0: return _PrintVariables(self.mem, cmd_val, attrs, True, builtin=_EXPORT) if arg.n: for pair in cmd_val.pairs: if pair.rval is not None: e_usage("doesn't accept RHS with -n", span_id=pair.spid) # NOTE: we don't care if it wasn't found, like bash. self.mem.ClearFlag(pair.var_name, state.ClearExport, scope_e.Dynamic) else: for pair in cmd_val.pairs: # NOTE: when rval is None, only flags are changed self.mem.SetVar(lvalue.Named(pair.var_name), pair.rval, scope_e.Dynamic, flags=state.SetExport) return 0
def _EvalLhsArith(node, mem, arith_ev): """sh_lhs_expr -> lvalue. Very similar to EvalLhs above in core/cmd_exec. """ assert isinstance(node, sh_lhs_expr_t), node if node.tag == sh_lhs_expr_e.Name: # (( i = 42 )) lval = lvalue.Named(node.name) # TODO: location info. Use the = token? #lval.spids.append(spid) return lval elif node.tag == sh_lhs_expr_e.IndexedName: # (( a[42] = 42 )) # The index of MaybeStrArray needs to be coerced to int, but not the index of # an AssocArray. if mem.IsAssocArray(node.name, scope_e.Dynamic): key = arith_ev.EvalWordToString(node.index) lval = lvalue.Keyed(node.name, key) else: index = arith_ev.EvalToIndex(node.index) lval = lvalue.Indexed(node.name, index) # TODO: location info. Use the = token? #lval.spids.append(node.spids[0]) else: raise AssertionError(node.tag) return lval
def _EvalLhsArith(node, mem, arith_ev): # type: (sh_lhs_expr_t, Mem, ArithEvaluator) -> lvalue_t """sh_lhs_expr -> lvalue. Very similar to EvalLhs above in core/cmd_exec. """ assert isinstance(node, sh_lhs_expr_t), node UP_node = node with tagswitch(node) as case: if case(sh_lhs_expr_e.Name): # (( i = 42 )) node = cast(sh_lhs_expr__Name, UP_node) lval = lvalue.Named(node.name) # type: lvalue_t # TODO: location info. Use the = token? #lval.spids.append(spid) elif case(sh_lhs_expr_e.IndexedName): # (( a[42] = 42 )) node = cast(sh_lhs_expr__IndexedName, UP_node) # The index of MaybeStrArray needs to be coerced to int, but not the # index of an AssocArray. if mem.IsAssocArray(node.name, scope_e.Dynamic): key = arith_ev.EvalWordToString(node.index) lval = lvalue.Keyed(node.name, key) else: index = arith_ev.EvalToInt(node.index) lval = lvalue.Indexed(node.name, index) # TODO: location info. Use the = token? #lval.spids.append(node.spids[0]) else: raise AssertionError(node.tag_()) return lval
def EvalArithLhs(self, anode, span_id): # type: (arith_expr_t, int) -> lvalue_t """ For (( a[x] = 1 )) etc. """ UP_anode = anode if anode.tag_() == arith_expr_e.Binary: anode = cast(arith_expr__Binary, UP_anode) if anode.op_id == Id.Arith_LBracket: var_name, span_id = self._VarRefOrWord(anode.left) if var_name is not None: if self.mem.IsAssocArray(var_name, scope_e.Dynamic): key = self.EvalWordToString(anode.right) lval2 = lvalue.Keyed(var_name, key) lval2.spids.append(span_id) lval = lval2 # type: lvalue_t return lval else: index = self.EvalToInt(anode.right) lval3 = lvalue.Indexed(var_name, index) lval3.spids.append(span_id) lval = lval3 return lval var_name, span_id = self._VarRefOrWord(anode) if var_name is not None: lval1 = lvalue.Named(var_name) lval1.spids.append(span_id) lval = lval1 return lval # e.g. unset 'x-y'. status 2 for runtime parse error e_die('Invalid place to modify', span_id=span_id, status=2)
def EvalLhs(node, arith_ev, mem, spid, lookup_mode): """sh_lhs_expr -> lvalue. Used for a=b and a[x]=b """ assert isinstance(node, sh_lhs_expr_t), node if node.tag == sh_lhs_expr_e.Name: # a=x lval = lvalue.Named(node.name) lval.spids.append(spid) elif node.tag == sh_lhs_expr_e.IndexedName: # a[1+2]=x if mem.IsAssocArray(node.name, lookup_mode): key = arith_ev.EvalWordToString(node.index) lval = lvalue.Keyed(node.name, key) lval.spids.append(node.spids[0]) # copy left-most token over else: index = arith_ev.EvalToIndex(node.index) lval = lvalue.Indexed(node.name, index) lval.spids.append(node.spids[0]) # copy left-most token over else: raise AssertionError(node.tag) return lval
def _EvalTempEnv(self, more_env, flags): """For FOO=1 cmd.""" for env_pair in more_env: val = self.word_ev.EvalWordToString(env_pair.val) # Set each var so the next one can reference it. Example: # FOO=1 BAR=$FOO ls / self.mem.SetVar(lvalue.Named(env_pair.name), val, flags, scope_e.LocalOnly)
def testUnset(self): mem = _InitMem() # unset a mem.Unset(lvalue.Named('a'), scope_e.Dynamic) return # not implemented yet # unset a[1] mem.Unset(lvalue.Indexed('a', 1), scope_e.Dynamic)
def _All(self, var_name): # type: (str) -> int contents = _ReadAll() # No error conditions? lhs = lvalue.Named(var_name) self.mem.SetValue(lhs, value.Str(contents), scope_e.LocalOnly) return 0
def _UnsetVar(self, name, spid): if not match.IsValidVarName(name): raise args.UsageError( 'got invalid variable name %r' % name, span_id=spid) ok, found = self.mem.Unset(lvalue.Named(name), scope_e.Dynamic) if not ok: self.errfmt.Print("Can't unset readonly variable %r", name, span_id=spid) return ok, found
def testGetVar(self): mem = _InitMem() # readonly a=x mem.SetVar(lvalue.Named('a'), value.Str('x'), (var_flags_e.ReadOnly, ), scope_e.Dynamic) val = mem.GetVar('a', scope_e.Dynamic) test_lib.AssertAsdlEqual(self, value.Str('x'), val) val = mem.GetVar('undef', scope_e.Dynamic) test_lib.AssertAsdlEqual(self, value.Undef(), val)
def EvalLHS(self, node): if 0: print('EvalLHS()') node.PrettyPrint() print('') if node.tag == expr_e.Var: return lvalue.Named(node.name.val) else: # TODO: # subscripts, tuple unpacking, starred expressions, etc. raise NotImplementedError(node.__class__.__name__)
def __init__( self, parse_ctx, # type: ParseContext exec_opts, # type: optview.Exec mutable_opts, # type: MutableOpts mem, # type: Mem f, # type: _DebugFile ): # type: (...) -> None """ Args: parse_ctx: For parsing PS4. exec_opts: For xtrace setting mem: for retrieving PS4 word_ev: for evaluating PS4 """ self.parse_ctx = parse_ctx self.exec_opts = exec_opts self.mutable_opts = mutable_opts self.mem = mem self.f = f # can be stderr, the --debug-file, etc. self.word_ev = None # type: NormalWordEvaluator self.ind = 0 # changed by process, proc, source, eval self.indents = [''] # "pooled" to avoid allocations # PS4 value -> compound_word. PS4 is scoped. self.parse_cache = {} # type: Dict[str, compound_word] # Mutate objects to save allocations self.val_indent = value.Str('') self.val_punct = value.Str('') self.val_pid_str = value.Str('') # mutated by SetProcess # Can these be global constants? I don't think we have that in ASDL yet. self.lval_indent = lvalue.Named('SHX_indent') self.lval_punct = lvalue.Named('SHX_punct') self.lval_pid_str = lvalue.Named('SHX_pid_str')
def testGetValue(self): mem = _InitMem() # readonly a=x mem.SetValue(lvalue.Named('a'), value.Str('x'), scope_e.Dynamic, flags=state.SetReadOnly) val = mem.GetValue('a', scope_e.Dynamic) test_lib.AssertAsdlEqual(self, value.Str('x'), val) val = mem.GetValue('undef', scope_e.Dynamic) test_lib.AssertAsdlEqual(self, value.Undef(), val)
def _Line(self, arg, var_name): # type: (arg_types.read, str) -> int line = _ReadLine() if len(line) == 0: # EOF return 1 if not arg.with_eol: if line.endswith('\r\n'): line = line[:-2] elif line.endswith('\n'): line = line[:-1] lhs = lvalue.Named(var_name) self.mem.SetVar(lhs, value.Str(line), scope_e.LocalOnly) return 0
def _gen(): while True: try: loop_val = it.next() # e.g. x except StopIteration: break self.mem.SetVar(lvalue.Named(iter_name), value.Obj(loop_val), scope_e.LocalOnly) if comp.cond: b = self.EvalExpr(comp.cond) else: b = True if b: item = self.EvalExpr(node.elt) # e.g. x*2 yield item
def EvalPlaceExpr(self, place): # type: (place_expr_t) -> Union[lvalue__Named, lvalue__ObjIndex, lvalue__ObjAttr] if place.tag == place_expr_e.Var: return lvalue.Named(place.name.val) if place.tag == place_expr_e.Subscript: obj = self.EvalExpr(place.obj) index = self._EvalIndices(place.indices) return lvalue.ObjIndex(obj, index) if place.tag == place_expr_e.Attribute: obj = self.EvalExpr(place.obj) if place.op.id == Id.Expr_RArrow: index = place.attr.val return lvalue.ObjIndex(obj, index) else: return lvalue.ObjAttr(obj, place.attr.val) raise NotImplementedError(place)
def testSearchPath(self): mem = _InitMem() #print(mem) search_path = state.SearchPath(mem) # Relative path works without $PATH self.assertEqual(None, search_path.Lookup('__nonexistent__')) self.assertEqual('bin/osh', search_path.Lookup('bin/osh')) self.assertEqual(None, search_path.Lookup('grep')) # Set $PATH mem.SetVar(lvalue.Named('PATH'), value.Str('/bin:/usr/bin'), (), scope_e.GlobalOnly) self.assertEqual(None, search_path.Lookup('__nonexistent__')) self.assertEqual('bin/osh', search_path.Lookup('bin/osh')) # Not hermetic, but should be true on POSIX systems. self.assertEqual('/usr/bin/env', search_path.Lookup('env'))
def _Line(self, arg, var_name): # type: (arg_types.read, str) -> int line = _ReadLine() if len(line) == 0: # EOF return 1 if not arg.with_eol: if line.endswith('\r\n'): line = line[:-2] elif line.endswith('\n'): line = line[:-1] # Lines that don't start with a single quote aren't QSN. They may contain # a single quote internally, like: # # Fool's Gold if arg.q and line.startswith("'"): arena = self.parse_ctx.arena line_reader = reader.StringLineReader(line, arena) lexer = self.parse_ctx._MakeLexer(line_reader) # The parser only yields valid tokens: # Char_Literals, Char_OneChar, Char_Hex, Char_UBraced # So we can use word_compile.EvalCStringToken, which is also used for # $''. # Important: we don't generate Id.Unknown_Backslash because that is valid # in echo -e. We just make it Id.Unknown_Tok? try: # TODO: read should know about stdin, and redirects, and pipelines? with alloc.ctx_Location(arena, source.Stdin('')): tokens = qsn_native.Parse(lexer) except error.Parse as e: ui.PrettyPrintError(e, arena) return 1 tmp = [word_compile.EvalCStringToken(t) for t in tokens] line = ''.join(tmp) lhs = lvalue.Named(var_name) self.mem.SetValue(lhs, value.Str(line), scope_e.LocalOnly) return 0
def EvalLhs(node, arith_ev, mem, spid, lookup_mode): # type: (sh_lhs_expr_t, ArithEvaluator, Mem, int, scope_t) -> lvalue_t """sh_lhs_expr -> lvalue. Used for a=b and a[x]=b """ assert isinstance(node, sh_lhs_expr_t), node UP_node = node lval = None # type: lvalue_t with tagswitch(node) as case: if case(sh_lhs_expr_e.Name): # a=x node = cast(sh_lhs_expr__Name, UP_node) # Note: C++ constructor doesn't take spids directly. Should we add that? lval1 = lvalue.Named(node.name) lval1.spids.append(spid) lval = lval1 elif case(sh_lhs_expr_e.IndexedName): # a[1+2]=x node = cast(sh_lhs_expr__IndexedName, UP_node) if mem.IsAssocArray(node.name, lookup_mode): key = arith_ev.EvalWordToString(node.index) # copy left-mode spid lval2 = lvalue.Keyed(node.name, key) lval2.spids.append(node.spids[0]) lval = lval2 else: index = arith_ev.EvalToInt(node.index) # copy left-mode spid lval3 = lvalue.Indexed(node.name, index) lval3.spids.append(node.spids[0]) lval = lval3 else: raise AssertionError(node.tag_()) return lval
def EvalShellLhs(self, node, spid, lookup_mode): # type: (sh_lhs_expr_t, int, scope_t) -> lvalue_t """Evaluate a shell LHS expression, i.e. place expression. For a=b and a[x]=b etc. """ assert isinstance(node, sh_lhs_expr_t), node UP_node = node lval = None # type: lvalue_t with tagswitch(node) as case: if case(sh_lhs_expr_e.Name): # a=x node = cast(sh_lhs_expr__Name, UP_node) # Note: C++ constructor doesn't take spids directly. Should we add that? lval1 = lvalue.Named(node.name) lval1.spids.append(spid) lval = lval1 elif case(sh_lhs_expr_e.IndexedName): # a[1+2]=x node = cast(sh_lhs_expr__IndexedName, UP_node) if self.mem.IsAssocArray(node.name, lookup_mode): key = self.EvalWordToString(node.index) lval2 = lvalue.Keyed(node.name, key) lval2.spids.append(node.spids[0]) lval = lval2 else: index = self.EvalToInt(node.index) lval3 = lvalue.Indexed(node.name, index) lval3.spids.append(node.spids[0]) lval = lval3 else: raise AssertionError(node.tag_()) return lval
def testSearchPath(self): mem = _InitMem() #print(mem) search_path = state.SearchPath(mem) # Relative path works without $PATH self.assertEqual(None, search_path.Lookup('__nonexistent__')) self.assertEqual('bin/osh', search_path.Lookup('bin/osh')) self.assertEqual(None, search_path.Lookup('grep')) # Set $PATH mem.SetVar(lvalue.Named('PATH'), value.Str('/bin:/usr/bin'), (), scope_e.GlobalOnly) self.assertEqual(None, search_path.Lookup('__nonexistent__')) self.assertEqual('bin/osh', search_path.Lookup('bin/osh')) # Not hermetic, but should be true on POSIX systems. # Also see https://www.freedesktop.org/wiki/Software/systemd/TheCaseForTheUsrMerge/ # - on some systems, /bin is a symlink to /usr/bin if os.path.isfile('/bin/env'): self.assertEqual(search_path.Lookup('env'), '/bin/env') else: self.assertEqual(search_path.Lookup('env'), '/usr/bin/env')
def _WriteFdToMem(self, fd_name, fd): # type: (str, int) -> None if self.mem: # setvar, not setref state.SetLocalShopt(self.mem, lvalue.Named(fd_name), value.Str(str(fd)))
def EvalExpr(self, node): # type: (expr_t) -> Any """ This is a naive PyObject evaluator! It uses the type dispatch of the host Python interpreter. Returns: A Python object of ANY type. Should be wrapped in value.Obj() for storing in Mem. """ if 0: print('EvalExpr()') node.PrettyPrint() print('') if node.tag == expr_e.Const: id_ = node.c.id if id_ == Id.Expr_DecInt: return int(node.c.val) elif id_ == Id.Expr_BinInt: return int(node.c.val, 2) elif id_ == Id.Expr_OctInt: return int(node.c.val, 8) elif id_ == Id.Expr_HexInt: return int(node.c.val, 16) elif id_ == Id.Expr_Float: return float(node.c.val) elif id_ == Id.Expr_Null: return None elif id_ == Id.Expr_True: return True elif id_ == Id.Expr_False: return False elif id_ == Id.Expr_Name: # for {name: 'bob'} # Maybe also :Symbol? return node.c.val # NOTE: We could allow Ellipsis for a[:, ...] here, but we're not using # it yet. raise AssertionError(id_) if node.tag == expr_e.Var: return self.LookupVar(node.name.val) if node.tag == expr_e.CommandSub: return self.ex.RunCommandSub(node.command_list) if node.tag == expr_e.ShArrayLiteral: words = braces.BraceExpandWords(node.words) strs = self.word_ev.EvalWordSequence(words) #log('ARRAY LITERAL EVALUATED TO -> %s', strs) return objects.StrArray(strs) if node.tag == expr_e.DoubleQuoted: # In an ideal world, I would *statically* disallow: # - "$@" and "${array[@]}" # - backticks like `echo hi` # - $(( 1+2 )) and $[] -- although useful for refactoring # - not sure: ${x%%} -- could disallow this # - these enters the ArgDQ state: "${a:-foo bar}" ? # But that would complicate the parser/evaluator. So just rely on # strict_array to disallow the bad parts. return self.word_ev.EvalDoubleQuotedToString(node) if node.tag == expr_e.SingleQuoted: return word_eval.EvalSingleQuoted(node) if node.tag == expr_e.BracedVarSub: return self.word_ev.EvalBracedVarSubToString(node) if node.tag == expr_e.SimpleVarSub: return self.word_ev.EvalSimpleVarSubToString(node.token) if node.tag == expr_e.Unary: child = self.EvalExpr(node.child) if node.op.id == Id.Arith_Minus: return -child if node.op.id == Id.Arith_Tilde: return ~child if node.op.id == Id.Expr_Not: return not child raise NotImplementedError(node.op.id) if node.tag == expr_e.Binary: left = self.EvalExpr(node.left) right = self.EvalExpr(node.right) if node.op.id == Id.Arith_Plus: return left + right if node.op.id == Id.Arith_Minus: return left - right if node.op.id == Id.Arith_Star: return left * right if node.op.id == Id.Arith_Slash: # NOTE: from __future__ import division changes 5/2! # But just make it explicit. return float(left) / right # floating point division if node.op.id == Id.Expr_Div: return left // right # integer divison if node.op.id == Id.Expr_Mod: return left % right if node.op.id == Id.Arith_Caret: # Exponentiation return left**right # Bitwise if node.op.id == Id.Arith_Amp: return left & right if node.op.id == Id.Arith_Pipe: return left | right if node.op.id == Id.Expr_Xor: return left ^ right if node.op.id == Id.Arith_DGreat: return left >> right if node.op.id == Id.Arith_DLess: return left << right # Logical if node.op.id == Id.Expr_And: return left and right if node.op.id == Id.Expr_Or: return left or right raise NotImplementedError(node.op.id) if node.tag == expr_e.Range: # 1:10 or 1:10:2 lower = self.EvalExpr(node.lower) upper = self.EvalExpr(node.upper) return xrange(lower, upper) if node.tag == expr_e.Slice: # a[:0] lower = self.EvalExpr(node.lower) if node.lower else None upper = self.EvalExpr(node.upper) if node.upper else None return slice(lower, upper) if node.tag == expr_e.Compare: left = self.EvalExpr(node.left) result = True # Implicit and for op, right_expr in zip(node.ops, node.comparators): right = self.EvalExpr(right_expr) if op.id == Id.Arith_Less: result = left < right elif op.id == Id.Arith_Great: result = left > right elif op.id == Id.Arith_GreatEqual: result = left >= right elif op.id == Id.Arith_LessEqual: result = left <= right elif op.id == Id.Arith_DEqual: result = left == right elif op.id == Id.Expr_In: result = left in right elif op.id == Id.Node_NotIn: result = left not in right elif op.id == Id.Expr_Is: result = left is right elif op.id == Id.Node_IsNot: result = left is not right else: try: if op.id == Id.Arith_Tilde: result = self._EvalMatch(left, right, True) elif op.id == Id.Expr_NotTilde: result = not self._EvalMatch(left, right, False) else: raise AssertionError(op.id) except RuntimeError as e: # Status 2 indicates a regex parse error. This is fatal in OSH but # not in bash, which treats [[ like a command with an exit code. e_die("Invalid regex %r", right, span_id=op.span_id, status=2) if not result: return result left = right return result if node.tag == expr_e.IfExp: b = self.EvalExpr(node.test) if b: return self.EvalExpr(node.body) else: return self.EvalExpr(node.orelse) if node.tag == expr_e.List: return [self.EvalExpr(e) for e in node.elts] if node.tag == expr_e.Tuple: return tuple(self.EvalExpr(e) for e in node.elts) if node.tag == expr_e.Dict: # NOTE: some keys are expr.Const keys = [self.EvalExpr(e) for e in node.keys] values = [] for i, e in enumerate(node.values): if e.tag == expr_e.Implicit: v = self.LookupVar(keys[i]) # {name} else: v = self.EvalExpr(e) values.append(v) return dict(zip(keys, values)) if node.tag == expr_e.ListComp: # TODO: # - Consolidate with command_e.OilForIn in osh/cmd_exec.py? # - Do I have to push a temp frame here? # Hm... lexical or dynamic scope is an issue. result = [] comp = node.generators[0] obj = self.EvalExpr(comp.iter) # TODO: Handle x,y etc. iter_name = comp.lhs[0].name.val if isinstance(obj, str): e_die("Strings aren't iterable") else: it = obj.__iter__() while True: try: loop_val = it.next() # e.g. x except StopIteration: break self.mem.SetVar(lvalue.Named(iter_name), value.Obj(loop_val), scope_e.LocalOnly) if comp.cond: b = self.EvalExpr(comp.cond) else: b = True if b: item = self.EvalExpr(node.elt) # e.g. x*2 result.append(item) return result if node.tag == expr_e.GeneratorExp: comp = node.generators[0] obj = self.EvalExpr(comp.iter) # TODO: Support (x for x, y in ...) iter_name = comp.lhs[0].name.val it = obj.__iter__() # TODO: There is probably a much better way to do this! # The scope of the loop variable is wrong, etc. def _gen(): while True: try: loop_val = it.next() # e.g. x except StopIteration: break self.mem.SetVar(lvalue.Named(iter_name), value.Obj(loop_val), scope_e.LocalOnly) if comp.cond: b = self.EvalExpr(comp.cond) else: b = True if b: item = self.EvalExpr(node.elt) # e.g. x*2 yield item return _gen() if node.tag == expr_e.Lambda: return objects.Lambda(node, self.ex) if node.tag == expr_e.FuncCall: func = self.EvalExpr(node.func) pos_args, named_args = self.EvalArgList(node.args) ret = func(*pos_args, **named_args) return ret if node.tag == expr_e.Subscript: obj = self.EvalExpr(node.obj) index = self._EvalIndices(node.indices) return obj[index] # TODO: obj.method() should be separate if node.tag == expr_e.Attribute: # obj.attr o = self.EvalExpr(node.obj) id_ = node.op.id if id_ == Id.Expr_Dot: name = node.attr.val # TODO: Does this do the bound method thing we do NOT want? return getattr(o, name) if id_ == Id.Expr_RArrow: # d->key is like d['key'] name = node.attr.val return o[name] if id_ == Id.Expr_DColon: # StaticName::member raise NotImplementedError(id_) # TODO: We should prevent virtual lookup here? This is a pure static # namespace lookup? # But Python doesn't any hook for this. # Maybe we can just check that it's a module? And modules don't lookup # in a supertype or __class__, etc. raise AssertionError(id_) if node.tag == expr_e.RegexLiteral: # obj.attr # TODO: Should this just be an object that ~ calls? return objects.Regex(self.EvalRegex(node.regex)) if node.tag == expr_e.ArrayLiteral: # obj.attr items = [self.EvalExpr(item) for item in node.items] if items: # Determine type at runtime? If we have something like @[(i) (j)] # then we don't know its type until runtime. first = items[0] if isinstance(first, bool): return objects.BoolArray(bool(x) for x in items) elif isinstance(first, int): return objects.IntArray(int(x) for x in items) elif isinstance(first, float): return objects.FloatArray(float(x) for x in items) elif isinstance(first, str): return objects.StrArray(str(x) for x in items) else: raise AssertionError(first) else: # TODO: Should this have an unknown type? # What happens when you mutate or extend it? You have to make sure # that the type tags match? return objects.BoolArray(items) raise NotImplementedError(node.__class__.__name__)
def _WriteFdToMem(self, fd_name, fd): # type: (str, int) -> None if self.mem: self.mem.SetVar(lvalue.Named(fd_name), value.Str(str(fd)), scope_e.Dynamic)
def Run(self, cmd_val): # type: (cmd_value__Assign) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() attrs = flag_spec.Parse('new_var', arg_r) arg = arg_types.new_var(attrs.attrs) status = 0 if arg.f: names = arg_r.Rest() if len(names): # NOTE: in bash, -f shows the function body, while -F shows the name. # Right now we just show the name. status = self._PrintFuncs(names) else: e_usage('passed -f without args') return status if arg.F: names = arg_r.Rest() if len(names): status = self._PrintFuncs(names) else: # weird bash quirk: they're printed in a different format! for func_name in sorted(self.funcs): print('declare -f %s' % (func_name)) return status if arg.p: # Lookup and print variables. return _PrintVariables(self.mem, cmd_val, attrs, True) elif len(cmd_val.pairs) == 0: return _PrintVariables(self.mem, cmd_val, attrs, False) # # Set variables # #raise error.Usage("doesn't understand %s" % cmd_val.argv[1:]) if cmd_val.builtin_id == builtin_i.local: lookup_mode = scope_e.LocalOnly else: # declare/typeset if arg.g: lookup_mode = scope_e.GlobalOnly else: lookup_mode = scope_e.LocalOnly flags = 0 if arg.x == '-': flags |= state.SetExport if arg.r == '-': flags |= state.SetReadOnly if arg.n == '-': flags |= state.SetNameref flags_to_clear = 0 if arg.x == '+': flags |= state.ClearExport if arg.r == '+': flags |= state.ClearReadOnly if arg.n == '+': flags |= state.ClearNameref for pair in cmd_val.pairs: rval = pair.rval # declare -a foo=(a b); declare -a foo; should not reset to empty array if rval is None and (arg.a or arg.A): old_val = self.mem.GetVar(pair.var_name) if arg.a: if old_val.tag_() != value_e.MaybeStrArray: rval = value.MaybeStrArray([]) elif arg.A: if old_val.tag_() != value_e.AssocArray: rval = value.AssocArray({}) rval = _ReconcileTypes(rval, arg.a, arg.A, pair.spid) self.mem.SetVar(lvalue.Named(pair.var_name), rval, lookup_mode, flags=flags) return status
def SetLocalString(mem, name, s): # type: (state.Mem, str, str) -> None """Bind a local string.""" assert isinstance(s, str) mem.SetValue(lvalue.Named(name), value.Str(s), scope_e.LocalOnly)
def EvalLhsAndLookup(node, arith_ev, mem, exec_opts, lookup_mode=scope_e.Dynamic): # type: (sh_lhs_expr_t, ArithEvaluator, Mem, optview.Exec, scope_t) -> Tuple[value_t, lvalue_t] """Evaluate the operand for i++, a[0]++, i+=2, a[0]+=2 as an R-value. Also used by the Executor for s+='x' and a[42]+='x'. Args: node: syntax_asdl.sh_lhs_expr Returns: value_t, lvalue_t """ #log('sh_lhs_expr NODE %s', node) assert isinstance(node, sh_lhs_expr_t), node UP_node = node with tagswitch(node) as case: if case(sh_lhs_expr_e.Name): # a = b node = cast(sh_lhs_expr__Name, UP_node) # Problem: It can't be an array? # a=(1 2) # (( a++ )) lval = lvalue.Named(node.name) # type: lvalue_t val = _LookupVar(node.name, mem, exec_opts) elif case(sh_lhs_expr_e.IndexedName): # a[1] = b node = cast(sh_lhs_expr__IndexedName, UP_node) # See tdop.IsIndexable for valid values: # - VarRef (not Name): a[1] # - FuncCall: f(x), 1 # - Binary LBracket: f[1][1] -- no semantics for this? val = mem.GetVar(node.name) UP_val = val with tagswitch(val) as case2: if case2(value_e.Str): e_die("Can't assign to characters of string %r", node.name) elif case2(value_e.Undef): # compatible behavior: Treat it like an array. # TODO: Does this code ever get triggered? It seems like the error is # caught earlier. index = arith_ev.EvalToInt(node.index) lval = lvalue.Indexed(node.name, index) if exec_opts.nounset(): e_die("Undefined variable can't be indexed") else: val = value.Str('') elif case2(value_e.MaybeStrArray): array_val = cast(value__MaybeStrArray, UP_val) #log('ARRAY %s -> %s, index %d', node.name, array, index) index = arith_ev.EvalToInt(node.index) lval = lvalue.Indexed(node.name, index) # NOTE: Similar logic in RHS Arith_LBracket try: s = array_val.strs[index] except IndexError: s = None if s is None: val = value.Str( '') # NOTE: Other logic is value.Undef()? 0? else: assert isinstance(s, str), s val = value.Str(s) elif case2(value_e.AssocArray): # declare -A a; a['x']+=1 assoc_val = cast(value__AssocArray, UP_val) key = arith_ev.EvalWordToString(node.index) lval = lvalue.Keyed(node.name, key) s = assoc_val.d.get(key) if s is None: val = value.Str('') else: val = value.Str(s) else: raise AssertionError(val.tag_()) else: raise AssertionError(node.tag_()) return val, lval