def ToLValue(node): # type: (arith_expr_t) -> sh_lhs_expr_t """Determine if a node is a valid L-value by whitelisting tags. Valid: x = y a[1] = y Invalid: a[0][0] = y """ UP_node = node with tagswitch(node) as case: if case(arith_expr_e.VarRef): node = cast(arith_expr__VarRef, UP_node) # For consistency with osh/cmd_parse.py, append a span_id. # TODO: (( a[ x ] = 1 )) and a[x]=1 should use different LST nodes. n = sh_lhs_expr.Name(node.token.val) n.spids.append(node.token.span_id) return n elif case(arith_expr_e.Binary): node = cast(arith_expr__Binary, UP_node) if (node.op_id == Id.Arith_LBracket and node.left.tag_() == arith_expr_e.VarRef): left = cast(arith_expr__VarRef, node.left) return sh_lhs_expr.IndexedName(left.token.val, node.right) # But a[0][0] = 1 is NOT valid. return None
def Eval(self, node): # type: (command_t) -> None UP_node = node with tagswitch(node) as case: if case(command_e.Simple): node = cast(command__Simple, UP_node) # Need splitter for this. if 0: cmd_val = self.word_ev.EvalWordSequence2(node.words, allow_assign=True) for arg in cmd_val.argv: log('arg %s', arg) for w in node.words: val = self.word_ev.EvalWordToString(w) # TODO: how to print repr() in C++? log('arg %d', val.tag_()) elif case(command_e.DParen): node = cast(command__DParen, UP_node) a = self.arith_ev.Eval(node.child) # TODO: how to print repr() in C++? log('arith val %d', a.tag_()) else: log('Unhandled node %s', NewStr(command_str(node.tag_())))
def CommandId(w): # type: (word_t) -> Id_t UP_w = w with tagswitch(w) as case: if case(word_e.Token): tok = cast(Token, UP_w) return tok.id elif case(word_e.Compound): w = cast(compound_word, UP_w) # Has to be a single literal part if len(w.parts) != 1: return Id.Word_Compound token_type = _LiteralId(w.parts[0]) if token_type == Id.Undefined_Tok: return Id.Word_Compound elif token_type in (Id.Lit_LBrace, Id.Lit_RBrace, Id.Lit_Equals, Id.ControlFlow_Return): # OSH and Oil recognize: { } # Oil recognizes: = return return token_type token_kind = consts.GetKind(token_type) if token_kind == Kind.KW: return token_type return Id.Word_Compound else: raise AssertionError(w.tag_())
def Push(self, redirects, waiter): # type: (List[redirect_t], Waiter) -> bool #log('> fd_state.Push %s', redirects) new_frame = _FdFrame() self.stack.append(new_frame) self.cur_frame = new_frame for r in redirects: # TODO: Could we use inheritance to make this cheaper? UP_r = r with tagswitch(r) as case: if case(redirect_e.Path): r = cast(redirect__Path, UP_r) op_spid = r.op_spid elif case(redirect_e.FileDesc): r = cast(redirect__FileDesc, UP_r) op_spid = r.op_spid elif case(redirect_e.HereDoc): r = cast(redirect__HereDoc, UP_r) op_spid = r.op_spid else: raise AssertionError() #log('apply %s', r) self.errfmt.PushLocation(op_spid) try: if not self._ApplyRedirect(r, waiter): return False # for bad descriptor finally: self.errfmt.PopLocation() #log('done applying %d redirects', len(redirects)) return True
def GetJoinChar(self): # type: () -> str """ For decaying arrays by joining, eg. "$@" -> $@. array """ # https://www.gnu.org/software/bash/manual/bashref.html#Special-Parameters # http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02 # "When the expansion occurs within a double-quoted string (see # Double-Quotes), it shall expand to a single field with the value of # each parameter separated by the first character of the IFS variable, or # by a <space> if IFS is unset. If IFS is set to a null string, this is # not equivalent to unsetting it; its first character does not exist, so # the parameter values are concatenated." val = self.mem.GetValue('IFS') # type: value_t UP_val = val with tagswitch(val) as case: if case(value_e.Undef): return ' ' elif case(value_e.Str): val = cast(value__Str, UP_val) if len(val.s): return val.s[0] else: return '' else: # TODO: Raise proper error raise AssertionError("IFS shouldn't be an array")
def OnShAssignment(self, lval, op, val, flags, which_scopes): # type: (lvalue_t, assign_op_t, value_t, int, scope_t) -> None buf = self._ShTraceBegin() if not buf: return left = '?' UP_lval = lval with tagswitch(lval) as case: if case(lvalue_e.Named): lval = cast(lvalue__Named, UP_lval) left = lval.name elif case(lvalue_e.Indexed): lval = cast(lvalue__Indexed, UP_lval) left = '%s[%d]' % (lval.name, lval.index) elif case(lvalue_e.Keyed): lval = cast(lvalue__Keyed, UP_lval) left = '%s[%s]' % (lval.name, qsn.maybe_shell_encode(lval.key)) buf.write(left) # Only two possibilities here buf.write('+=' if op == assign_op_e.PlusEqual else '=') _PrintShValue(val, buf) buf.write('\n') self.f.write(buf.getvalue())
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 _PrintShValue(val, buf): # type: (value_t, mylib.BufWriter) -> None """Using maybe_shell_encode() for legacy xtrace_details.""" # NOTE: This is a bit like _PrintVariables for declare -p result = '?' UP_val = val with tagswitch(val) as case: if case(value_e.Str): val = cast(value__Str, UP_val) result = qsn.maybe_shell_encode(val.s) elif case(value_e.MaybeStrArray): val = cast(value__MaybeStrArray, UP_val) parts = ['('] for s in val.strs: parts.append(qsn.maybe_shell_encode(s)) parts.append(')') result = ' '.join(parts) elif case(value_e.AssocArray): val = cast(value__AssocArray, UP_val) parts = ['('] for k, v in iteritems(val.d): parts.append( '[%s]=%s' % (qsn.maybe_shell_encode(k), qsn.maybe_shell_encode(v))) parts.append(')') result = ' '.join(parts) buf.write(result)
def BoolId(w): # type: (word_t) -> Id_t UP_w = w with tagswitch(w) as case: if case(word_e.String): # for test/[ w = cast(word__String, UP_w) return w.id elif case(word_e.Token): tok = cast(Token, UP_w) return tok.id elif case(word_e.Compound): w = cast(compound_word, UP_w) if len(w.parts) != 1: return Id.Word_Compound token_type = _LiteralId(w.parts[0]) if token_type == Id.Undefined_Tok: return Id.Word_Compound # It's a regular word # This is outside the BoolUnary/BoolBinary namespace, but works the same. if token_type in (Id.KW_Bang, Id.Lit_DRightBracket): return token_type # special boolean "tokens" token_kind = consts.GetKind(token_type) if token_kind in (Kind.BoolUnary, Kind.BoolBinary): return token_type # boolean operators return Id.Word_Compound else: # I think Empty never happens in this context? raise AssertionError(w.tag_())
def LeftMostSpanForWord(w): # type: (word_t) -> int UP_w = w with tagswitch(w) as case: if case(word_e.Compound): w = cast(compound_word, UP_w) if len(w.parts): return LeftMostSpanForPart(w.parts[0]) else: # This is possible for empty brace sub alternative {a,b,} return runtime.NO_SPID elif case(word_e.Token): tok = cast(Token, UP_w) return tok.span_id elif case(word_e.Empty): return runtime.NO_SPID elif case(word_e.BracedTree): w = cast(word__BracedTree, UP_w) # This should always have one part? return LeftMostSpanForPart(w.parts[0]) elif case(word_e.String): w = cast(word__String, UP_w) return w.span_id # See _StringWordEmitter in osh/builtin_bracket.py else: raise AssertionError(w.tag_())
def _SetToArg(action, suffix, arg_r, out): # type: (SetToArgAction, Optional[str], Reader, _Attributes) -> bool """ Perform the action. """ if suffix: # for the ',' in -d, arg = suffix else: arg_r.Next() arg = arg_r.Peek() if arg is None: e_usage('expected argument to %r' % ('-' + action.name), span_id=arg_r.SpanId()) # e.g. spec.LongFlag('--format', ['text', 'html']) # Should change to arg.Enum([...]) with tagswitch(action.flag_type) as case: if case(flag_type_e.Enum): alts = cast(flag_type__Enum, action.flag_type).alts if arg not in alts: e_usage('got invalid argument %r to %r, expected one of: %s' % (arg, ('-' + action.name), ', '.join(alts)), span_id=arg_r.SpanId()) val = value.Str(arg) # type: value_t elif case(flag_type_e.Str): val = value.Str(arg) elif case(flag_type_e.Int): try: i = int(arg) except ValueError: e_usage('expected integer after %s, got %r' % ('-' + action.name, arg), span_id=arg_r.SpanId()) # So far all our int values are > 0, so use -1 as the 'unset' value if i < 0: e_usage('got invalid integer for %s: %s' % ('-' + action.name, arg), span_id=arg_r.SpanId()) val = value.Int(i) elif case(flag_type_e.Float): try: val = value.Float(float(arg)) except ValueError: e_usage('expected number after %r, got %r' % ('-' + action.name, arg), span_id=arg_r.SpanId()) else: raise AssertionError() out.Set(action.name, val) return action.quit_parsing_flags
def _VarRefOrWord(node, dynamic_arith): # type: (arith_expr_t, bool) -> bool with tagswitch(node) as case: if case(arith_expr_e.VarRef): return True elif case(arith_expr_e.Word): if dynamic_arith: return True return False
def _RightMostSpanForPart(part): # type: (word_part_t) -> int UP_part = part with tagswitch(part) as case: if case(word_part_e.ShArrayLiteral): part = cast(sh_array_literal, UP_part) # TODO: Return ) return LeftMostSpanForWord(part.words[0]) # Hm this is a=(1 2 3) elif case(word_part_e.Literal): # Just use the token tok = cast(Token, UP_part) return tok.span_id elif case(word_part_e.EscapedLiteral): part = cast(word_part__EscapedLiteral, UP_part) return part.token.span_id elif case(word_part_e.SingleQuoted): part = cast(single_quoted, UP_part) return part.spids[1] # right ' elif case(word_part_e.DoubleQuoted): part = cast(double_quoted, UP_part) return part.spids[1] # right " elif case(word_part_e.SimpleVarSub): part = cast(simple_var_sub, UP_part) return part.token.span_id elif case(word_part_e.BracedVarSub): part = cast(braced_var_sub, UP_part) spid = part.spids[1] # right } assert spid != runtime.NO_SPID return spid elif case(word_part_e.CommandSub): part = cast(command_sub, UP_part) return part.spids[1] elif case(word_part_e.TildeSub): return runtime.NO_SPID elif case(word_part_e.ArithSub): part = cast(word_part__ArithSub, UP_part) return part.spids[1] elif case(word_part_e.ExtGlob): part = cast(word_part__ExtGlob, UP_part) return part.spids[1] # TODO: Do Splice and FuncCall need it? else: raise AssertionError(part.tag_())
def SpanForArithExpr(node): # type: (arith_expr_t) -> int UP_node = node with tagswitch(node) as case: if case(arith_expr_e.VarRef): token = cast(Token, UP_node) return token.span_id elif case(arith_expr_e.Word): w = cast(compound_word, UP_node) return word_.LeftMostSpanForWord(w) return runtime.NO_SPID
def _ExpandPart( parts, # type: List[word_part_t] first_alt_index, # type: int suffixes, # type: List[List[word_part_t]] ): # type: (...) -> List[List[word_part_t]] """Mutually recursive with _BraceExpand. Args: parts: input parts first_alt_index: index of the first BracedTuple suffixes: List of suffixes to append. """ out = [] # type: List[List[word_part_t]] prefix = parts[:first_alt_index] expand_part = parts[first_alt_index] UP_part = expand_part with tagswitch(expand_part) as case: if case(word_part_e.BracedTuple): expand_part = cast(word_part__BracedTuple, UP_part) # Call _BraceExpand on each of the inner words too! expanded_alts = [] # type: List[List[word_part_t]] for w in expand_part.words: expanded_alts.extend(_BraceExpand(w.parts)) for alt_parts in expanded_alts: for suffix in suffixes: out_parts = [] # type: List[word_part_t] out_parts.extend(prefix) out_parts.extend(alt_parts) out_parts.extend(suffix) out.append(out_parts) elif case(word_part_e.BracedRange): expand_part = cast(word_part__BracedRange, UP_part) # Not mutually recursive with _BraceExpand strs = _RangeStrings(expand_part) for s in strs: for suffix in suffixes: out_parts_ = [] # type: List[word_part_t] out_parts_.extend(prefix) # Preserve span_id from the original t = Token(Id.Lit_Chars, expand_part.spids[0], s) out_parts_.append(t) out_parts_.extend(suffix) out.append(out_parts_) else: raise AssertionError() return out
def SpanForArithExpr(node): # type: (arith_expr_t) -> int UP_node = node with tagswitch(node) as case: if case(arith_expr_e.VarRef): # TODO: VarRef should store a token instead of a string! return runtime.NO_SPID elif case(arith_expr_e.ArithWord): node = cast(arith_expr__ArithWord, UP_node) return word_.LeftMostSpanForWord(node.w) return runtime.NO_SPID
def OnMatch(self, prefix, suffix, arg_r, out): # type: (Optional[str], Optional[str], Reader, _Attributes) -> bool """Called when the flag matches.""" if suffix: # for the ',' in -d, arg = suffix else: arg_r.Next() arg = arg_r.Peek() if arg is None: e_usage('expected argument to %r' % ('-' + self.name), span_id=arg_r.SpanId()) # e.g. spec.LongFlag('--format', ['text', 'html']) # Should change to arg.Enum([...]) with tagswitch(self.flag_type) as case: if case(flag_type_e.Enum): alts = cast(flag_type__Enum, self.flag_type).alts if arg not in alts: e_usage( 'got invalid argument %r to %r, expected one of: %s' % (arg, ('-' + self.name), ', '.join(alts)), span_id=arg_r.SpanId()) val = value.Str(arg) # type: value_t elif case(flag_type_e.Str): val = value.Str(arg) elif case(flag_type_e.Int): try: val = value.Int(int(arg)) except ValueError: e_usage('expected integer after %r, got %r' % ('-' + self.name, arg), span_id=arg_r.SpanId()) elif case(flag_type_e.Float): try: val = value.Float(float(arg)) except ValueError: e_usage('expected number after %r, got %r' % ('-' + self.name, arg), span_id=arg_r.SpanId()) else: raise AssertionError() out.Set(self.name, val) return self.quit_parsing_flags
def find_dynamic_token(part): # type: (word_part_t) -> token|bool """Recursive search for dynamic token. This is patterned on word_._EvalWordPart, but with ~inverted boolean logic: it returns the token object if one is found, otherwise False. """ UP_part = part with tagswitch(part) as case: if case(word_part_e.ShArrayLiteral): return cast(Token, UP_part).token elif case(word_part_e.AssocArrayLiteral): return cast(Token, UP_part).token elif case(word_part_e.Literal): return False elif case(word_part_e.EscapedLiteral): return False elif case(word_part_e.SingleQuoted): return False elif case(word_part_e.DoubleQuoted): part = cast(double_quoted, UP_part) for p in part.parts: tok = find_dynamic_token(p) if tok: return tok return False elif case( word_part_e.CommandSub, word_part_e.SimpleVarSub, word_part_e.BracedVarSub, word_part_e.TildeSub, word_part_e.ArithSub, word_part_e.ExtGlob, word_part_e.Splice, ): return cast(Token, UP_part).token else: raise AssertionError(part.tag_())
def _ValToIntOrError(self, val, span_id=runtime.NO_SPID): # type: (value_t, int) -> int try: UP_val = val with tagswitch(val) as case: if case(value_e.Undef ): # 'nounset' already handled before got here # Happens upon a[undefined]=42, which unfortunately turns into a[0]=42. #log('blame_word %s arena %s', blame_word, self.arena) e_die('Undefined value in arithmetic context', span_id=span_id) elif case(value_e.Int): val = cast(value__Int, UP_val) return val.i elif case(value_e.Str): val = cast(value__Str, UP_val) return _StringToInteger(val.s, span_id=span_id) # calls e_die elif case(value_e.Obj): # Note: this handles var x = 42; echo $(( x > 2 )). if mylib.PYTHON: val = cast(value__Obj, UP_val) if isinstance(val.obj, int): return val.obj raise AssertionError() # not in C++ except error.FatalRuntime as e: if self.exec_opts.strict_arith(): raise else: span_id = word_.SpanIdFromError(e) self.errfmt.PrettyPrintError(e, prefix='warning: ') return 0 # Arrays and associative arrays always fail -- not controlled by strict_arith. # In bash, (( a )) is like (( a[0] )), but I don't want that. # And returning '0' gives different results. e_die("Expected a value convertible to integer, got %s", ui.ValType(val), span_id=span_id)
def _VarRefOrWord(self, anode): # type: (arith_expr_t) -> Tuple[Optional[str], int] """ Returns (var_name, span_id) if the arith node can be interpreted that way """ UP_anode = anode with tagswitch(anode) as case: if case(arith_expr_e.VarRef): tok = cast(Token, UP_anode) return (tok.val, tok.span_id) elif case(arith_expr_e.Word): w = cast(compound_word, UP_anode) var_name = self.EvalWordToString(w) span_id = word_.LeftMostSpanForWord(w) return (var_name, span_id) no_str = None # type: str return (no_str, runtime.NO_SPID)
def BraceExpandWords(words): # type: (List[word_t]) -> List[compound_word] out = [] # type: List[compound_word] for w in words: UP_w = w with tagswitch(w) as case: if case(word_e.BracedTree): w = cast(word__BracedTree, UP_w) parts_list = _BraceExpand(w.parts) tmp = [compound_word(p) for p in parts_list] out.extend(tmp) elif case(word_e.Compound): w = cast(compound_word, UP_w) out.append(w) else: raise AssertionError(w.tag_()) return out
def _GetSplitter(self, ifs=None): # type: (str) -> IfsSplitter """Based on the current stack frame, get the splitter.""" if ifs is None: val = self.mem.GetVar('IFS') UP_val = val with tagswitch(val) as case: if case(value_e.Undef): ifs = DEFAULT_IFS elif case(value_e.Str): val = cast(value__Str, UP_val) ifs = val.s else: # TODO: Raise proper error raise AssertionError("IFS shouldn't be an array") try: sp = self.splitters[ifs] except KeyError: # Figure out what kind of splitter we should instantiate. ifs_whitespace = mylib.BufWriter() ifs_other = mylib.BufWriter() for c in ifs: if c in ' \t\n': # Happens to be the same as DEFAULT_IFS ifs_whitespace.write(c) else: # TODO: \ not supported ifs_other.write(c) sp = IfsSplitter(ifs_whitespace.getvalue(), ifs_other.getvalue()) # NOTE: Technically, we could make the key more precise. IFS=$' \t' is # the same as IFS=$'\t '. But most programs probably don't do that, and # everything should work in any case. self.splitters[ifs] = sp return sp
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 SpanForLhsExpr(node): # type: (sh_lhs_expr_t) -> int # This switch is annoying but we don't have inheritance from the sum type # (because of diamond issue). We might change the schema later, which maeks # it moot. See the comment in frontend/syntax.asdl. UP_node = node with tagswitch(node) as case: if case(sh_lhs_expr_e.Name): node = cast(sh_lhs_expr__Name, UP_node) spids = node.spids elif case(sh_lhs_expr_e.IndexedName): node = cast(sh_lhs_expr__IndexedName, UP_node) spids = node.spids else: # Should not see UnparsedIndex raise AssertionError() if len(spids): return spids[0] else: return runtime.NO_SPID
def RightMostSpanForWord(w): # type: (word_t) -> int """Needed for here doc delimiters.""" UP_w = w with tagswitch(w) as case: if case(word_e.Compound): w = cast(compound_word, UP_w) if len(w.parts) == 0: # TODO: Use Empty instead raise AssertionError("Compound shouldn't be empty") else: end = w.parts[-1] return _RightMostSpanForPart(end) elif case(word_e.Empty): return runtime.NO_SPID elif case(word_e.Token): tok = cast(Token, UP_w) return tok.span_id else: raise AssertionError(w.tag_())
def Set(self, name, val): # type: (str, value_t) -> None self.attrs[name] = val if mylib.PYTHON: # Backward compatibility! with tagswitch(val) as case: if case(value_e.Undef): py_val = None # type: Any elif case(value_e.Bool): py_val = cast(value__Bool, val).b elif case(value_e.Int): py_val = cast(value__Int, val).i elif case(value_e.Float): py_val = cast(value__Float, val).f elif case(value_e.Str): py_val = cast(value__Str, val).s else: raise AssertionError(val) # debug-completion -> debug_completion setattr(self, name.replace('-', '_'), py_val)
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 OnProcessStart(self, pid, why): # type: (int, trace_t) -> None buf = self._RichTraceBegin('|') if not buf: return # TODO: ProcessSub and PipelinePart are commonly command.Simple, and also # Fork/ForkWait through the BraceGroup. We could print those argv arrays. UP_why = why with tagswitch(why) as case: # Synchronous cases if case(trace_e.External): why = cast(trace__External, UP_why) buf.write('command %d:' % pid) _PrintArgv(why.argv, buf) # Everything below is the same. Could use string literals? elif case(trace_e.ForkWait): buf.write('forkwait %d\n' % pid) elif case(trace_e.CommandSub): buf.write('command sub %d\n' % pid) # Async cases elif case(trace_e.ProcessSub): buf.write('proc sub %d\n' % pid) elif case(trace_e.HereDoc): buf.write('here doc %d\n' % pid) elif case(trace_e.Fork): buf.write('fork %d\n' % pid) elif case(trace_e.PipelinePart): buf.write('part %d\n' % pid) else: raise AssertionError() self.f.write(buf.getvalue())
def _ApplyRedirect(self, r, waiter): # type: (redirect, Waiter) -> None arg = r.arg UP_arg = arg with tagswitch(arg) as case: if case(redirect_arg_e.Path): arg = cast(redirect_arg__Path, UP_arg) if r.op_id in (Id.Redir_Great, Id.Redir_AndGreat): # > &> # NOTE: This is different than >| because it respects noclobber, but # that option is almost never used. See test/wild.sh. mode = posix.O_CREAT | posix.O_WRONLY | posix.O_TRUNC elif r.op_id == Id.Redir_Clobber: # >| mode = posix.O_CREAT | posix.O_WRONLY | posix.O_TRUNC elif r.op_id in (Id.Redir_DGreat, Id.Redir_AndDGreat): # >> &>> mode = posix.O_CREAT | posix.O_WRONLY | posix.O_APPEND elif r.op_id == Id.Redir_Less: # < mode = posix.O_RDONLY elif r.op_id == Id.Redir_LessGreat: # <> mode = posix.O_CREAT | posix.O_RDWR else: raise NotImplementedError(r.op_id) # NOTE: 0666 is affected by umask, all shells use it. try: open_fd = posix.open(arg.filename, mode, 0o666) except OSError as e: self.errfmt.Print_("Can't open %r: %s" % (arg.filename, pyutil.strerror(e)), span_id=r.op_spid) raise # redirect failed new_fd = self._PushDup(open_fd, r.loc) if new_fd != NO_FD: posix.close(open_fd) # Now handle &> and &>> and their variants. These pairs are the same: # # stdout_stderr.py &> out-err.txt # stdout_stderr.py > out-err.txt 2>&1 # # stdout_stderr.py 3&> out-err.txt # stdout_stderr.py 3> out-err.txt 2>&3 # # Ditto for {fd}> and {fd}&> if r.op_id in (Id.Redir_AndGreat, Id.Redir_AndDGreat): self._PushDup(new_fd, redir_loc.Fd(2)) elif case(redirect_arg_e.CopyFd): # e.g. echo hi 1>&2 arg = cast(redirect_arg__CopyFd, UP_arg) if r.op_id == Id.Redir_GreatAnd: # 1>&2 self._PushDup(arg.target_fd, r.loc) elif r.op_id == Id.Redir_LessAnd: # 0<&5 # The only difference between >& and <& is the default file # descriptor argument. self._PushDup(arg.target_fd, r.loc) else: raise NotImplementedError() elif case(redirect_arg_e.MoveFd): # e.g. echo hi 5>&6- arg = cast(redirect_arg__MoveFd, UP_arg) new_fd = self._PushDup(arg.target_fd, r.loc) if new_fd != NO_FD: posix.close(arg.target_fd) UP_loc = r.loc if r.loc.tag_() == redir_loc_e.Fd: fd = cast(redir_loc__Fd, UP_loc).fd else: fd = NO_FD self.cur_frame.saved.append(_RedirFrame(new_fd, fd, False)) elif case(redirect_arg_e.CloseFd): # e.g. echo hi 5>&- self._PushCloseFd(r.loc) elif case(redirect_arg_e.HereDoc): arg = cast(redirect_arg__HereDoc, UP_arg) # NOTE: Do these descriptors have to be moved out of the range 0-9? read_fd, write_fd = posix.pipe() self._PushDup(read_fd, r.loc) # stdin is now the pipe # We can't close like we do in the filename case above? The writer can # get a "broken pipe". self._PushClose(read_fd) thunk = _HereDocWriterThunk(write_fd, arg.body) # TODO: Use PIPE_SIZE to save a process in the case of small here docs, # which are the common case. (dash does this.) start_process = True #start_process = False if start_process: here_proc = Process(thunk, self.job_state) # NOTE: we could close the read pipe here, but it doesn't really # matter because we control the code. _ = here_proc.Start() #log('Started %s as %d', here_proc, pid) self._PushWait(here_proc, waiter) # Now that we've started the child, close it in the parent. posix.close(write_fd) else: posix.write(write_fd, arg.body) posix.close(write_fd)
def Run(self, cmd_val): arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # skip 'json' action, action_spid = arg_r.Peek2() if action is None: raise error.Usage(_JSON_ACTION_ERROR) arg_r.Next() if action == 'write': arg, _ = JSON_WRITE_SPEC.Parse(arg_r) # GetVar() of each name and print it. for var_name in arg_r.Rest(): if var_name.startswith(':'): var_name = var_name[1:] val = self.mem.GetVar(var_name) with tagswitch(val) as case: if case(value_e.Undef): # TODO: blame the right span_id self.errfmt.Print("no variable named %r is defined", var_name) return 1 elif case(value_e.Str): obj = val.s elif case(value_e.MaybeStrArray): obj = val.strs elif case(value_e.AssocArray): obj = val.d elif case(value_e.Obj): obj = val.obj else: raise AssertionError(val) if arg.pretty: indent = arg.indent extra_newline = False else: # How yajl works: if indent is -1, then everything is on one line. indent = -1 extra_newline = True j = yajl.dump(obj, sys.stdout, indent=indent) if extra_newline: sys.stdout.write('\n') # TODO: Accept a block. They aren't hooked up yet. if cmd_val.block: # TODO: flatten value.{Str,Obj} into a flat dict? namespace = self.cmd_ev.EvalBlock(cmd_val.block) print(yajl.dump(namespace)) elif action == 'read': arg, _ = JSON_READ_SPEC.Parse(arg_r) # TODO: # Respect -validate=F var_name, name_spid = arg_r.ReadRequired2("expected variable name") if var_name.startswith(':'): var_name = var_name[1:] if not match.IsValidVarName(var_name): raise error.Usage('got invalid variable name %r' % var_name, span_id=name_spid) try: # Use a global _STDIN, because we get EBADF on a redirect if we use a # local. A Py_DECREF closes the file, which we don't want, because the # redirect is responsible for freeing it. # # https://github.com/oilshell/oil/issues/675 # # TODO: write a better binding like yajl.readfd() # # It should use streaming like here: # https://lloyd.github.io/yajl/ obj = yajl.load(_STDIN) except ValueError as e: self.errfmt.Print('json read: %s', e, span_id=action_spid) return 1 self.mem.SetVar(sh_lhs_expr.Name(var_name), value.Obj(obj), scope_e.LocalOnly) else: raise error.Usage(_JSON_ACTION_ERROR, span_id=action_spid) return 0