def _BindNewAssocArrayWithEntry(self, namespace, lval, value, new_flags): """Fill 'namespace' with a new indexed array entry.""" d = {lval.index: value.s} # TODO: RHS has to be string? new_value = runtime.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.cell(new_value, False, readonly, False)
def _BindNewArrayWithEntry(self, namespace, lval, value, new_flags): """Fill 'namespace' with a new indexed array entry.""" items = [None] * lval.index items.append(value.s) new_value = runtime.StrArray(items) # arrays can't be exported; can't have AssocArray flag readonly = var_flags_e.ReadOnly in new_flags namespace[lval.name] = runtime.cell(new_value, False, readonly, False)
def SetVar(self, lval, value, 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? 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', value) if lval.spids: span_id = lval.spids[0] line_span = self.arena.GetLineSpan(span_id) line_id = line_span.line_id #line = arena.GetLine(line_id) path, line_num = self.arena.GetDebugInfo(line_id) col = line_span.col #length = line_span.length util.log('--- spid %s: %s, line %d, col %d', span_id, path, line_num + 1, col) # TODO: Need the arena to look it up the line spid and line number. # Maybe this should return one of (cell, scope). existing cell, or the # scope to put it in? # _FindCellOrScope cell, namespace = self._FindCellAndNamespace( lval.name, lookup_mode) if cell: if value is not None: if cell.readonly: # TODO: error context e_die("Can't assign to readonly value %r", lval.name) cell.val = value if var_flags_e.Exported in new_flags: cell.exported = True if var_flags_e.ReadOnly in new_flags: cell.readonly = True else: if value is None: value = runtime.Undef() # export foo, readonly foo cell = runtime.cell(value, var_flags_e.Exported in new_flags, var_flags_e.ReadOnly 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: # a[1]=(1 2 3) if value.tag == value_e.StrArray: e_die("Can't assign array to array member" ) # TODO: error context cell, namespace = self._FindCellAndNamespace( lval.name, lookup_mode) if cell: if cell.val.tag != value_e.StrArray: # s=x # s[1]=y e_die("Can't index non-array") # TODO: error context if cell.readonly: e_die("Can't assign to readonly value") strs = cell.val.strs try: strs[lval.index] = value.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] = value.s else: # When the array doesn't exist yet, it is created filled with None. # Access to the array needs to explicitly filter those sentinel values. # It also wastes memory. But indexed access is fast. # What should be optimized for? Bash uses a linked list. Random access # takes linear time, but iteration skips unset entries automatically. # - Maybe represent as hash table? Then it's not an ASDL type? # representations: # - array_item.Str array_item.Undef # - parallel array: val.strs, val.undefs # - or change ASDL type checking # - ASDL language does not allow: StrArray(string?* strs) # - or add dict to ASDL? Didn't it support obj? # - finding the max index is linear time? # - also you have to sort the indices # # array ops: # a=(1 2) # a[1]=x # a+=(1 2) # ${a[@]} - get all # ${#a[@]} - length # ${!a[@]} - keys # That seems pretty minimal. items = [None] * lval.index items.append(value.s) new_value = runtime.StrArray(items) # arrays can't be exported cell = runtime.cell(new_value, False, var_flags_e.ReadOnly in new_flags) namespace[lval.name] = cell else: raise AssertionError
def SetVar(self, lval, value, new_flags, lookup_mode, strict_array=False): """ 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? 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', value) if lval.spids: span_id = lval.spids[0] line_span = self.arena.GetLineSpan(span_id) line_id = line_span.line_id #line = arena.GetLine(line_id) path, line_num = self.arena.GetDebugInfo(line_id) col = line_span.col #length = line_span.length util.log('--- spid %s: %s, line %d, col %d', span_id, path, line_num + 1, col) # TODO: Need the arena to look it up the line spid and line number. # Maybe this should return one of (cell, scope). existing cell, or the # scope to put it in? # _FindCellOrScope cell, namespace = self._FindCellAndNamespace( lval.name, lookup_mode) if cell: if value is not None: if cell.readonly: # TODO: error context e_die("Can't assign to readonly value %r", lval.name) cell.val = value 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 value is None: # set -o nounset; local foo; echo $foo # It's still undefined! value = runtime.Undef() # export foo, readonly foo cell = runtime.cell(value, 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 value.tag == value_e.StrArray: e_die("Can't assign array to array member", span_id=left_spid) cell, namespace = self._FindCellAndNamespace( lval.name, lookup_mode) if not cell: self._BindNewArrayWithEntry(namespace, lval, value, new_flags) return # bash/mksh have annoying behavior of letting you do LHS assignment to # Undef, which then turns into an array. (Undef means that set -o # nounset fails.) cell_tag = cell.val.tag if (cell_tag == value_e.Str or (cell_tag == value_e.Undef and strict_array)): # 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) if cell_tag == value_e.Undef: if cell.is_assoc_array: self._BindNewAssocArrayWithEntry(namespace, lval, value, new_flags) else: self._BindNewArrayWithEntry(namespace, lval, value, new_flags) return if cell_tag == value_e.StrArray: strs = cell.val.strs try: strs[lval.index] = value.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] = value.s return if cell_tag == value_e.AssocArray: cell.val.d[lval.index] = value.s return else: raise AssertionError(lval.__class__.__name__)