def _BindNewAssocArrayWithEntry(self, namespace, lval, val, new_flags): """Fill 'namespace' with a new indexed array entry.""" d = {lval.index: val.s} # TODO: RHS has to be string? new_value = value.AssocArray(d) # associative arrays can't be exported; don't need AssocArray flag readonly = var_flags_e.ReadOnly in new_flags namespace[lval.name] = runtime_asdl.cell(new_value, False, readonly, False)
def _BindNewArrayWithEntry(self, namespace, lval, val, new_flags): """Fill 'namespace' with a new indexed array entry.""" items = [None] * lval.index items.append(val.s) new_value = value.StrArray(items) # arrays can't be exported; can't have AssocArray flag readonly = var_flags_e.ReadOnly in new_flags namespace[lval.name] = runtime_asdl.cell(new_value, False, readonly, False)
def SetVar(self, lval, val, new_flags, lookup_mode): """ Args: lval: lvalue val: value, or None if only changing flags new_flags: tuple of flags to set: ReadOnly | Exported () means no flags to start with None means unchanged? scope: Local | Global | Dynamic - for builtins, PWD, etc. NOTE: in bash, PWD=/ changes the directory. But not in dash. """ # STRICTNESS / SANENESS: # # 1) Don't create arrays automatically, e.g. a[1000]=x # 2) Never change types? yeah I think that's a good idea, at least for oil # (not sh, for compatibility). set -o strict-types or something. That # means arrays have to be initialized with let arr = [], which is fine. # This helps with stuff like IFS. It starts off as a string, and assigning # it to a list is en error. I guess you will have to turn this no for # bash? # # TODO: # - COMPUTED_VARS can't be set # - What about PWD / OLDPWD / UID / EUID ? You can simply make them # readonly. # - $PS1 and $PS4 should be PARSED when they are set, to avoid the error on use # - Other validity: $HOME could be checked for existence assert new_flags is not None if lval.tag == lvalue_e.LhsName: #if lval.name == 'ldflags': # TODO: Turn this into a tracing feature. Like osh --tracevar ldflags # --tracevar foo. Has to respect environment variables too. if 0: util.log('--- SETTING ldflags to %s', val) if lval.spids: span_id = lval.spids[0] line_span = self.arena.GetLineSpan(span_id) line_id = line_span.line_id source_str = self.arena.GetLineSourceString(line_id) line_num = self.arena.GetLineNumber(line_id) #length = line_span.length util.log('--- spid %s: %s, line %d, col %d', span_id, source_str, line_num+1, line_span.col) # TODO: Need the arena to look it up the line spid and line number. cell, namespace = self._FindCellAndNamespace(lval.name, lookup_mode) if cell: if val is not None: if cell.readonly: # TODO: error context e_die("Can't assign to readonly value %r", lval.name) cell.val = val if var_flags_e.Exported in new_flags: cell.exported = True if var_flags_e.ReadOnly in new_flags: cell.readonly = True if var_flags_e.AssocArray in new_flags: cell.is_assoc_array = True else: if val is None: # set -o nounset; local foo; echo $foo # It's still undefined! val = value.Undef() # export foo, readonly foo cell = runtime_asdl.cell(val, var_flags_e.Exported in new_flags, var_flags_e.ReadOnly in new_flags, var_flags_e.AssocArray in new_flags) namespace[lval.name] = cell if (cell.val is not None and cell.val.tag == value_e.StrArray and cell.exported): e_die("Can't export array") # TODO: error context elif lval.tag == lvalue_e.LhsIndexedName: # TODO: All paths should have this? We can get here by a[x]=1 or # (( a[ x ] = 1 )). Maybe we should make them different? if lval.spids: left_spid = lval.spids[0] else: left_spid = const.NO_INTEGER # TODO: This is a parse error! # a[1]=(1 2 3) if val.tag == value_e.StrArray: e_die("Can't assign array to array member", span_id=left_spid) # bash/mksh have annoying behavior of letting you do LHS assignment to # Undef, which then turns into an INDEXED array. (Undef means that set # -o nounset fails.) cell, namespace = self._FindCellAndNamespace(lval.name, lookup_mode) if not cell: self._BindNewArrayWithEntry(namespace, lval, val, new_flags) return cell_tag = cell.val.tag if cell_tag == value_e.Str: # s=x # s[1]=y # invalid e_die("Entries in value of type %s can't be assigned to", cell.val.__class__.__name__, span_id=left_spid) if cell.readonly: e_die("Can't assign to readonly value", span_id=left_spid) # This is for the case where we did declare -a foo or declare -A foo. # There IS a cell, but it's still undefined. if cell_tag == value_e.Undef: if cell.is_assoc_array: self._BindNewAssocArrayWithEntry(namespace, lval, val, new_flags) else: self._BindNewArrayWithEntry(namespace, lval, val, new_flags) return if cell_tag == value_e.StrArray: strs = cell.val.strs try: strs[lval.index] = val.s except IndexError: # Fill it in with None. It could look like this: # ['1', 2, 3, None, None, '4', None] # Then ${#a[@]} counts the entries that are not None. # # TODO: strict-array for Oil arrays won't auto-fill. n = lval.index - len(strs) + 1 strs.extend([None] * n) strs[lval.index] = val.s return if cell_tag == value_e.AssocArray: cell.val.d[lval.index] = val.s return else: raise AssertionError(lval.__class__.__name__)
def SetVar(self, lval, val, flags_to_set, lookup_mode, flags_to_clear=(), keyword_id=None): """ Args: lval: lvalue val: value, or None if only changing flags flags_to_set: tuple of flags to set: ReadOnly | Exported () means no flags to start with scope: Local | Global | Dynamic - for builtins, PWD, etc. NOTE: in bash, PWD=/ changes the directory. But not in dash. """ # STRICTNESS / SANENESS: # # 1) Don't create arrays automatically, e.g. a[1000]=x # 2) Never change types? yeah I think that's a good idea, at least for oil # (not sh, for compatibility). set -o strict-types or something. That # means arrays have to be initialized with let arr = [], which is fine. # This helps with stuff like IFS. It starts off as a string, and assigning # it to a list is an error. I guess you will have to turn this no for # bash? # # TODO: # - COMPUTED_VARS can't be set # - What about PWD / OLDPWD / UID / EUID ? You can simply make them # readonly. # - $PS1 and $PS4 should be PARSED when they are set, to avoid the error on use # - Other validity: $HOME could be checked for existence assert flags_to_set is not None if lval.tag == lvalue_e.Named: cell, namespace = self._FindCellAndNamespace( lval.name, lookup_mode) self._CheckOilKeyword(keyword_id, lval, cell) if cell: # Clear before checking readonly bit. # NOTE: Could be cell.flags &= flag_clear_mask if var_flags_e.Exported in flags_to_clear: cell.exported = False if var_flags_e.ReadOnly in flags_to_clear: cell.readonly = False if val is not None: # e.g. declare -rx existing if cell.readonly: # TODO: error context e_die("Can't assign to readonly value %r", lval.name) cell.val = val # NOTE: Could be cell.flags |= flag_set_mask if var_flags_e.Exported in flags_to_set: cell.exported = True if var_flags_e.ReadOnly in flags_to_set: cell.readonly = True else: if val is None: # declare -rx nonexistent # set -o nounset; local foo; echo $foo # It's still undefined! val = value.Undef() # export foo, readonly foo cell = runtime_asdl.cell(val, var_flags_e.Exported in flags_to_set, var_flags_e.ReadOnly in flags_to_set) namespace[lval.name] = cell # Maintain invariant that only strings and undefined cells can be # exported. if (cell.val is not None and cell.val.tag not in (value_e.Undef, value_e.Str) and cell.exported): e_die("Can't export array") # TODO: error context elif lval.tag == lvalue_e.Indexed: # There is no syntax 'declare a[x]' assert val is not None, val # TODO: All paths should have this? We can get here by a[x]=1 or # (( a[ x ] = 1 )). Maybe we should make them different? left_spid = lval.spids[0] if lval.spids else const.NO_INTEGER # bash/mksh have annoying behavior of letting you do LHS assignment to # Undef, which then turns into an INDEXED array. (Undef means that set # -o nounset fails.) cell, namespace = self._FindCellAndNamespace( lval.name, lookup_mode) self._CheckOilKeyword(keyword_id, lval, cell) if not cell: self._BindNewArrayWithEntry(namespace, lval, val, flags_to_set) return if cell.readonly: e_die("Can't assign to readonly array", span_id=left_spid) cell_tag = cell.val.tag # undef[0]=y is allowed if cell_tag == value_e.Undef: self._BindNewArrayWithEntry(namespace, lval, val, flags_to_set) return if cell_tag == value_e.Str: # s=x # s[1]=y # invalid e_die("Entries in value of type %s can't be assigned to", cell.val.__class__.__name__, span_id=left_spid) if cell_tag == value_e.MaybeStrArray: strs = cell.val.strs try: strs[lval.index] = val.s except IndexError: # Fill it in with None. It could look like this: # ['1', 2, 3, None, None, '4', None] # Then ${#a[@]} counts the entries that are not None. # # TODO: strict-array for Oil arrays won't auto-fill. n = lval.index - len(strs) + 1 strs.extend([None] * n) strs[lval.index] = val.s return # AssocArray shouldn't happen because we query IsAssocArray before # evaluating lhs_expr. e_die("Object of this type can't be indexed: %s", cell.val) elif lval.tag == lvalue_e.Keyed: # There is no syntax 'declare A["x"]' assert val is not None, val left_spid = lval.spids[0] if lval.spids else const.NO_INTEGER cell, namespace = self._FindCellAndNamespace( lval.name, lookup_mode) self._CheckOilKeyword(keyword_id, lval, cell) # We already looked it up before making the lvalue assert cell.val.tag == value_e.AssocArray, cell if cell.readonly: e_die("Can't assign to readonly associative array", span_id=left_spid) cell.val.d[lval.key] = val.s else: raise AssertionError(lval.__class__.__name__)