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 __call__(self, cmd_val): arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() arg, arg_index = READONLY_SPEC.Parse(arg_r) for pair in cmd_val.pairs: if pair.rval is None: if arg.a: rval = value.MaybeStrArray([]) elif arg.A: rval = value.AssocArray({}) else: rval = None else: rval = pair.rval if not _CheckType(rval, arg, self.errfmt, pair.spid): return 1 # NOTE: # - when rval is None, only flags are changed # - dynamic scope because flags on locals can be changed, etc. self.mem.SetVar(pair.lval, rval, (var_flags_e.ReadOnly,), scope_e.Dynamic) return 0
def SetArrayDynamic(mem, name, a): """Set an array by looking up the stack. Used for _init_completion. """ assert isinstance(a, list) mem.SetVar(sh_lhs_expr.Name(name), value.MaybeStrArray(a), (), scope_e.Dynamic)
def _BindNewArrayWithEntry(self, namespace, lval, val, flags_to_set): """Fill 'namespace' with a new indexed array entry.""" items = [None] * lval.index items.append(val.s) new_value = value.MaybeStrArray(items) # arrays can't be exported; can't have AssocArray flag readonly = var_flags_e.ReadOnly in flags_to_set namespace[lval.name] = runtime_asdl.cell(new_value, False, readonly)
def __call__(self, cmd_val): arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() arg, arg_index = NEW_VAR_SPEC.Parse(arg_r) status = 0 # NOTE: in bash, -f shows the function body, while -F shows the name. In # osh, they're identical and behave like -F. if arg.f or arg.F: # Lookup and print functions. names = [pair.lval.name for pair in cmd_val.pairs] if names: for name in names: if name in self.funcs: print(name) # TODO: Could print LST, or render LST. Bash does this. 'trap' too. #print(funcs[name]) else: status = 1 elif arg.F: for func_name in sorted(self.funcs): print('declare -f %s' % (func_name)) else: raise args.UsageError('declare/typeset -f without args') return status if arg.p: # Lookup and print variables. names = [pair.lval.name for pair in cmd_val.pairs] if names: for name in names: val = self.mem.GetVar(name) if val.tag != value_e.Undef: # TODO: Print flags. print(name) else: status = 1 else: raise args.UsageError('declare/typeset -p without args') return status # # Set variables # #raise args.UsageError("doesn't understand %s" % cmd_val.argv[1:]) if cmd_val.builtin_id == builtin_e.LOCAL: lookup_mode = scope_e.LocalOnly else: # declare/typeset if arg.g: lookup_mode = scope_e.GlobalOnly else: lookup_mode = scope_e.LocalOnly flags_to_set = [] if arg.x == '-': flags_to_set.append(var_flags_e.Exported) if arg.r == '-': flags_to_set.append(var_flags_e.ReadOnly) flags_to_clear = [] if arg.x == '+': flags_to_clear.append(var_flags_e.Exported) if arg.r == '+': flags_to_clear.append(var_flags_e.ReadOnly) for pair in cmd_val.pairs: if pair.rval is None: if arg.a: rval = value.MaybeStrArray([]) elif arg.A: rval = value.AssocArray({}) else: rval = None else: rval = pair.rval if not _CheckType(rval, arg, self.errfmt, pair.spid): return 1 self.mem.SetVar(pair.lval, rval, flags_to_set, lookup_mode, flags_to_clear=flags_to_clear) return status
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 SetGlobalArray(mem, name, a): """Helper for completion.""" assert isinstance(a, list) mem.SetVar(lhs_expr.LhsName(name), value.MaybeStrArray(a), (), scope_e.GlobalOnly)
def GetVar(self, name, lookup_mode=scope_e.Dynamic): assert isinstance(name, str), name # TODO: Short-circuit down to _FindCellAndNamespace by doing a single hash # lookup: # COMPUTED_VARS = {'PIPESTATUS': 1, 'FUNCNAME': 1, ...} # if name not in COMPUTED_VARS: ... if name == 'ARGV': # TODO: # - Reuse the MaybeStrArray? # - @@ could be an alias for ARGV (in command mode, but not expr mode) return value.MaybeStrArray(self.GetArgv()) if name == 'PIPESTATUS': return value.MaybeStrArray([str(i) for i in self.pipe_status[-1]]) # Do lookup of system globals before looking at user variables. Note: we # could optimize this at compile-time like $?. That would break # ${!varref}, but it's already broken for $?. if name == 'FUNCNAME': # bash wants it in reverse order. This is a little inefficient but we're # not depending on deque(). strs = [] for func_name, source_name, _, _, _ in reversed(self.debug_stack): if func_name: strs.append(func_name) if source_name: strs.append('source') # bash doesn't give name # Temp stacks are ignored if self.has_main: strs.append('main') # bash does this return value.MaybeStrArray(strs) # TODO: Reuse this object too? # This isn't the call source, it's the source of the function DEFINITION # (or the sourced # file itself). if name == 'BASH_SOURCE': return value.MaybeStrArray(list(reversed(self.bash_source))) # This is how bash source SHOULD be defined, but it's not! if name == 'CALL_SOURCE': strs = [] for func_name, source_name, call_spid, _, _ in reversed( self.debug_stack): # should only happen for the first entry if call_spid == const.NO_INTEGER: continue span = self.arena.GetLineSpan(call_spid) source_str = self.arena.GetLineSourceString(span.line_id) strs.append(source_str) if self.has_main: strs.append('-') # Bash does this to line up with main? return value.MaybeStrArray(strs) # TODO: Reuse this object too? if name == 'BASH_LINENO': strs = [] for _, _, call_spid, _, _ in reversed(self.debug_stack): # should only happen for the first entry if call_spid == const.NO_INTEGER: continue span = self.arena.GetLineSpan(call_spid) line_num = self.arena.GetLineNumber(span.line_id) strs.append(str(line_num)) if self.has_main: strs.append('0') # Bash does this to line up with main? return value.MaybeStrArray(strs) # TODO: Reuse this object too? if name == 'LINENO': span = self.arena.GetLineSpan(self.current_spid) # TODO: maybe use interned GetLineNumStr? s = str(self.arena.GetLineNumber(span.line_id)) self.line_num.s = s return self.line_num # This is OSH-specific. Get rid of it in favor of ${BASH_SOURCE[0]} ? if name == 'SOURCE_NAME': # Update and reuse an object. span = self.arena.GetLineSpan(self.current_spid) self.source_name.s = self.arena.GetLineSourceString(span.line_id) return self.source_name cell, _ = self._FindCellAndNamespace(name, lookup_mode) if cell: return cell.val return value.Undef()
def OldValue(lval, mem, exec_opts): # type: (lvalue_t, Mem, optview.Exec) -> value_t """ Used by s+='x' and (( i += 1 )) TODO: We need a stricter and less ambiguous version for Oil. Problem: - why does lvalue have Indexed and Keyed, while sh_lhs_expr only has IndexedName? - should I have lvalue.Named and lvalue.Indexed only? - and Indexed uses the index_t type? - well that might be Str or Int """ assert isinstance(lval, lvalue_t), lval # TODO: refactor lvalue_t to make this simpler UP_lval = lval with tagswitch(lval) as case: if case(lvalue_e.Named): # (( i++ )) lval = cast(lvalue__Named, UP_lval) var_name = lval.name elif case(lvalue_e.Indexed): # (( a[i]++ )) lval = cast(lvalue__Indexed, UP_lval) var_name = lval.name elif case(lvalue_e.Keyed): # (( A['K']++ )) ? I think this works lval = cast(lvalue__Keyed, UP_lval) var_name = lval.name else: raise AssertionError() val = _LookupVar(var_name, mem, exec_opts) UP_val = val with tagswitch(lval) as case: if case(lvalue_e.Named): return val elif case(lvalue_e.Indexed): lval = cast(lvalue__Indexed, UP_lval) array_val = None # type: value__MaybeStrArray with tagswitch(val) as case2: if case2(value_e.Undef): array_val = value.MaybeStrArray([]) elif case2(value_e.MaybeStrArray): tmp = cast(value__MaybeStrArray, UP_val) # mycpp rewrite: add tmp. cast() creates a new var in inner scope array_val = tmp else: e_die("Can't use [] on value of type %s", ui.ValType(val)) s = word_eval.GetArrayItem(array_val.strs, lval.index) 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 case(lvalue_e.Keyed): lval = cast(lvalue__Keyed, UP_lval) assoc_val = None # type: value__AssocArray with tagswitch(val) as case2: if case2(value_e.Undef): # This never happens, because undef[x]+= is assumed to raise AssertionError() elif case2(value_e.AssocArray): tmp2 = cast(value__AssocArray, UP_val) # mycpp rewrite: add tmp. cast() creates a new var in inner scope assoc_val = tmp2 else: e_die("Can't use [] on value of type %s", ui.ValType(val)) s = assoc_val.d.get(lval.key) if s is None: val = value.Str('') else: val = value.Str(s) else: raise AssertionError() return val
def testSetVarClearFlag(self): mem = _InitMem() print(mem) mem.PushCall('my-func', 0, ['ONE']) self.assertEqual(2, len(mem.var_stack)) # internal details # local x=y mem.SetVar(lvalue.Named('x'), value.Str('y'), (), scope_e.LocalOnly) self.assertEqual('y', mem.var_stack[-1]['x'].val.s) # New frame mem.PushCall('my-func', 0, ['TWO']) self.assertEqual(3, len(mem.var_stack)) # internal details # x=y -- test out dynamic scope mem.SetVar(lvalue.Named('x'), value.Str('YYY'), (), scope_e.Dynamic) self.assertEqual('YYY', mem.var_stack[-2]['x'].val.s) self.assertEqual(None, mem.var_stack[-1].get('x')) # myglobal=g mem.SetVar(lvalue.Named('myglobal'), value.Str('g'), (), scope_e.Dynamic) self.assertEqual('g', mem.var_stack[0]['myglobal'].val.s) self.assertEqual(False, mem.var_stack[0]['myglobal'].exported) # 'export PYTHONPATH=/' mem.SetVar(lvalue.Named('PYTHONPATH'), value.Str('/'), (var_flags_e.Exported, ), scope_e.Dynamic) self.assertEqual('/', mem.var_stack[0]['PYTHONPATH'].val.s) self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported) ex = mem.GetExported() self.assertEqual('/', ex['PYTHONPATH']) mem.SetVar(lvalue.Named('PYTHONPATH'), None, (var_flags_e.Exported, ), scope_e.Dynamic) self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported) # 'export myglobal'. None means don't touch it. Undef would be confusing # because it might mean "unset", but we have a separated API for that. mem.SetVar(lvalue.Named('myglobal'), None, (var_flags_e.Exported, ), scope_e.Dynamic) self.assertEqual(True, mem.var_stack[0]['myglobal'].exported) # export g2 -- define and export empty mem.SetVar(lvalue.Named('g2'), None, (var_flags_e.Exported, ), scope_e.Dynamic) self.assertEqual(value_e.Undef, mem.var_stack[0]['g2'].val.tag) self.assertEqual(True, mem.var_stack[0]['g2'].exported) # readonly myglobal self.assertEqual(False, mem.var_stack[0]['myglobal'].readonly) mem.SetVar(lvalue.Named('myglobal'), None, (var_flags_e.ReadOnly, ), scope_e.Dynamic) self.assertEqual(True, mem.var_stack[0]['myglobal'].readonly) mem.SetVar(lvalue.Named('PYTHONPATH'), value.Str('/lib'), (), scope_e.Dynamic) self.assertEqual('/lib', mem.var_stack[0]['PYTHONPATH'].val.s) self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported) # COMPREPLY=(1 2 3) # invariant to enforce: arrays can't be exported mem.SetVar(lvalue.Named('COMPREPLY'), value.MaybeStrArray(['1', '2', '3']), (), scope_e.GlobalOnly) self.assertEqual(['1', '2', '3'], mem.var_stack[0]['COMPREPLY'].val.strs) # export COMPREPLY try: mem.SetVar(lvalue.Named('COMPREPLY'), None, (var_flags_e.Exported, ), scope_e.Dynamic) except error.FatalRuntime as e: pass else: self.fail("Expected failure") # readonly r=1 mem.SetVar(lvalue.Named('r'), value.Str('1'), (var_flags_e.ReadOnly, ), scope_e.Dynamic) self.assertEqual('1', mem.var_stack[0]['r'].val.s) self.assertEqual(False, mem.var_stack[0]['r'].exported) self.assertEqual(True, mem.var_stack[0]['r'].readonly) print(mem) # r=newvalue try: mem.SetVar(lvalue.Named('r'), value.Str('newvalue'), (), scope_e.Dynamic) except error.FatalRuntime as e: pass else: self.fail("Expected failure") # readonly r2 -- define empty readonly mem.SetVar(lvalue.Named('r2'), None, (var_flags_e.ReadOnly, ), scope_e.Dynamic) self.assertEqual(value_e.Undef, mem.var_stack[0]['r2'].val.tag) self.assertEqual(True, mem.var_stack[0]['r2'].readonly) # export -n PYTHONPATH # Remove the exported property. NOTE: scope is LocalOnly for Oil? self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported) mem.ClearFlag('PYTHONPATH', var_flags_e.Exported, scope_e.Dynamic) self.assertEqual(False, mem.var_stack[0]['PYTHONPATH'].exported) lhs = lvalue.Indexed('a', 1) lhs.spids.append(0) # a[1]=2 mem.SetVar(lhs, value.Str('2'), (), scope_e.Dynamic) self.assertEqual([None, '2'], mem.var_stack[0]['a'].val.strs) # a[1]=3 mem.SetVar(lhs, value.Str('3'), (), scope_e.Dynamic) self.assertEqual([None, '3'], mem.var_stack[0]['a'].val.strs) # a[1]=(x y z) # illegal but doesn't parse anyway if 0: try: mem.SetVar(lhs, value.MaybeStrArray(['x', 'y', 'z']), (), scope_e.Dynamic) except error.FatalRuntime as e: pass else: self.fail("Expected failure") # readonly a mem.SetVar(lvalue.Named('a'), None, (var_flags_e.ReadOnly, ), scope_e.Dynamic) self.assertEqual(True, mem.var_stack[0]['a'].readonly) try: # a[1]=3 mem.SetVar(lhs, value.Str('3'), (), scope_e.Dynamic) except error.FatalRuntime as e: pass else: self.fail("Expected failure")
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): # This is only used for a STATUS QUERY now. We only show the name, # not the body. status = self._PrintFuncs(names) else: # Disallow this since it would be incompatible. e_usage('with -f expects function names') return status if arg.F: names = arg_r.Rest() if len(names): status = self._PrintFuncs(names) else: # bash quirk: with no names, 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: which_scopes = scope_e.LocalOnly else: # declare/typeset if arg.g: which_scopes = scope_e.GlobalOnly else: which_scopes = 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.GetValue(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.SetValue(lvalue.Named(pair.var_name), rval, which_scopes, flags=flags) return status