2016-05-02 19:31:33 +00:00
|
|
|
// Provides the "linkage" between a parse_node_tree_t and actual execution structures (job_t, etc.)
|
|
|
|
//
|
|
|
|
// A note on error handling: fish has two kind of errors, fatal parse errors non-fatal runtime
|
|
|
|
// errors. A fatal error prevents execution of the entire file, while a non-fatal error skips that
|
|
|
|
// job.
|
|
|
|
//
|
|
|
|
// Non-fatal errors are printed as soon as they are encountered; otherwise you would have to wait
|
|
|
|
// for the execution to finish to see them.
|
2016-05-18 22:30:21 +00:00
|
|
|
#include "config.h" // IWYU pragma: keep
|
|
|
|
|
2019-10-13 22:50:48 +00:00
|
|
|
#include "parse_execution.h"
|
|
|
|
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <errno.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <termios.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <wctype.h>
|
2016-07-06 05:33:32 +00:00
|
|
|
|
|
|
|
#include <algorithm>
|
2019-10-13 22:50:48 +00:00
|
|
|
#include <cwchar>
|
2016-04-21 06:00:54 +00:00
|
|
|
#include <memory>
|
2016-05-02 19:31:33 +00:00
|
|
|
#include <string>
|
2017-02-11 02:47:02 +00:00
|
|
|
#include <type_traits>
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <vector>
|
2016-04-21 06:00:54 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
#include "builtin.h"
|
2017-06-16 00:57:37 +00:00
|
|
|
#include "builtin_function.h"
|
2016-05-02 19:31:33 +00:00
|
|
|
#include "common.h"
|
|
|
|
#include "complete.h"
|
2015-07-25 15:14:25 +00:00
|
|
|
#include "env.h"
|
|
|
|
#include "event.h"
|
2016-05-02 19:31:33 +00:00
|
|
|
#include "exec.h"
|
2013-12-24 21:17:24 +00:00
|
|
|
#include "expand.h"
|
2019-05-27 22:56:53 +00:00
|
|
|
#include "flog.h"
|
2015-07-25 15:14:25 +00:00
|
|
|
#include "function.h"
|
2016-04-21 06:00:54 +00:00
|
|
|
#include "io.h"
|
2017-09-09 04:14:26 +00:00
|
|
|
#include "maybe.h"
|
2016-04-21 06:00:54 +00:00
|
|
|
#include "parse_constants.h"
|
2016-05-02 19:31:33 +00:00
|
|
|
#include "parse_util.h"
|
|
|
|
#include "parser.h"
|
|
|
|
#include "path.h"
|
2016-04-21 06:00:54 +00:00
|
|
|
#include "proc.h"
|
2016-05-02 19:31:33 +00:00
|
|
|
#include "reader.h"
|
2019-12-20 04:41:53 +00:00
|
|
|
#include "timer.h"
|
2018-01-20 19:58:57 +00:00
|
|
|
#include "tnode.h"
|
2018-01-20 21:14:29 +00:00
|
|
|
#include "tokenizer.h"
|
2019-10-19 01:08:22 +00:00
|
|
|
#include "trace.h"
|
2016-05-02 19:31:33 +00:00
|
|
|
#include "util.h"
|
|
|
|
#include "wildcard.h"
|
|
|
|
#include "wutil.h"
|
2013-12-24 21:17:24 +00:00
|
|
|
|
2018-01-14 09:17:57 +00:00
|
|
|
namespace g = grammar;
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
/// These are the specific statement types that support redirections.
|
2018-01-22 20:22:42 +00:00
|
|
|
static constexpr bool type_is_redirectable_block(parse_token_type_t type) {
|
|
|
|
return type == symbol_block_statement || type == symbol_if_statement ||
|
|
|
|
type == symbol_switch_statement;
|
|
|
|
}
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
static bool specific_statement_type_is_redirectable_block(const parse_node_t &node) {
|
2018-01-22 20:22:42 +00:00
|
|
|
return type_is_redirectable_block(node.type);
|
2014-01-07 18:45:36 +00:00
|
|
|
}
|
2013-12-24 21:17:24 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
/// Get the name of a redirectable block, for profiling purposes.
|
|
|
|
static wcstring profiling_cmd_name_for_redirectable_block(const parse_node_t &node,
|
|
|
|
const parse_node_tree_t &tree,
|
|
|
|
const wcstring &src) {
|
2014-02-09 22:04:43 +00:00
|
|
|
assert(specific_statement_type_is_redirectable_block(node));
|
|
|
|
assert(node.has_source());
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get the source for the block, and cut it at the next statement terminator.
|
2014-02-09 22:04:43 +00:00
|
|
|
const size_t src_start = node.source_start;
|
|
|
|
|
2018-01-16 06:13:37 +00:00
|
|
|
auto term = tree.find_child<g::end_command>(node);
|
|
|
|
assert(term.has_source() && term.source_range()->start >= src_start);
|
2019-01-16 21:48:25 +00:00
|
|
|
size_t src_len = term.source_range()->start - src_start;
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2014-02-09 22:04:43 +00:00
|
|
|
wcstring result = wcstring(src, src_start, src_len);
|
|
|
|
result.append(L"...");
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-10-14 22:45:40 +00:00
|
|
|
/// Get a redirection from stderr to stdout (i.e. 2>&1).
|
2019-12-13 00:44:24 +00:00
|
|
|
static redirection_spec_t get_stderr_merge() {
|
|
|
|
const wchar_t *stdout_fileno_str = L"1";
|
|
|
|
return redirection_spec_t{STDERR_FILENO, redirection_mode_t::fd, stdout_fileno_str};
|
2019-10-14 22:45:40 +00:00
|
|
|
}
|
|
|
|
|
2018-11-04 07:58:44 +00:00
|
|
|
parse_execution_context_t::parse_execution_context_t(parsed_source_ref_t pstree, parser_t *p,
|
2020-01-16 01:14:47 +00:00
|
|
|
const operation_context_t &ctx,
|
2019-12-08 21:03:42 +00:00
|
|
|
job_lineage_t lineage)
|
2020-01-16 01:14:47 +00:00
|
|
|
: pstree(std::move(pstree)), parser(p), ctx(ctx), lineage(std::move(lineage)) {}
|
2013-12-24 21:17:24 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Utilities
|
2013-12-24 21:17:24 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
wcstring parse_execution_context_t::get_source(const parse_node_t &node) const {
|
2017-12-22 22:40:15 +00:00
|
|
|
return node.get_source(pstree->src);
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
|
2018-01-14 09:26:28 +00:00
|
|
|
tnode_t<g::plain_statement> parse_execution_context_t::infinite_recursive_statement_in_job_list(
|
|
|
|
tnode_t<g::job_list> job_list, wcstring *out_func_name) const {
|
2016-05-02 19:31:33 +00:00
|
|
|
// This is a bit fragile. It is a test to see if we are inside of function call, but not inside
|
|
|
|
// a block in that function call. If, in the future, the rules for what block scopes are pushed
|
|
|
|
// on function invocation changes, then this check will break.
|
2014-01-01 23:29:56 +00:00
|
|
|
const block_t *current = parser->block_at_index(0), *parent = parser->block_at_index(1);
|
2016-05-02 19:31:33 +00:00
|
|
|
bool is_within_function_call =
|
2019-12-22 23:37:14 +00:00
|
|
|
(current && parent && current->type() == block_type_t::top && parent->is_function_call());
|
2016-05-02 19:31:33 +00:00
|
|
|
if (!is_within_function_call) {
|
2018-01-13 23:36:14 +00:00
|
|
|
return {};
|
2014-01-01 23:29:56 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-11-10 20:36:46 +00:00
|
|
|
// Get the function name of the immediate block.
|
|
|
|
const wcstring &forbidden_function_name = parent->function_name;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get the first job in the job list.
|
2018-03-03 02:09:16 +00:00
|
|
|
tnode_t<g::job> first_job = job_list.try_get_child<g::job_conjunction, 1>().child<0>();
|
2018-01-14 09:26:28 +00:00
|
|
|
if (!first_job) {
|
2018-01-13 23:36:14 +00:00
|
|
|
return {};
|
2014-01-01 23:29:56 +00:00
|
|
|
}
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Here's the statement node we find that's infinite recursive.
|
2018-01-13 23:36:14 +00:00
|
|
|
tnode_t<grammar::plain_statement> infinite_recursive_statement;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-12-21 10:45:07 +00:00
|
|
|
// Ignore the jobs variable assigment and "time" prefixes.
|
|
|
|
tnode_t<g::statement> statement = first_job.child<2>();
|
|
|
|
tnode_t<g::job_continuation> continuation = first_job.child<3>();
|
2018-09-11 05:29:52 +00:00
|
|
|
const null_environment_t nullenv{};
|
2018-01-16 00:33:36 +00:00
|
|
|
while (statement) {
|
2019-12-21 10:45:07 +00:00
|
|
|
// Get the list of plain statements.
|
|
|
|
// Ignore statements with decorations like 'builtin' or 'command', since those
|
|
|
|
// are not infinite recursion. In particular that is what enables 'wrapper functions'.
|
2018-01-16 00:33:36 +00:00
|
|
|
tnode_t<g::plain_statement> plain_statement =
|
|
|
|
statement.try_get_child<g::decorated_statement, 0>()
|
|
|
|
.try_get_child<g::plain_statement, 0>();
|
|
|
|
if (plain_statement) {
|
|
|
|
maybe_t<wcstring> cmd = command_for_plain_statement(plain_statement, pstree->src);
|
2019-04-25 18:22:17 +00:00
|
|
|
if (cmd &&
|
2020-01-16 01:14:47 +00:00
|
|
|
expand_one(*cmd, {expand_flag::skip_cmdsubst, expand_flag::skip_variables}, ctx) &&
|
2018-01-16 00:33:36 +00:00
|
|
|
cmd == forbidden_function_name) {
|
|
|
|
// This is it.
|
|
|
|
infinite_recursive_statement = plain_statement;
|
2019-11-19 02:34:50 +00:00
|
|
|
if (out_func_name != nullptr) {
|
2018-01-16 00:33:36 +00:00
|
|
|
*out_func_name = forbidden_function_name;
|
|
|
|
}
|
|
|
|
break;
|
2014-01-01 23:29:56 +00:00
|
|
|
}
|
|
|
|
}
|
2018-01-16 00:33:36 +00:00
|
|
|
statement = continuation.next_in_list<g::statement>();
|
2014-01-01 23:29:56 +00:00
|
|
|
}
|
2018-01-16 00:33:36 +00:00
|
|
|
|
2014-01-01 23:29:56 +00:00
|
|
|
return infinite_recursive_statement;
|
|
|
|
}
|
|
|
|
|
2019-03-24 19:29:25 +00:00
|
|
|
process_type_t parse_execution_context_t::process_type_for_command(
|
2018-01-15 23:15:45 +00:00
|
|
|
tnode_t<grammar::plain_statement> statement, const wcstring &cmd) const {
|
2019-03-24 19:29:25 +00:00
|
|
|
enum process_type_t process_type = process_type_t::external;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Determine the process type, which depends on the statement decoration (command, builtin,
|
|
|
|
// etc).
|
2018-01-15 23:15:45 +00:00
|
|
|
enum parse_statement_decoration_t decoration = get_decoration(statement);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-04-11 03:38:19 +00:00
|
|
|
switch (decoration) {
|
|
|
|
case parse_statement_decoration_exec:
|
|
|
|
process_type = process_type_t::exec;
|
|
|
|
break;
|
|
|
|
case parse_statement_decoration_command:
|
|
|
|
process_type = process_type_t::external;
|
|
|
|
break;
|
|
|
|
case parse_statement_decoration_builtin:
|
|
|
|
process_type = process_type_t::builtin;
|
|
|
|
break;
|
|
|
|
case parse_statement_decoration_none:
|
2019-05-05 03:20:52 +00:00
|
|
|
if (function_exists(cmd, *parser)) {
|
2019-04-11 03:38:19 +00:00
|
|
|
process_type = process_type_t::function;
|
|
|
|
} else if (builtin_exists(cmd)) {
|
|
|
|
process_type = process_type_t::builtin;
|
|
|
|
} else {
|
|
|
|
process_type = process_type_t::external;
|
|
|
|
}
|
|
|
|
break;
|
2013-12-31 22:37:37 +00:00
|
|
|
}
|
2019-04-11 03:38:19 +00:00
|
|
|
|
2013-12-31 22:37:37 +00:00
|
|
|
return process_type;
|
|
|
|
}
|
2013-12-24 21:17:24 +00:00
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
maybe_t<end_execution_reason_t> parse_execution_context_t::check_end_execution() const {
|
2016-05-02 19:31:33 +00:00
|
|
|
if (shell_is_exiting()) {
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::cancelled;
|
2016-05-04 22:19:47 +00:00
|
|
|
}
|
2020-01-14 23:20:04 +00:00
|
|
|
if (parser && parser->cancellation_signal) {
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::cancelled;
|
2016-05-04 22:19:47 +00:00
|
|
|
}
|
2019-05-20 02:29:25 +00:00
|
|
|
const auto &ld = parser->libdata();
|
|
|
|
if (ld.returning) {
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::control_flow;
|
2013-12-30 00:23:26 +00:00
|
|
|
}
|
2019-05-20 02:29:25 +00:00
|
|
|
if (ld.loop_status != loop_status_t::normals) {
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::control_flow;
|
2017-01-21 22:15:03 +00:00
|
|
|
}
|
2019-12-18 02:10:29 +00:00
|
|
|
return none();
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
/// Return whether the job contains a single statement, of block type, with no redirections.
|
2018-01-16 00:00:19 +00:00
|
|
|
bool parse_execution_context_t::job_is_simple_block(tnode_t<g::job> job_node) const {
|
2019-12-21 10:45:07 +00:00
|
|
|
tnode_t<g::statement> statement = job_node.child<2>();
|
2014-01-07 18:45:36 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Must be no pipes.
|
2019-12-21 10:45:07 +00:00
|
|
|
if (job_node.child<3>().try_get_child<g::tok_pipe, 0>()) {
|
2014-01-07 18:45:36 +00:00
|
|
|
return false;
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2018-01-22 20:22:42 +00:00
|
|
|
// Helper to check if an argument or redirection list has no redirections.
|
|
|
|
auto is_empty = [](tnode_t<g::arguments_or_redirections_list> lst) -> bool {
|
2018-01-22 21:18:34 +00:00
|
|
|
return !lst.next_in_list<g::redirection>();
|
2018-01-22 20:22:42 +00:00
|
|
|
};
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2018-01-22 20:22:42 +00:00
|
|
|
// Check if we're a block statement with redirections. We do it this obnoxious way to preserve
|
|
|
|
// type safety (in case we add more specific statement types).
|
|
|
|
const parse_node_t &specific_statement = statement.get_child_node<0>();
|
|
|
|
switch (specific_statement.type) {
|
|
|
|
case symbol_block_statement:
|
|
|
|
return is_empty(statement.require_get_child<g::block_statement, 0>().child<3>());
|
|
|
|
case symbol_switch_statement:
|
|
|
|
return is_empty(statement.require_get_child<g::switch_statement, 0>().child<5>());
|
|
|
|
case symbol_if_statement:
|
|
|
|
return is_empty(statement.require_get_child<g::if_statement, 0>().child<3>());
|
2018-03-03 02:09:16 +00:00
|
|
|
case symbol_not_statement:
|
2018-01-22 20:22:42 +00:00
|
|
|
case symbol_decorated_statement:
|
|
|
|
// not block statements
|
|
|
|
return false;
|
|
|
|
default:
|
|
|
|
assert(0 && "Unexpected child block type");
|
|
|
|
return false;
|
|
|
|
}
|
2014-01-07 18:45:36 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::run_if_statement(
|
|
|
|
tnode_t<g::if_statement> statement, const block_t *associated_block) {
|
|
|
|
end_execution_reason_t result = end_execution_reason_t::ok;
|
2013-12-27 09:38:43 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// We have a sequence of if clauses, with a final else, resulting in a single job list that we
|
|
|
|
// execute.
|
2018-01-14 10:02:02 +00:00
|
|
|
tnode_t<g::job_list> job_list_to_execute;
|
|
|
|
tnode_t<g::if_clause> if_clause = statement.child<0>();
|
|
|
|
tnode_t<g::else_clause> else_clause = statement.child<1>();
|
2019-10-19 01:08:22 +00:00
|
|
|
|
|
|
|
// We start with the 'if'.
|
|
|
|
trace_if_enabled(*parser, L"if");
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
for (;;) {
|
2019-12-18 02:10:29 +00:00
|
|
|
if (auto ret = check_end_execution()) {
|
|
|
|
result = *ret;
|
2013-12-31 22:37:37 +00:00
|
|
|
break;
|
|
|
|
}
|
2016-05-02 19:31:33 +00:00
|
|
|
|
|
|
|
// An if condition has a job and a "tail" of andor jobs, e.g. "foo ; and bar; or baz".
|
2018-03-02 18:23:57 +00:00
|
|
|
tnode_t<g::job_conjunction> condition_head = if_clause.child<1>();
|
2018-01-14 10:02:02 +00:00
|
|
|
tnode_t<g::andor_job_list> condition_boolean_tail = if_clause.child<3>();
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-24 01:34:46 +00:00
|
|
|
// Check the condition and the tail. We treat end_execution_reason_t::error here as failure,
|
|
|
|
// in accordance with historic behavior.
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t cond_ret = run_job_conjunction(condition_head, associated_block);
|
|
|
|
if (cond_ret == end_execution_reason_t::ok) {
|
2018-03-31 21:57:24 +00:00
|
|
|
cond_ret = run_job_list(condition_boolean_tail, associated_block);
|
2015-12-19 22:45:45 +00:00
|
|
|
}
|
2016-05-02 19:31:33 +00:00
|
|
|
const bool take_branch =
|
2020-01-24 00:45:00 +00:00
|
|
|
(cond_ret == end_execution_reason_t::ok) && parser->get_last_status() == EXIT_SUCCESS;
|
2013-12-31 22:37:37 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
if (take_branch) {
|
|
|
|
// Condition succeeded.
|
2018-01-14 10:02:02 +00:00
|
|
|
job_list_to_execute = if_clause.child<4>();
|
2013-12-27 09:38:43 +00:00
|
|
|
break;
|
2018-01-14 10:02:02 +00:00
|
|
|
}
|
|
|
|
auto else_cont = else_clause.try_get_child<g::else_continuation, 1>();
|
|
|
|
if (!else_cont) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// 'if' condition failed, no else clause, return 0, we're done.
|
2019-05-12 21:00:44 +00:00
|
|
|
parser->set_last_statuses(statuses_t::just(STATUS_CMD_OK));
|
2013-12-27 09:38:43 +00:00
|
|
|
break;
|
2016-05-02 19:31:33 +00:00
|
|
|
} else {
|
|
|
|
// We have an 'else continuation' (either else-if or else).
|
2018-01-14 10:02:02 +00:00
|
|
|
if (auto maybe_if_clause = else_cont.try_get_child<g::if_clause, 0>()) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// it's an 'else if', go to the next one.
|
2015-12-15 22:59:03 +00:00
|
|
|
if_clause = maybe_if_clause;
|
2018-01-14 10:02:02 +00:00
|
|
|
else_clause = else_cont.try_get_child<g::else_clause, 1>();
|
|
|
|
assert(else_clause && "Expected to have an else clause");
|
2019-10-19 01:08:22 +00:00
|
|
|
trace_if_enabled(*parser, L"else if");
|
2016-05-02 19:31:33 +00:00
|
|
|
} else {
|
|
|
|
// It's the final 'else', we're done.
|
2018-01-14 10:02:02 +00:00
|
|
|
job_list_to_execute = else_cont.try_get_child<g::job_list, 1>();
|
|
|
|
assert(job_list_to_execute && "Should have a job list");
|
2019-10-19 01:08:22 +00:00
|
|
|
trace_if_enabled(*parser, L"else");
|
2013-12-27 09:38:43 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Execute any job list we got.
|
2018-01-16 00:08:06 +00:00
|
|
|
if (job_list_to_execute) {
|
2019-05-19 21:40:06 +00:00
|
|
|
block_t *ib = parser->push_block(block_t::if_block());
|
2018-01-16 00:08:06 +00:00
|
|
|
run_job_list(job_list_to_execute, ib);
|
2019-12-18 02:10:29 +00:00
|
|
|
if (auto ret = check_end_execution()) {
|
|
|
|
result = *ret;
|
2018-03-31 21:57:24 +00:00
|
|
|
}
|
|
|
|
parser->pop_block(ib);
|
2016-05-02 19:31:33 +00:00
|
|
|
} else {
|
2019-10-19 01:36:03 +00:00
|
|
|
// No job list means no successful conditions, so return 0 (issue #1443).
|
2019-05-12 21:00:44 +00:00
|
|
|
parser->set_last_statuses(statuses_t::just(STATUS_CMD_OK));
|
2015-01-21 07:44:49 +00:00
|
|
|
}
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2019-10-19 01:08:22 +00:00
|
|
|
trace_if_enabled(*parser, L"end if");
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// It's possible there's a last-minute cancellation (issue #1297).
|
2019-12-18 02:10:29 +00:00
|
|
|
if (auto ret = check_end_execution()) {
|
|
|
|
result = *ret;
|
2014-02-12 09:39:06 +00:00
|
|
|
}
|
2013-12-27 09:38:43 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Otherwise, take the exit status of the job list. Reversal of issue #1061.
|
2013-12-31 22:37:37 +00:00
|
|
|
return result;
|
2013-12-27 09:38:43 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::run_begin_statement(
|
|
|
|
tnode_t<g::job_list> contents) {
|
2017-01-21 23:35:35 +00:00
|
|
|
// Basic begin/end block. Push a scope block, run jobs, pop it
|
2019-10-19 01:08:22 +00:00
|
|
|
trace_if_enabled(*parser, L"begin");
|
2019-12-22 23:37:14 +00:00
|
|
|
block_t *sb = parser->push_block(block_t::scope_block(block_type_t::begin));
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t ret = run_job_list(contents, sb);
|
2013-12-27 09:38:43 +00:00
|
|
|
parser->pop_block(sb);
|
2019-10-19 01:08:22 +00:00
|
|
|
trace_if_enabled(*parser, L"end begin");
|
2013-12-31 22:37:37 +00:00
|
|
|
return ret;
|
2014-01-15 09:40:40 +00:00
|
|
|
}
|
2013-12-27 09:38:43 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Define a function.
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::run_function_statement(
|
2020-01-03 22:40:28 +00:00
|
|
|
tnode_t<grammar::block_statement> statement, tnode_t<grammar::function_header> header) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get arguments.
|
2018-01-15 23:15:45 +00:00
|
|
|
wcstring_list_t arguments;
|
|
|
|
argument_node_list_t arg_nodes = header.descendants<g::argument>();
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t result =
|
|
|
|
this->expand_arguments_from_nodes(arg_nodes, &arguments, failglob);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
if (result != end_execution_reason_t::ok) {
|
2016-10-31 03:26:10 +00:00
|
|
|
return result;
|
|
|
|
}
|
2019-10-19 01:08:22 +00:00
|
|
|
trace_if_enabled(*parser, L"function", arguments);
|
2017-07-27 03:17:04 +00:00
|
|
|
io_streams_t streams(0); // no limit on the amount of output from builtin_function()
|
2020-01-03 22:40:28 +00:00
|
|
|
int err = builtin_function(*parser, streams, arguments, pstree, statement);
|
2019-05-12 21:00:44 +00:00
|
|
|
parser->set_last_statuses(statuses_t::just(err));
|
2016-10-31 03:26:10 +00:00
|
|
|
|
2019-07-21 20:53:05 +00:00
|
|
|
wcstring errtext = streams.err.contents();
|
|
|
|
if (!errtext.empty()) {
|
2020-01-24 01:34:46 +00:00
|
|
|
return this->report_error(err, header, L"%ls", errtext.c_str());
|
2013-12-27 09:38:43 +00:00
|
|
|
}
|
2013-12-31 22:37:37 +00:00
|
|
|
return result;
|
2013-12-27 09:38:43 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::run_block_statement(
|
|
|
|
tnode_t<g::block_statement> statement, const block_t *associated_block) {
|
2018-01-14 09:42:58 +00:00
|
|
|
tnode_t<g::block_header> bheader = statement.child<0>();
|
|
|
|
tnode_t<g::job_list> contents = statement.child<1>();
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t ret = end_execution_reason_t::ok;
|
2018-01-14 09:42:58 +00:00
|
|
|
if (auto header = bheader.try_get_child<g::for_header, 0>()) {
|
|
|
|
ret = run_for_statement(header, contents);
|
|
|
|
} else if (auto header = bheader.try_get_child<g::while_header, 0>()) {
|
2018-03-31 21:57:24 +00:00
|
|
|
ret = run_while_statement(header, contents, associated_block);
|
2018-01-14 09:42:58 +00:00
|
|
|
} else if (auto header = bheader.try_get_child<g::function_header, 0>()) {
|
2020-01-03 22:40:28 +00:00
|
|
|
ret = run_function_statement(statement, header);
|
2018-01-14 09:42:58 +00:00
|
|
|
} else if (auto header = bheader.try_get_child<g::begin_header, 0>()) {
|
2018-01-20 22:05:34 +00:00
|
|
|
ret = run_begin_statement(contents);
|
2018-01-14 09:42:58 +00:00
|
|
|
} else {
|
2019-05-30 09:54:09 +00:00
|
|
|
FLOGF(error, L"Unexpected block header: %ls\n", bheader.node()->describe().c_str());
|
2018-01-14 09:42:58 +00:00
|
|
|
PARSER_DIE();
|
2013-12-27 09:38:43 +00:00
|
|
|
}
|
2013-12-31 22:37:37 +00:00
|
|
|
return ret;
|
2013-12-27 09:38:43 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::run_for_statement(
|
2018-01-14 09:47:09 +00:00
|
|
|
tnode_t<grammar::for_header> header, tnode_t<grammar::job_list> block_contents) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get the variable name: `for var_name in ...`. We expand the variable name. It better result
|
|
|
|
// in just one.
|
2018-01-14 09:47:09 +00:00
|
|
|
tnode_t<g::tok_string> var_name_node = header.child<1>();
|
2014-02-22 02:20:51 +00:00
|
|
|
wcstring for_var_name = get_source(var_name_node);
|
2020-01-16 01:14:47 +00:00
|
|
|
if (!expand_one(for_var_name, expand_flags_t{}, ctx)) {
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_error(STATUS_EXPAND_ERROR, var_name_node,
|
|
|
|
FAILED_EXPANSION_VARIABLE_NAME_ERR_MSG, for_var_name.c_str());
|
2014-02-22 02:20:51 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get the contents to iterate over.
|
2018-01-15 23:15:45 +00:00
|
|
|
wcstring_list_t arguments;
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t ret = this->expand_arguments_from_nodes(
|
|
|
|
get_argument_nodes(header.child<3>()), &arguments, nullglob);
|
|
|
|
if (ret != end_execution_reason_t::ok) {
|
2014-05-31 19:41:27 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-08 08:03:34 +00:00
|
|
|
auto var = parser->vars().get(for_var_name, ENV_DEFAULT);
|
|
|
|
if (var && var->read_only()) {
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_error(STATUS_INVALID_ARGS, var_name_node,
|
|
|
|
L"You cannot use read-only variable '%ls' in a for loop",
|
|
|
|
for_var_name.c_str());
|
2020-01-08 08:03:34 +00:00
|
|
|
}
|
|
|
|
int retval;
|
|
|
|
if (var) {
|
|
|
|
retval = parser->vars().set(for_var_name, ENV_LOCAL | ENV_USER, var->as_list());
|
|
|
|
} else {
|
|
|
|
retval = parser->vars().set_empty(for_var_name, ENV_LOCAL | ENV_USER);
|
2017-09-09 04:14:26 +00:00
|
|
|
}
|
2020-01-08 08:03:34 +00:00
|
|
|
assert(retval == ENV_OK);
|
2017-09-09 04:14:26 +00:00
|
|
|
|
2019-04-08 18:22:21 +00:00
|
|
|
if (!valid_var_name(for_var_name)) {
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_error(STATUS_INVALID_ARGS, var_name_node, BUILTIN_ERR_VARNAME, L"for",
|
|
|
|
for_var_name.c_str());
|
2019-04-08 18:22:21 +00:00
|
|
|
}
|
|
|
|
|
2019-10-19 01:08:22 +00:00
|
|
|
trace_if_enabled(*parser, L"for", arguments);
|
2019-05-19 21:40:06 +00:00
|
|
|
block_t *fb = parser->push_block(block_t::for_block());
|
2013-12-27 09:38:43 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Now drive the for loop.
|
2018-01-15 23:15:45 +00:00
|
|
|
for (const wcstring &val : arguments) {
|
2019-12-18 02:10:29 +00:00
|
|
|
if (auto reason = check_end_execution()) {
|
|
|
|
ret = *reason;
|
2014-01-01 08:04:02 +00:00
|
|
|
break;
|
2013-12-31 22:37:37 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2018-09-14 07:36:26 +00:00
|
|
|
int retval = parser->vars().set_one(for_var_name, ENV_DEFAULT | ENV_USER, val);
|
2017-12-21 21:53:18 +00:00
|
|
|
assert(retval == ENV_OK && "for loop variable should have been successfully set");
|
|
|
|
(void)retval;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-05-19 06:12:34 +00:00
|
|
|
auto &ld = parser->libdata();
|
|
|
|
ld.loop_status = loop_status_t::normals;
|
2013-12-29 00:18:38 +00:00
|
|
|
this->run_job_list(block_contents, fb);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
if (check_end_execution() == end_execution_reason_t::control_flow) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Handle break or continue.
|
2019-05-19 06:12:34 +00:00
|
|
|
bool do_break = (ld.loop_status == loop_status_t::breaks);
|
|
|
|
ld.loop_status = loop_status_t::normals;
|
|
|
|
if (do_break) {
|
2013-12-30 00:23:26 +00:00
|
|
|
break;
|
|
|
|
}
|
2013-12-29 06:52:06 +00:00
|
|
|
}
|
2013-12-27 09:38:43 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2014-01-07 18:45:36 +00:00
|
|
|
parser->pop_block(fb);
|
2019-10-19 01:08:22 +00:00
|
|
|
trace_if_enabled(*parser, L"end for");
|
2013-12-31 22:37:37 +00:00
|
|
|
return ret;
|
2013-12-27 09:38:43 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::run_switch_statement(
|
2018-01-14 10:30:18 +00:00
|
|
|
tnode_t<grammar::switch_statement> statement) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get the switch variable.
|
2018-01-14 10:30:18 +00:00
|
|
|
tnode_t<grammar::argument> switch_value_n = statement.child<1>();
|
|
|
|
const wcstring switch_value = get_source(switch_value_n);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Expand it. We need to offset any errors by the position of the string.
|
2020-01-16 00:13:41 +00:00
|
|
|
completion_list_t switch_values_expanded;
|
2014-03-22 00:13:33 +00:00
|
|
|
parse_error_list_t errors;
|
2020-01-16 01:14:47 +00:00
|
|
|
auto expand_ret = expand_string(switch_value, &switch_values_expanded,
|
|
|
|
expand_flag::no_descriptions, ctx, &errors);
|
2018-01-14 10:30:18 +00:00
|
|
|
parse_error_offset_source_start(&errors, switch_value_n.source_range()->start);
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2020-01-24 01:34:46 +00:00
|
|
|
switch (expand_ret.result) {
|
2020-01-22 19:46:02 +00:00
|
|
|
case expand_result_t::error:
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_errors(expand_ret.status, errors);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-22 19:58:10 +00:00
|
|
|
case expand_result_t::cancel:
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::cancelled;
|
2020-01-22 19:58:10 +00:00
|
|
|
|
2020-01-22 19:46:02 +00:00
|
|
|
case expand_result_t::wildcard_no_match:
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_error(STATUS_UNMATCHED_WILDCARD, switch_value_n, WILDCARD_ERR_MSG,
|
|
|
|
get_source(switch_value_n).c_str());
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2020-01-22 19:46:02 +00:00
|
|
|
case expand_result_t::ok:
|
|
|
|
if (switch_values_expanded.size() > 1) {
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_error(STATUS_INVALID_ARGS, switch_value_n,
|
2020-01-22 19:46:02 +00:00
|
|
|
_(L"switch: Expected at most one argument, got %lu\n"),
|
|
|
|
switch_values_expanded.size());
|
|
|
|
}
|
|
|
|
break;
|
2016-10-31 03:26:10 +00:00
|
|
|
}
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2020-01-22 19:46:02 +00:00
|
|
|
// If we expanded to nothing, match the empty string.
|
|
|
|
assert(switch_values_expanded.size() <= 1 && "Should have at most one expansion");
|
|
|
|
wcstring switch_value_expanded = L"";
|
|
|
|
if (!switch_values_expanded.empty()) {
|
|
|
|
switch_value_expanded = std::move(switch_values_expanded.front().completion);
|
|
|
|
}
|
2014-03-28 23:56:44 +00:00
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t result = end_execution_reason_t::ok;
|
2019-05-19 21:40:06 +00:00
|
|
|
block_t *sb = parser->push_block(block_t::switch_block());
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-10-31 03:26:10 +00:00
|
|
|
// Expand case statements.
|
2018-01-14 10:30:18 +00:00
|
|
|
tnode_t<g::case_item_list> case_item_list = statement.child<3>();
|
|
|
|
tnode_t<g::case_item> matching_case_item{};
|
|
|
|
while (auto case_item = case_item_list.next_in_list<g::case_item>()) {
|
2019-12-18 02:10:29 +00:00
|
|
|
if (auto ret = check_end_execution()) {
|
|
|
|
result = *ret;
|
2016-10-31 03:26:10 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-10-31 03:26:10 +00:00
|
|
|
// Expand arguments. A case item list may have a wildcard that fails to expand to
|
|
|
|
// anything. We also report case errors, but don't stop execution; i.e. a case item that
|
|
|
|
// contains an unexpandable process will report and then fail to match.
|
2018-01-15 23:15:45 +00:00
|
|
|
auto arg_nodes = get_argument_nodes(case_item.child<1>());
|
2016-10-31 03:26:10 +00:00
|
|
|
wcstring_list_t case_args;
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t case_result =
|
2018-01-15 23:15:45 +00:00
|
|
|
this->expand_arguments_from_nodes(arg_nodes, &case_args, failglob);
|
2020-01-24 00:45:00 +00:00
|
|
|
if (case_result == end_execution_reason_t::ok) {
|
2018-01-14 10:30:18 +00:00
|
|
|
for (const wcstring &arg : case_args) {
|
2016-10-31 03:26:10 +00:00
|
|
|
// Unescape wildcards so they can be expanded again.
|
|
|
|
wcstring unescaped_arg = parse_util_unescape_wildcards(arg);
|
|
|
|
bool match = wildcard_match(switch_value_expanded, unescaped_arg);
|
|
|
|
|
|
|
|
// If this matched, we're done.
|
|
|
|
if (match) {
|
|
|
|
matching_case_item = case_item;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-03-28 23:56:44 +00:00
|
|
|
}
|
2018-01-14 10:30:18 +00:00
|
|
|
if (matching_case_item) break;
|
2016-10-31 03:26:10 +00:00
|
|
|
}
|
2013-12-27 11:58:42 +00:00
|
|
|
|
2018-01-14 10:30:18 +00:00
|
|
|
if (matching_case_item) {
|
2016-10-31 03:26:10 +00:00
|
|
|
// Success, evaluate the job list.
|
2020-01-24 00:45:00 +00:00
|
|
|
assert(result == end_execution_reason_t::ok && "Expected success");
|
2018-01-14 10:30:18 +00:00
|
|
|
auto job_list = matching_case_item.child<3>();
|
|
|
|
result = this->run_job_list(job_list, sb);
|
2014-03-28 23:56:44 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-10-31 03:26:10 +00:00
|
|
|
parser->pop_block(sb);
|
2013-12-31 22:37:37 +00:00
|
|
|
return result;
|
2013-12-27 09:38:43 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::run_while_statement(
|
|
|
|
tnode_t<grammar::while_header> header, tnode_t<grammar::job_list> contents,
|
|
|
|
const block_t *associated_block) {
|
|
|
|
end_execution_reason_t ret = end_execution_reason_t::ok;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-01-21 00:37:20 +00:00
|
|
|
// "The exit status of the while loop shall be the exit status of the last compound-list-2
|
|
|
|
// executed, or zero if none was executed."
|
|
|
|
// Here are more detailed requirements:
|
|
|
|
// - If we execute the loop body zero times, or the loop body is empty, the status is success.
|
|
|
|
// - An empty loop body is treated as true, both in the loop condition and after loop exit.
|
|
|
|
// - The exit status of the last command is visible in the loop condition. (i.e. do not set the
|
|
|
|
// exit status to true BEFORE executing the loop condition).
|
|
|
|
// We achieve this by restoring the status if the loop condition fails, plus a special
|
|
|
|
// affordance for the first condition.
|
|
|
|
bool first_cond_check = true;
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// The conditions of the while loop.
|
2018-03-02 18:23:57 +00:00
|
|
|
tnode_t<g::job_conjunction> condition_head = header.child<1>();
|
2018-01-14 10:41:37 +00:00
|
|
|
tnode_t<g::andor_job_list> condition_boolean_tail = header.child<3>();
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-10-19 01:08:22 +00:00
|
|
|
trace_if_enabled(*parser, L"while");
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Run while the condition is true.
|
|
|
|
for (;;) {
|
2019-01-21 00:37:20 +00:00
|
|
|
// Save off the exit status if it came from the loop body. We'll restore it if the condition
|
|
|
|
// is false.
|
2019-02-25 09:21:32 +00:00
|
|
|
auto cond_saved_status =
|
2019-05-12 21:00:44 +00:00
|
|
|
first_cond_check ? statuses_t::just(EXIT_SUCCESS) : parser->get_last_statuses();
|
2019-01-21 00:37:20 +00:00
|
|
|
first_cond_check = false;
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Check the condition.
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t cond_ret =
|
|
|
|
this->run_job_conjunction(condition_head, associated_block);
|
|
|
|
if (cond_ret == end_execution_reason_t::ok) {
|
2018-03-31 21:57:24 +00:00
|
|
|
cond_ret = run_job_list(condition_boolean_tail, associated_block);
|
2015-12-19 22:45:45 +00:00
|
|
|
}
|
2016-05-02 19:31:33 +00:00
|
|
|
|
2019-01-21 00:37:20 +00:00
|
|
|
// If the loop condition failed to execute, then exit the loop without modifying the exit
|
|
|
|
// status. If the loop condition executed with a failure status, restore the status and then
|
|
|
|
// exit the loop.
|
2020-01-24 00:45:00 +00:00
|
|
|
if (cond_ret != end_execution_reason_t::ok) {
|
2019-01-21 00:37:20 +00:00
|
|
|
break;
|
2019-05-12 21:00:44 +00:00
|
|
|
} else if (parser->get_last_status() != EXIT_SUCCESS) {
|
|
|
|
parser->set_last_statuses(cond_saved_status);
|
2013-12-31 22:37:37 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Check cancellation.
|
2019-12-18 02:10:29 +00:00
|
|
|
if (auto reason = check_end_execution()) {
|
|
|
|
ret = *reason;
|
2013-12-31 22:37:37 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2018-03-31 21:57:24 +00:00
|
|
|
// Push a while block and then check its cancellation reason.
|
2019-05-19 06:12:34 +00:00
|
|
|
auto &ld = parser->libdata();
|
|
|
|
ld.loop_status = loop_status_t::normals;
|
2019-10-19 01:08:22 +00:00
|
|
|
|
2019-05-19 21:40:06 +00:00
|
|
|
block_t *wb = parser->push_block(block_t::while_block());
|
2018-01-14 10:41:37 +00:00
|
|
|
this->run_job_list(contents, wb);
|
2019-12-18 02:10:29 +00:00
|
|
|
auto cancel_reason = this->check_end_execution();
|
2018-03-31 21:57:24 +00:00
|
|
|
parser->pop_block(wb);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
if (cancel_reason == end_execution_reason_t::control_flow) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Handle break or continue.
|
2019-05-24 23:09:10 +00:00
|
|
|
bool do_break = (ld.loop_status == loop_status_t::breaks);
|
|
|
|
ld.loop_status = loop_status_t::normals;
|
|
|
|
if (do_break) {
|
2013-12-30 00:23:26 +00:00
|
|
|
break;
|
2019-05-24 23:09:10 +00:00
|
|
|
} else {
|
|
|
|
continue;
|
2013-12-30 00:23:26 +00:00
|
|
|
}
|
2013-12-29 06:52:06 +00:00
|
|
|
}
|
2016-05-02 19:31:33 +00:00
|
|
|
|
|
|
|
// no_exec means that fish was invoked with -n or --no-execute. If set, we allow the loop to
|
|
|
|
// not-execute once so its contents can be checked, and then break.
|
2019-05-12 22:04:18 +00:00
|
|
|
if (no_exec()) {
|
2014-07-11 18:28:10 +00:00
|
|
|
break;
|
|
|
|
}
|
2013-12-26 20:24:00 +00:00
|
|
|
}
|
2019-10-19 01:08:22 +00:00
|
|
|
trace_if_enabled(*parser, L"end while");
|
2013-12-31 22:37:37 +00:00
|
|
|
return ret;
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 01:34:46 +00:00
|
|
|
// Reports an error. Always returns end_execution_reason_t::error.
|
|
|
|
end_execution_reason_t parse_execution_context_t::report_error(int status, const parse_node_t &node,
|
2020-01-24 00:45:00 +00:00
|
|
|
const wchar_t *fmt, ...) const {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Create an error.
|
2016-02-28 02:37:59 +00:00
|
|
|
parse_error_list_t error_list = parse_error_list_t(1);
|
|
|
|
parse_error_t *error = &error_list.at(0);
|
|
|
|
error->source_start = node.source_start;
|
|
|
|
error->source_length = node.source_length;
|
2016-05-02 19:31:33 +00:00
|
|
|
error->code = parse_error_syntax; // hackish
|
2016-02-28 02:37:59 +00:00
|
|
|
|
|
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
|
|
error->text = vformat_string(fmt, va);
|
|
|
|
va_end(va);
|
|
|
|
|
2020-01-24 01:34:46 +00:00
|
|
|
return this->report_errors(status, error_list);
|
2014-03-22 00:13:33 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::report_errors(
|
2020-01-24 01:34:46 +00:00
|
|
|
int status, const parse_error_list_t &error_list) const {
|
2020-01-14 23:20:04 +00:00
|
|
|
if (!parser->cancellation_signal) {
|
2016-05-02 19:31:33 +00:00
|
|
|
if (error_list.empty()) {
|
2019-05-29 06:07:04 +00:00
|
|
|
FLOG(error, L"Error reported but no error text found.");
|
2014-03-22 00:13:33 +00:00
|
|
|
}
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get a backtrace.
|
2014-01-09 02:20:55 +00:00
|
|
|
wcstring backtrace_and_desc;
|
2017-12-22 22:40:15 +00:00
|
|
|
parser->get_backtrace(pstree->src, error_list, backtrace_and_desc);
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Print it.
|
2017-01-03 05:11:53 +00:00
|
|
|
if (!should_suppress_stderr_for_tests()) {
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, L"%ls", backtrace_and_desc.c_str());
|
2017-01-03 05:11:53 +00:00
|
|
|
}
|
2013-12-24 21:17:24 +00:00
|
|
|
|
2020-01-24 01:34:46 +00:00
|
|
|
// Mark status.
|
|
|
|
parser->set_last_statuses(statuses_t::just(status));
|
|
|
|
}
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::error;
|
2013-12-27 09:38:43 +00:00
|
|
|
}
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
/// Handle the case of command not found.
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::handle_command_not_found(
|
2018-01-15 20:41:54 +00:00
|
|
|
const wcstring &cmd_str, tnode_t<g::plain_statement> statement, int err_code) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// We couldn't find the specified command. This is a non-fatal error. We want to set the exit
|
|
|
|
// status to 127, which is the standard number used by other shells like bash and zsh.
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
const wchar_t *const cmd = cmd_str.c_str();
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
if (err_code != ENOENT) {
|
2020-01-24 01:34:46 +00:00
|
|
|
return this->report_error(STATUS_NOT_EXECUTABLE, statement,
|
|
|
|
_(L"The file '%ls' is not executable by this user"), cmd);
|
2016-05-02 19:31:33 +00:00
|
|
|
} else {
|
|
|
|
// Handle unrecognized commands with standard command not found handler that can make better
|
|
|
|
// error messages.
|
2013-12-30 00:23:26 +00:00
|
|
|
wcstring_list_t event_args;
|
2015-04-18 19:53:43 +00:00
|
|
|
{
|
2018-01-15 23:15:45 +00:00
|
|
|
auto args = get_argument_nodes(statement.child<1>());
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t arg_result =
|
2018-01-15 23:15:45 +00:00
|
|
|
this->expand_arguments_from_nodes(args, &event_args, failglob);
|
2015-04-18 19:53:43 +00:00
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
if (arg_result != end_execution_reason_t::ok) {
|
2015-04-18 19:53:43 +00:00
|
|
|
return arg_result;
|
|
|
|
}
|
|
|
|
|
|
|
|
event_args.insert(event_args.begin(), cmd_str);
|
|
|
|
}
|
|
|
|
|
2019-06-03 09:31:13 +00:00
|
|
|
event_fire_generic(*parser, L"fish_command_not_found", &event_args);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Here we want to report an error (so it shows a backtrace), but with no text.
|
2020-01-24 01:34:46 +00:00
|
|
|
return this->report_error(STATUS_CMD_UNKNOWN, statement, L"");
|
2013-12-30 00:23:26 +00:00
|
|
|
}
|
|
|
|
}
|
2013-12-29 00:18:38 +00:00
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::expand_command(
|
|
|
|
tnode_t<grammar::plain_statement> statement, wcstring *out_cmd,
|
|
|
|
wcstring_list_t *out_args) const {
|
2018-08-26 08:41:45 +00:00
|
|
|
// Here we're expanding a command, for example $HOME/bin/stuff or $randomthing. The first
|
|
|
|
// completion becomes the command itself, everything after becomes arguments. Command
|
|
|
|
// substitutions are not supported.
|
|
|
|
parse_error_list_t errors;
|
|
|
|
|
|
|
|
// Get the unexpanded command string. We expect to always get it here.
|
|
|
|
wcstring unexp_cmd = *command_for_plain_statement(statement, pstree->src);
|
2019-10-04 18:28:47 +00:00
|
|
|
size_t pos_of_command_token = statement.child<0>().source_range()->start;
|
2018-08-26 08:41:45 +00:00
|
|
|
|
|
|
|
// Expand the string to produce completions, and report errors.
|
2019-04-22 22:06:52 +00:00
|
|
|
expand_result_t expand_err =
|
2020-01-16 01:14:47 +00:00
|
|
|
expand_to_command_and_args(unexp_cmd, ctx, out_cmd, out_args, &errors);
|
2019-04-22 22:06:52 +00:00
|
|
|
if (expand_err == expand_result_t::error) {
|
2019-10-04 18:28:47 +00:00
|
|
|
// Issue #5812 - the expansions were done on the command token,
|
|
|
|
// excluding prefixes such as " " or "if ".
|
|
|
|
// This means that the error positions are relative to the beginning
|
|
|
|
// of the token; we need to make them relative to the original source.
|
|
|
|
for (auto &error : errors) error.source_start += pos_of_command_token;
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_errors(STATUS_ILLEGAL_CMD, errors);
|
2019-04-22 22:06:52 +00:00
|
|
|
} else if (expand_err == expand_result_t::wildcard_no_match) {
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_error(STATUS_UNMATCHED_WILDCARD, statement, WILDCARD_ERR_MSG,
|
|
|
|
get_source(statement).c_str());
|
2018-08-26 08:41:45 +00:00
|
|
|
}
|
2020-01-22 19:46:02 +00:00
|
|
|
assert(expand_err == expand_result_t::ok);
|
2018-08-26 08:41:45 +00:00
|
|
|
|
|
|
|
// Complain if the resulting expansion was empty, or expanded to an empty string.
|
|
|
|
if (out_cmd->empty()) {
|
2020-01-24 01:34:46 +00:00
|
|
|
return this->report_error(STATUS_ILLEGAL_CMD, statement,
|
|
|
|
_(L"The expanded command was empty."));
|
2018-08-26 08:41:45 +00:00
|
|
|
}
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::ok;
|
2018-08-26 08:41:45 +00:00
|
|
|
}
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
/// Creates a 'normal' (non-block) process.
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::populate_plain_process(
|
2018-01-15 20:12:42 +00:00
|
|
|
job_t *job, process_t *proc, tnode_t<grammar::plain_statement> statement) {
|
2019-11-19 02:34:50 +00:00
|
|
|
assert(job != nullptr);
|
|
|
|
assert(proc != nullptr);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// We may decide that a command should be an implicit cd.
|
2013-12-30 00:23:26 +00:00
|
|
|
bool use_implicit_cd = false;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2018-08-26 08:41:45 +00:00
|
|
|
// Get the command and any arguments due to expanding the command.
|
|
|
|
wcstring cmd;
|
|
|
|
wcstring_list_t args_from_cmd_expansion;
|
|
|
|
auto ret = expand_command(statement, &cmd, &args_from_cmd_expansion);
|
2020-01-24 00:45:00 +00:00
|
|
|
if (ret != end_execution_reason_t::ok) {
|
2018-08-26 08:41:45 +00:00
|
|
|
return ret;
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2018-08-26 08:41:45 +00:00
|
|
|
assert(!cmd.empty() && "expand_command should not produce an empty command");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Determine the process type.
|
2013-12-31 22:37:37 +00:00
|
|
|
enum process_type_t process_type = process_type_for_command(statement, cmd);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2018-08-21 03:50:27 +00:00
|
|
|
// Protect against exec with background processes running
|
2019-05-27 21:52:48 +00:00
|
|
|
if (process_type == process_type_t::exec && parser->is_interactive()) {
|
2018-08-21 03:50:27 +00:00
|
|
|
bool have_bg = false;
|
2019-05-05 05:12:31 +00:00
|
|
|
for (const auto &bg : parser->jobs()) {
|
2018-12-30 21:32:07 +00:00
|
|
|
// The assumption here is that if it is a foreground job,
|
|
|
|
// it's related to us.
|
|
|
|
// This stops us from asking if we're doing `exec` inside a function.
|
|
|
|
if (!bg->is_completed() && !bg->is_foreground()) {
|
2018-08-21 03:50:27 +00:00
|
|
|
have_bg = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (have_bg) {
|
2019-04-29 01:13:55 +00:00
|
|
|
uint64_t current_run_count = reader_run_count();
|
|
|
|
uint64_t &last_exec_run_count = parser->libdata().last_exec_run_counter;
|
|
|
|
if (isatty(STDIN_FILENO) && current_run_count - 1 != last_exec_run_count) {
|
2019-05-05 05:12:31 +00:00
|
|
|
reader_bg_job_warning(*parser);
|
2019-04-29 01:13:55 +00:00
|
|
|
last_exec_run_count = current_run_count;
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::error;
|
2019-04-29 01:13:55 +00:00
|
|
|
} else {
|
2019-05-05 05:12:31 +00:00
|
|
|
hup_background_jobs(*parser);
|
2018-08-21 03:50:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-07 22:57:58 +00:00
|
|
|
wcstring path_to_external_command;
|
2019-03-24 19:29:25 +00:00
|
|
|
if (process_type == process_type_t::external || process_type == process_type_t::exec) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Determine the actual command. This may be an implicit cd.
|
2018-09-16 19:48:50 +00:00
|
|
|
bool has_command = path_get_path(cmd, &path_to_external_command, parser->vars());
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// If there was no command, then we care about the value of errno after checking for it, to
|
|
|
|
// distinguish between e.g. no file vs permissions problem.
|
2013-12-30 00:23:26 +00:00
|
|
|
const int no_cmd_err_code = errno;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// If the specified command does not exist, and is undecorated, try using an implicit cd.
|
2018-01-15 20:12:42 +00:00
|
|
|
if (!has_command && get_decoration(statement) == parse_statement_decoration_none) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Implicit cd requires an empty argument and redirection list.
|
2018-01-15 20:12:42 +00:00
|
|
|
tnode_t<g::arguments_or_redirections_list> args = statement.child<1>();
|
2018-08-26 08:41:45 +00:00
|
|
|
if (args_from_cmd_expansion.empty() && !args.try_get_child<g::argument, 0>() &&
|
|
|
|
!args.try_get_child<g::redirection, 0>()) {
|
|
|
|
// Ok, no arguments or redirections; check to see if the command is a directory.
|
2018-11-18 02:02:28 +00:00
|
|
|
use_implicit_cd =
|
2018-09-16 11:05:17 +00:00
|
|
|
path_as_implicit_cd(cmd, parser->vars().get_pwd_slash(), parser->vars())
|
|
|
|
.has_value();
|
2013-12-30 00:23:26 +00:00
|
|
|
}
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
if (!has_command && !use_implicit_cd) {
|
|
|
|
// No command.
|
2015-04-18 19:53:43 +00:00
|
|
|
return this->handle_command_not_found(cmd, statement, no_cmd_err_code);
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2018-08-26 08:41:45 +00:00
|
|
|
// Produce the full argument list and the set of IO redirections.
|
|
|
|
wcstring_list_t cmd_args;
|
2019-12-13 00:44:24 +00:00
|
|
|
redirection_spec_list_t redirections;
|
2016-05-02 19:31:33 +00:00
|
|
|
if (use_implicit_cd) {
|
2018-08-26 08:41:45 +00:00
|
|
|
// Implicit cd is simple.
|
|
|
|
cmd_args = {L"cd", cmd};
|
2014-01-07 22:57:58 +00:00
|
|
|
path_to_external_command.clear();
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// If we have defined a wrapper around cd, use it, otherwise use the cd builtin.
|
2019-05-05 03:20:52 +00:00
|
|
|
process_type =
|
|
|
|
function_exists(L"cd", *parser) ? process_type_t::function : process_type_t::builtin;
|
2016-05-02 19:31:33 +00:00
|
|
|
} else {
|
2018-08-26 08:41:45 +00:00
|
|
|
// Not implicit cd.
|
2017-04-05 04:28:57 +00:00
|
|
|
const globspec_t glob_behavior = (cmd == L"set" || cmd == L"count") ? nullglob : failglob;
|
2018-08-26 08:41:45 +00:00
|
|
|
// Form the list of arguments. The command is the first argument, followed by any arguments
|
|
|
|
// from expanding the command, followed by the argument nodes themselves. E.g. if the
|
|
|
|
// command is '$gco foo' and $gco is git checkout.
|
|
|
|
cmd_args.push_back(cmd);
|
|
|
|
cmd_args.insert(cmd_args.end(), args_from_cmd_expansion.begin(),
|
|
|
|
args_from_cmd_expansion.end());
|
2018-01-15 23:15:45 +00:00
|
|
|
argument_node_list_t arg_nodes = statement.descendants<g::argument>();
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t arg_result =
|
2018-08-26 08:41:45 +00:00
|
|
|
this->expand_arguments_from_nodes(arg_nodes, &cmd_args, glob_behavior);
|
2020-01-24 00:45:00 +00:00
|
|
|
if (arg_result != end_execution_reason_t::ok) {
|
2014-05-31 19:41:27 +00:00
|
|
|
return arg_result;
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// The set of IO redirections that we construct for the process.
|
2020-01-24 01:34:46 +00:00
|
|
|
auto reason = this->determine_redirections(statement.child<1>(), &redirections);
|
|
|
|
if (reason != end_execution_reason_t::ok) {
|
|
|
|
return reason;
|
2013-12-31 22:37:37 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Determine the process type.
|
2013-12-31 22:37:37 +00:00
|
|
|
process_type = process_type_for_command(statement, cmd);
|
2013-12-30 00:23:26 +00:00
|
|
|
}
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Populate the process.
|
2013-12-31 22:37:37 +00:00
|
|
|
proc->type = process_type;
|
2018-08-26 08:41:45 +00:00
|
|
|
proc->set_argv(cmd_args);
|
2019-12-13 00:44:24 +00:00
|
|
|
proc->set_redirection_specs(std::move(redirections));
|
2019-03-17 02:32:07 +00:00
|
|
|
proc->actual_cmd = std::move(path_to_external_command);
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::ok;
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Determine the list of arguments, expanding stuff. Reports any errors caused by expansion. If we
|
|
|
|
// have a wildcard that could not be expanded, report the error and continue.
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::expand_arguments_from_nodes(
|
2018-01-15 23:15:45 +00:00
|
|
|
const argument_node_list_t &argument_nodes, wcstring_list_t *out_arguments,
|
|
|
|
globspec_t glob_behavior) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get all argument nodes underneath the statement. We guess we'll have that many arguments (but
|
|
|
|
// may have more or fewer, if there are wildcards involved).
|
2014-05-31 19:41:27 +00:00
|
|
|
out_arguments->reserve(out_arguments->size() + argument_nodes.size());
|
2020-01-16 00:13:41 +00:00
|
|
|
completion_list_t arg_expanded;
|
2019-07-28 22:37:31 +00:00
|
|
|
for (const auto &arg_node : argument_nodes) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Expect all arguments to have source.
|
2013-12-24 21:17:24 +00:00
|
|
|
assert(arg_node.has_source());
|
2017-12-22 22:40:15 +00:00
|
|
|
const wcstring arg_str = arg_node.get_source(pstree->src);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Expand this string.
|
2014-03-22 00:13:33 +00:00
|
|
|
parse_error_list_t errors;
|
2016-02-28 00:14:05 +00:00
|
|
|
arg_expanded.clear();
|
2020-01-16 01:14:47 +00:00
|
|
|
auto expand_ret =
|
|
|
|
expand_string(arg_str, &arg_expanded, expand_flag::no_descriptions, ctx, &errors);
|
2018-01-15 23:15:45 +00:00
|
|
|
parse_error_offset_source_start(&errors, arg_node.source_range()->start);
|
2020-01-24 01:34:46 +00:00
|
|
|
switch (expand_ret.result) {
|
2019-04-22 22:06:52 +00:00
|
|
|
case expand_result_t::error: {
|
2020-01-24 01:34:46 +00:00
|
|
|
return this->report_errors(expand_ret.status, errors);
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2020-01-24 01:34:46 +00:00
|
|
|
|
2020-01-22 19:58:10 +00:00
|
|
|
case expand_result_t::cancel: {
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::cancelled;
|
2020-01-22 19:58:10 +00:00
|
|
|
}
|
2019-04-22 22:06:52 +00:00
|
|
|
case expand_result_t::wildcard_no_match: {
|
2016-05-02 19:31:33 +00:00
|
|
|
if (glob_behavior == failglob) {
|
|
|
|
// Report the unmatched wildcard error and stop processing.
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_error(STATUS_UNMATCHED_WILDCARD, arg_node, WILDCARD_ERR_MSG,
|
|
|
|
get_source(arg_node).c_str());
|
2016-02-08 18:49:26 +00:00
|
|
|
}
|
2013-12-24 21:17:24 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-04-22 22:06:52 +00:00
|
|
|
case expand_result_t::ok: {
|
2013-12-24 21:17:24 +00:00
|
|
|
break;
|
|
|
|
}
|
2016-10-30 00:25:48 +00:00
|
|
|
default: {
|
|
|
|
DIE("unexpected expand_string() return value");
|
|
|
|
break;
|
|
|
|
}
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2017-01-27 00:14:50 +00:00
|
|
|
// Now copy over any expanded arguments. Use std::move() to avoid extra allocations; this
|
2016-05-02 19:31:33 +00:00
|
|
|
// is called very frequently.
|
2017-01-27 00:14:50 +00:00
|
|
|
out_arguments->reserve(out_arguments->size() + arg_expanded.size());
|
|
|
|
for (completion_t &new_arg : arg_expanded) {
|
|
|
|
out_arguments->push_back(std::move(new_arg.completion));
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-12-18 00:41:10 +00:00
|
|
|
// We may have received a cancellation during this expansion.
|
2019-12-18 02:10:29 +00:00
|
|
|
if (auto ret = check_end_execution()) {
|
|
|
|
return *ret;
|
2019-12-18 00:41:10 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::ok;
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 01:34:46 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::determine_redirections(
|
2019-12-13 00:44:24 +00:00
|
|
|
tnode_t<g::arguments_or_redirections_list> node, redirection_spec_list_t *out_redirections) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get all redirection nodes underneath the statement.
|
2018-01-22 21:18:34 +00:00
|
|
|
while (auto redirect_node = node.next_in_list<g::redirection>()) {
|
2019-10-14 20:20:31 +00:00
|
|
|
wcstring target; // file path or target fd
|
|
|
|
auto redirect = redirection_for_node(redirect_node, pstree->src, &target);
|
|
|
|
|
|
|
|
if (!redirect || !redirect->is_valid()) {
|
2019-12-13 00:44:24 +00:00
|
|
|
// TODO: figure out if this can ever happen. If so, improve this error message.
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_error(STATUS_INVALID_ARGS, redirect_node, _(L"Invalid redirection: %ls"),
|
|
|
|
redirect_node.get_source(pstree->src).c_str());
|
2019-10-14 20:20:31 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-04-25 18:34:49 +00:00
|
|
|
// PCA: I can't justify this skip_variables flag. It was like this when I got here.
|
2019-05-05 02:16:26 +00:00
|
|
|
bool target_expanded =
|
2020-01-16 01:14:47 +00:00
|
|
|
expand_one(target, no_exec() ? expand_flag::skip_variables : expand_flags_t{}, ctx);
|
2016-05-02 19:31:33 +00:00
|
|
|
if (!target_expanded || target.empty()) {
|
|
|
|
// TODO: Improve this error message.
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_error(STATUS_INVALID_ARGS, redirect_node,
|
|
|
|
_(L"Invalid redirection target: %ls"), target.c_str());
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-12-13 00:44:24 +00:00
|
|
|
// Make a redirection spec from the redirect token.
|
2019-10-14 20:20:31 +00:00
|
|
|
assert(redirect && redirect->is_valid() && "expected to have a valid redirection");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-12-13 00:44:24 +00:00
|
|
|
redirection_spec_t spec{redirect->fd, redirect->mode, std::move(target)};
|
|
|
|
|
|
|
|
// Validate this spec.
|
|
|
|
if (spec.mode == redirection_mode_t::fd && !spec.is_close() && !spec.get_target_as_fd()) {
|
|
|
|
const wchar_t *fmt =
|
|
|
|
_(L"Requested redirection to '%ls', which is not a valid file descriptor");
|
2020-01-24 01:34:46 +00:00
|
|
|
return report_error(STATUS_INVALID_ARGS, redirect_node, fmt, spec.target.c_str());
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2019-12-13 00:44:24 +00:00
|
|
|
out_redirections->push_back(std::move(spec));
|
2019-10-14 22:45:40 +00:00
|
|
|
|
|
|
|
if (redirect->stderr_merge) {
|
|
|
|
// This was a redirect like &> which also modifies stderr.
|
|
|
|
// Also redirect stderr to stdout.
|
2019-12-13 00:44:24 +00:00
|
|
|
out_redirections->push_back(get_stderr_merge());
|
2019-10-14 22:45:40 +00:00
|
|
|
}
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2020-01-24 01:34:46 +00:00
|
|
|
return end_execution_reason_t::ok;
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::populate_not_process(
|
2018-03-03 02:09:16 +00:00
|
|
|
job_t *job, process_t *proc, tnode_t<g::not_statement> not_statement) {
|
2019-10-15 21:37:10 +00:00
|
|
|
auto &flags = job->mut_flags();
|
|
|
|
flags.negate = !flags.negate;
|
2019-12-21 10:45:07 +00:00
|
|
|
auto optional_time = not_statement.require_get_child<g::optional_time, 2>();
|
2019-12-23 10:45:55 +00:00
|
|
|
if (optional_time.tag() == parse_optional_time_time) {
|
|
|
|
flags.has_time_prefix = true;
|
|
|
|
if (!job->mut_flags().foreground) {
|
2020-01-24 01:34:46 +00:00
|
|
|
return this->report_error(STATUS_INVALID_ARGS, not_statement, ERROR_TIME_BACKGROUND);
|
2019-12-23 10:45:55 +00:00
|
|
|
}
|
|
|
|
}
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
return this->populate_job_process(
|
2019-12-21 10:45:07 +00:00
|
|
|
job, proc, not_statement.require_get_child<g::statement, 3>(),
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
not_statement.require_get_child<g::variable_assignments, 1>());
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
|
2018-01-15 23:37:13 +00:00
|
|
|
template <typename Type>
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::populate_block_process(
|
|
|
|
job_t *job, process_t *proc, tnode_t<g::statement> statement,
|
|
|
|
tnode_t<Type> specific_statement) {
|
2019-03-24 19:29:25 +00:00
|
|
|
// We handle block statements by creating process_type_t::block_node, that will bounce back to
|
|
|
|
// us when it's time to execute them.
|
2016-10-09 21:38:26 +00:00
|
|
|
UNUSED(job);
|
2018-01-15 23:37:13 +00:00
|
|
|
static_assert(Type::token == symbol_block_statement || Type::token == symbol_if_statement ||
|
|
|
|
Type::token == symbol_switch_statement,
|
|
|
|
"Invalid block process");
|
2018-02-11 03:16:35 +00:00
|
|
|
assert(statement && "statement missing");
|
|
|
|
assert(specific_statement && "specific_statement missing");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// The set of IO redirections that we construct for the process.
|
2018-01-15 23:37:13 +00:00
|
|
|
// TODO: fix this ugly find_child.
|
2018-02-11 03:16:35 +00:00
|
|
|
auto arguments = specific_statement.template find_child<g::arguments_or_redirections_list>();
|
2019-12-13 00:44:24 +00:00
|
|
|
redirection_spec_list_t redirections;
|
2020-01-24 01:34:46 +00:00
|
|
|
auto reason = this->determine_redirections(arguments, &redirections);
|
|
|
|
if (reason == end_execution_reason_t::ok) {
|
|
|
|
proc->type = process_type_t::block_node;
|
|
|
|
proc->block_node_source = pstree;
|
|
|
|
proc->internal_block_node = statement;
|
|
|
|
proc->set_redirection_specs(std::move(redirections));
|
2019-12-13 00:44:24 +00:00
|
|
|
}
|
2020-01-24 01:34:46 +00:00
|
|
|
return reason;
|
2013-12-26 20:24:00 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::apply_variable_assignments(
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
process_t *proc, tnode_t<grammar::variable_assignments> variable_assignments,
|
|
|
|
const block_t **block) {
|
|
|
|
variable_assignment_node_list_t assignment_list =
|
|
|
|
get_variable_assignment_nodes(variable_assignments);
|
2020-01-24 00:45:00 +00:00
|
|
|
if (assignment_list.empty()) return end_execution_reason_t::ok;
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
*block = parser->push_block(block_t::variable_assignment_block());
|
|
|
|
for (const auto &variable_assignment : assignment_list) {
|
|
|
|
const wcstring &source = variable_assignment.get_source(pstree->src);
|
|
|
|
auto equals_pos = variable_assignment_equals_pos(source);
|
|
|
|
assert(equals_pos);
|
2020-01-17 12:40:56 +00:00
|
|
|
const wcstring variable_name = source.substr(0, *equals_pos);
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
const wcstring expression = source.substr(*equals_pos + 1);
|
2020-01-16 00:13:41 +00:00
|
|
|
completion_list_t expression_expanded;
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
parse_error_list_t errors;
|
|
|
|
// TODO this is mostly copied from expand_arguments_from_nodes, maybe extract to function
|
2020-01-16 01:14:47 +00:00
|
|
|
auto expand_ret = expand_string(expression, &expression_expanded,
|
|
|
|
expand_flag::no_descriptions, ctx, &errors);
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
parse_error_offset_source_start(
|
|
|
|
&errors, variable_assignment.source_range()->start + *equals_pos + 1);
|
2020-01-24 01:34:46 +00:00
|
|
|
switch (expand_ret.result) {
|
|
|
|
case expand_result_t::error:
|
|
|
|
return this->report_errors(expand_ret.status, errors);
|
|
|
|
|
|
|
|
case expand_result_t::cancel:
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::cancelled;
|
2020-01-24 01:34:46 +00:00
|
|
|
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
case expand_result_t::wildcard_no_match: // nullglob (equivalent to set)
|
2020-01-24 01:34:46 +00:00
|
|
|
case expand_result_t::ok:
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
break;
|
2020-01-24 01:34:46 +00:00
|
|
|
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
default: {
|
|
|
|
DIE("unexpected expand_string() return value");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
wcstring_list_t vals;
|
|
|
|
for (auto &completion : expression_expanded) {
|
|
|
|
vals.emplace_back(std::move(completion.completion));
|
|
|
|
}
|
|
|
|
if (proc) proc->variable_assignments.push_back({variable_name, vals});
|
2019-12-21 21:53:48 +00:00
|
|
|
parser->vars().set(variable_name, ENV_LOCAL | ENV_EXPORT, std::move(vals));
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
}
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::ok;
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::populate_job_process(
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
job_t *job, process_t *proc, tnode_t<grammar::statement> statement,
|
|
|
|
tnode_t<grammar::variable_assignments> variable_assignments) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get the "specific statement" which is boolean / block / if / switch / decorated.
|
2018-01-16 00:18:03 +00:00
|
|
|
const parse_node_t &specific_statement = statement.get_child_node<0>();
|
2014-01-15 09:40:40 +00:00
|
|
|
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
const block_t *block = nullptr;
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t result =
|
|
|
|
this->apply_variable_assignments(proc, variable_assignments, &block);
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
cleanup_t scope([&]() {
|
|
|
|
if (block) parser->pop_block(block);
|
|
|
|
});
|
2020-01-24 01:34:46 +00:00
|
|
|
if (result != end_execution_reason_t::ok) return result;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
switch (specific_statement.type) {
|
2018-03-03 02:09:16 +00:00
|
|
|
case symbol_not_statement: {
|
|
|
|
result = this->populate_not_process(job, proc, {&tree(), &specific_statement});
|
2013-12-24 21:17:24 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case symbol_block_statement:
|
2018-01-15 23:37:13 +00:00
|
|
|
result = this->populate_block_process(
|
2018-02-11 03:16:35 +00:00
|
|
|
job, proc, statement, tnode_t<g::block_statement>(&tree(), &specific_statement));
|
2018-01-15 23:37:13 +00:00
|
|
|
break;
|
2013-12-26 20:24:00 +00:00
|
|
|
case symbol_if_statement:
|
2018-01-15 23:37:13 +00:00
|
|
|
result = this->populate_block_process(
|
2018-02-11 03:16:35 +00:00
|
|
|
job, proc, statement, tnode_t<g::if_statement>(&tree(), &specific_statement));
|
2018-01-15 23:37:13 +00:00
|
|
|
break;
|
|
|
|
case symbol_switch_statement:
|
|
|
|
result = this->populate_block_process(
|
2018-02-11 03:16:35 +00:00
|
|
|
job, proc, statement, tnode_t<g::switch_statement>(&tree(), &specific_statement));
|
2013-12-24 21:17:24 +00:00
|
|
|
break;
|
2016-05-02 19:31:33 +00:00
|
|
|
case symbol_decorated_statement: {
|
|
|
|
// Get the plain statement. It will pull out the decoration itself.
|
2018-01-15 20:12:42 +00:00
|
|
|
tnode_t<g::decorated_statement> dec_stat{&tree(), &specific_statement};
|
|
|
|
auto plain_statement = dec_stat.find_child<g::plain_statement>();
|
2013-12-31 22:37:37 +00:00
|
|
|
result = this->populate_plain_process(job, proc, plain_statement);
|
2013-12-24 21:17:24 +00:00
|
|
|
break;
|
|
|
|
}
|
2016-05-02 19:31:33 +00:00
|
|
|
default: {
|
2019-05-30 09:54:09 +00:00
|
|
|
FLOGF(error, L"'%ls' not handled by new parser yet.",
|
2019-06-04 03:30:48 +00:00
|
|
|
specific_statement.describe().c_str());
|
2013-12-26 20:24:00 +00:00
|
|
|
PARSER_DIE();
|
|
|
|
break;
|
2016-05-02 19:31:33 +00:00
|
|
|
}
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2013-12-24 21:17:24 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::populate_job_from_job_node(
|
2018-01-14 09:17:57 +00:00
|
|
|
job_t *j, tnode_t<grammar::job> job_node, const block_t *associated_block) {
|
2016-10-09 21:38:26 +00:00
|
|
|
UNUSED(associated_block);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Tell the job what its command is.
|
2013-12-24 21:17:24 +00:00
|
|
|
j->set_command(get_source(job_node));
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// We are going to construct process_t structures for every statement in the job. Get the first
|
|
|
|
// statement.
|
2019-12-21 10:45:07 +00:00
|
|
|
tnode_t<g::optional_time> optional_time = job_node.child<0>();
|
|
|
|
tnode_t<g::variable_assignments> variable_assignments = job_node.child<1>();
|
|
|
|
tnode_t<g::statement> statement = job_node.child<2>();
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Create processes. Each one may fail.
|
2017-01-23 17:28:34 +00:00
|
|
|
process_list_t processes;
|
|
|
|
processes.emplace_back(new process_t());
|
2019-12-23 10:45:55 +00:00
|
|
|
if (optional_time.tag() == parse_optional_time_time) {
|
|
|
|
j->mut_flags().has_time_prefix = true;
|
|
|
|
if (job_node_is_background(job_node)) {
|
2020-01-24 01:34:46 +00:00
|
|
|
return this->report_error(STATUS_INVALID_ARGS, job_node, ERROR_TIME_BACKGROUND);
|
2019-12-23 10:45:55 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t result =
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
this->populate_job_process(j, processes.back().get(), statement, variable_assignments);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Construct process_ts for job continuations (pipelines), by walking the list until we hit the
|
|
|
|
// terminal (empty) job continuation.
|
2019-12-21 10:45:07 +00:00
|
|
|
tnode_t<g::job_continuation> job_cont = job_node.child<3>();
|
2018-01-14 09:17:57 +00:00
|
|
|
assert(job_cont);
|
|
|
|
while (auto pipe = job_cont.try_get_child<g::tok_pipe, 0>()) {
|
2020-01-24 00:45:00 +00:00
|
|
|
if (result != end_execution_reason_t::ok) {
|
2018-01-14 09:17:57 +00:00
|
|
|
break;
|
|
|
|
}
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
auto variable_assignments = job_cont.require_get_child<g::variable_assignments, 2>();
|
|
|
|
auto statement = job_cont.require_get_child<g::statement, 3>();
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Handle the pipe, whose fd may not be the obvious stdout.
|
2019-10-14 20:20:31 +00:00
|
|
|
auto parsed_pipe = pipe_or_redir_t::from_string(get_source(pipe));
|
|
|
|
assert(parsed_pipe.has_value() && parsed_pipe->is_pipe && "Failed to parse valid pipe");
|
|
|
|
if (!parsed_pipe->is_valid()) {
|
2020-01-24 01:34:46 +00:00
|
|
|
result = report_error(STATUS_INVALID_ARGS, pipe, ILLEGAL_FD_ERR_MSG,
|
|
|
|
get_source(pipe).c_str());
|
2014-01-13 11:57:59 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-10-14 20:20:31 +00:00
|
|
|
processes.back()->pipe_write_fd = parsed_pipe->fd;
|
2019-10-14 22:45:40 +00:00
|
|
|
if (parsed_pipe->stderr_merge) {
|
|
|
|
// This was a pipe like &| which redirects both stdout and stderr.
|
|
|
|
// Also redirect stderr to stdout.
|
2019-12-13 00:44:24 +00:00
|
|
|
auto specs = processes.back()->redirection_specs();
|
|
|
|
specs.push_back(get_stderr_merge());
|
|
|
|
processes.back()->set_redirection_specs(std::move(specs));
|
2019-10-14 22:45:40 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Store the new process (and maybe with an error).
|
2017-01-23 17:28:34 +00:00
|
|
|
processes.emplace_back(new process_t());
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
result =
|
|
|
|
this->populate_job_process(j, processes.back().get(), statement, variable_assignments);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get the next continuation.
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
job_cont = job_cont.require_get_child<g::job_continuation, 4>();
|
2018-01-14 09:17:57 +00:00
|
|
|
assert(job_cont);
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2017-01-23 17:28:34 +00:00
|
|
|
// Inform our processes of who is first and last
|
|
|
|
processes.front()->is_first_in_job = true;
|
|
|
|
processes.back()->is_last_in_job = true;
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Return what happened.
|
2020-01-24 00:45:00 +00:00
|
|
|
if (result == end_execution_reason_t::ok) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Link up the processes.
|
2016-10-23 20:58:12 +00:00
|
|
|
assert(!processes.empty()); //!OCLINT(multiple unary operator)
|
2017-01-23 17:28:34 +00:00
|
|
|
j->processes = std::move(processes);
|
2013-12-31 22:37:37 +00:00
|
|
|
}
|
|
|
|
return result;
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
|
2019-05-05 05:12:31 +00:00
|
|
|
static bool remove_job(parser_t &parser, job_t *job) {
|
|
|
|
for (auto j = parser.jobs().begin(); j != parser.jobs().end(); ++j) {
|
2018-12-31 03:25:16 +00:00
|
|
|
if (j->get() == job) {
|
2019-05-05 05:12:31 +00:00
|
|
|
parser.jobs().erase(j);
|
2018-12-31 03:25:16 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::run_1_job(tnode_t<g::job> job_node,
|
|
|
|
const block_t *associated_block) {
|
2019-12-18 02:10:29 +00:00
|
|
|
if (auto ret = check_end_execution()) {
|
|
|
|
return *ret;
|
2013-12-29 00:18:38 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Get terminal modes.
|
2013-12-24 21:17:24 +00:00
|
|
|
struct termios tmodes = {};
|
2019-05-27 21:52:48 +00:00
|
|
|
if (parser->is_interactive() && tcgetattr(STDIN_FILENO, &tmodes)) {
|
2016-10-22 18:21:13 +00:00
|
|
|
// Need real error handling here.
|
|
|
|
wperror(L"tcgetattr");
|
2020-01-24 01:34:46 +00:00
|
|
|
parser->set_last_statuses(statuses_t::just(STATUS_CMD_ERROR));
|
2020-01-24 00:45:00 +00:00
|
|
|
return end_execution_reason_t::error;
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Increment the eval_level for the duration of this command.
|
2018-02-12 05:42:23 +00:00
|
|
|
scoped_push<int> saved_eval_level(&parser->eval_level, parser->eval_level + 1);
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Save the node index.
|
2018-02-12 04:08:40 +00:00
|
|
|
scoped_push<tnode_t<grammar::job>> saved_node(&executing_job_node, job_node);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Profiling support.
|
2014-02-09 22:04:43 +00:00
|
|
|
long long start_time = 0, parse_time = 0, exec_time = 0;
|
|
|
|
profile_item_t *profile_item = this->parser->create_profile_item();
|
2019-11-19 02:34:50 +00:00
|
|
|
if (profile_item != nullptr) {
|
2014-02-09 22:04:43 +00:00
|
|
|
start_time = get_time();
|
|
|
|
}
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// When we encounter a block construct (e.g. while loop) in the general case, we create a "block
|
2018-02-11 03:16:35 +00:00
|
|
|
// process" containing its node. This allows us to handle block-level redirections.
|
2016-05-02 19:31:33 +00:00
|
|
|
// However, if there are no redirections, then we can just jump into the block directly, which
|
|
|
|
// is significantly faster.
|
|
|
|
if (job_is_simple_block(job_node)) {
|
2019-12-21 10:45:07 +00:00
|
|
|
tnode_t<g::optional_time> optional_time = job_node.child<0>();
|
|
|
|
cleanup_t timer = push_timer(optional_time.tag() == parse_optional_time_time);
|
|
|
|
tnode_t<g::variable_assignments> variable_assignments = job_node.child<1>();
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
const block_t *block = nullptr;
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t result =
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
this->apply_variable_assignments(nullptr, variable_assignments, &block);
|
|
|
|
cleanup_t scope([&]() {
|
|
|
|
if (block) parser->pop_block(block);
|
|
|
|
});
|
|
|
|
|
2019-12-21 10:45:07 +00:00
|
|
|
tnode_t<g::statement> statement = job_node.child<2>();
|
2018-01-16 00:18:03 +00:00
|
|
|
const parse_node_t &specific_statement = statement.get_child_node<0>();
|
2014-01-07 18:45:36 +00:00
|
|
|
assert(specific_statement_type_is_redirectable_block(specific_statement));
|
2020-01-24 00:45:00 +00:00
|
|
|
if (result == end_execution_reason_t::ok) {
|
Support FOO=bar syntax for passing variables to individual commands
This adds initial support for statements with prefixed variable assignments.
Statments like this are supported:
a=1 b=$a echo $b # outputs 1
Just like in other shells, the left-hand side of each assignment must
be a valid variable identifier (no quoting/escaping). Array indexing
(PATH[1]=/bin ls $PATH) is *not* yet supported, but can be added fairly
easily.
The right hand side may be any valid string token, like a command
substitution, or a brace expansion.
Since `a=* foo` is equivalent to `begin set -lx a *; foo; end`,
the assignment, like `set`, uses nullglob behavior, e.g. below command
can safely be used to check if a directory is empty.
x=/nothing/{,.}* test (count $x) -eq 0
Generic file completion is done after the equal sign, so for example
pressing tab after something like `HOME=/` completes files in the
root directory
Subcommand completion works, so something like
`GIT_DIR=repo.git and command git ` correctly calls git completions
(but the git completion does not use the variable as of now).
The variable assignment is highlighted like an argument.
Closes #6048
2019-10-23 01:13:29 +00:00
|
|
|
switch (specific_statement.type) {
|
|
|
|
case symbol_block_statement: {
|
|
|
|
result =
|
|
|
|
this->run_block_statement({&tree(), &specific_statement}, associated_block);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case symbol_if_statement: {
|
|
|
|
result =
|
|
|
|
this->run_if_statement({&tree(), &specific_statement}, associated_block);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case symbol_switch_statement: {
|
|
|
|
result = this->run_switch_statement({&tree(), &specific_statement});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
// Other types should be impossible due to the
|
|
|
|
// specific_statement_type_is_redirectable_block check.
|
|
|
|
PARSER_DIE();
|
|
|
|
break;
|
|
|
|
}
|
2016-05-02 19:31:33 +00:00
|
|
|
}
|
2014-01-07 18:45:36 +00:00
|
|
|
}
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2019-11-19 02:34:50 +00:00
|
|
|
if (profile_item != nullptr) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Block-types profile a little weird. They have no 'parse' time, and their command is
|
|
|
|
// just the block type.
|
2014-02-09 22:04:43 +00:00
|
|
|
exec_time = get_time();
|
2018-02-12 05:42:23 +00:00
|
|
|
profile_item->level = parser->eval_level;
|
2014-02-09 22:04:43 +00:00
|
|
|
profile_item->parse = 0;
|
2019-11-19 01:08:16 +00:00
|
|
|
profile_item->exec = static_cast<int>(exec_time - start_time);
|
2017-12-22 22:40:15 +00:00
|
|
|
profile_item->cmd = profiling_cmd_name_for_redirectable_block(
|
|
|
|
specific_statement, this->tree(), this->pstree->src);
|
2015-07-20 09:34:57 +00:00
|
|
|
profile_item->skipped = false;
|
2014-02-09 22:04:43 +00:00
|
|
|
}
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2014-02-09 22:04:43 +00:00
|
|
|
return result;
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-06-23 19:39:29 +00:00
|
|
|
const auto &ld = parser->libdata();
|
|
|
|
|
2019-04-30 06:08:01 +00:00
|
|
|
auto job_control_mode = get_job_control_mode();
|
2019-06-23 19:39:29 +00:00
|
|
|
bool wants_job_control =
|
|
|
|
(job_control_mode == job_control_t::all) ||
|
2020-01-30 00:08:10 +00:00
|
|
|
((job_control_mode == job_control_t::interactive) && parser->is_interactive()) ||
|
|
|
|
lineage.root_has_job_control;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-06-23 19:39:29 +00:00
|
|
|
job_t::properties_t props{};
|
|
|
|
props.wants_terminal = wants_job_control && !ld.is_event;
|
|
|
|
props.skip_notification =
|
2019-05-27 21:52:48 +00:00
|
|
|
ld.is_subshell || ld.is_block || ld.is_event || !parser->is_interactive();
|
2019-06-26 18:28:27 +00:00
|
|
|
props.from_event_handler = ld.is_event;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-12-08 21:03:42 +00:00
|
|
|
shared_ptr<job_t> job = std::make_shared<job_t>(acquire_job_id(), props, this->lineage);
|
2019-06-23 19:39:29 +00:00
|
|
|
job->tmodes = tmodes;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-10-15 21:37:10 +00:00
|
|
|
job->mut_flags().foreground = !job_node_is_background(job_node);
|
|
|
|
job->mut_flags().job_control = wants_job_control;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-05-12 21:42:18 +00:00
|
|
|
// We are about to populate a job. One possible argument to the job is a command substitution
|
|
|
|
// which may be interested in the job that's populating it, via '--on-job-exit caller'. Record
|
|
|
|
// the job ID here.
|
|
|
|
auto &libdata = parser->libdata();
|
|
|
|
const auto saved_caller_jid = libdata.caller_job_id;
|
Introduce the internal jobs for functions
This PR is aimed at improving how job ids are assigned. In particular,
previous to this commit, a job id would be consumed by functions (and
thus aliases). Since it's usual to use functions as command wrappers
this results in awkward job id assignments.
For example if the user is like me and just made the jump from vim -> neovim
then the user might create the following alias:
```
alias vim=nvim
```
Previous to this commit if the user ran `vim` after setting up this
alias, backgrounded (^Z) and ran `jobs` then the output might be:
```
Job Group State Command
2 60267 stopped nvim $argv
```
If the user subsequently opened another vim (nvim) session, backgrounded
and ran jobs then they might see what follows:
```
Job Group State Command
4 70542 stopped nvim $argv
2 60267 stopped nvim $argv
```
These job ids feel unnatural, especially when transitioning away from
e.g. bash where job ids are sequentially incremented (and aliases/functions
don't consume a job id).
See #6053 for more details.
As @ridiculousfish pointed out in
https://github.com/fish-shell/fish-shell/issues/6053#issuecomment-559899400,
we want to elide a job's job id if it corresponds to a single function in the
foreground. This translates to the following prerequisites:
- A job must correspond to a single process (i.e. the job continuation
must be empty)
- A job must be in the foreground (i.e. `&` wasn't appended)
- The job's single process must resolve to a function invocation
If all of these conditions are true then we should mark a job as
"internal" and somehow remove it from consideration when any
infrastructure tries to interact with jobs / job ids.
I saw two paths to implement these requirements:
- At the time of job creation calculate whether or not a job is
"internal" and use a separate list of job ids to track their ids.
Additionally introduce a new flag denoting that a job is internal so
that e.g. `jobs` doesn't list internal jobs
- I started implementing this route but quickly realized I was
computing the same information that would be computed later on (e.g.
"is this job a single process" and "is this jobs statement a
function"). Specifically I was computing data that populate_job_process
would end up computing later anyway. Additionally this added some
weird complexities to the job system (after the change there were two
job id lists AND an additional flag that had to be taken into
consideration)
- Once a function is about to be executed we release the current jobs
job id if the prerequisites are satisfied (which at this point have
been fully computed).
- I opted for this solution since it seems cleaner. In this
implementation "releasing a job id" is done by both calling
`release_job_id` and by marking the internal job_id member variable to
-1. The former operation allows subsequent child jobs to reuse that
same job id (so e.g. the situation described in Motivation doesn't
occur), and the latter ensures that no other job / job id
infrastructure will interact with these jobs because valid jobs have
positive job ids. The second operation causes job_id to become
non-const which leads to the list of code changes outside of `exec.c`
(i.e. a codemod from `job_t::job_id` -> `job_t::job_id()` and moving the
old member variable to a non-const private `job_t::job_id_`)
Note: Its very possible I missed something and setting the job id to -1
will break some other infrastructure, please let me know if so!
I tried to run `make/ninja lint`, but a bunch of non-relevant issues
appeared (e.g. `fatal error: 'config.h' file not found`). I did
successfully clang-format (`git clang-format -f`) and run tests, though.
This PR closes #6053.
2019-12-29 15:46:07 +00:00
|
|
|
libdata.caller_job_id = job->job_id();
|
2014-04-02 07:32:08 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Populate the job. This may fail for reasons like command_not_found. If this fails, an error
|
|
|
|
// will have been printed.
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t pop_result =
|
2018-01-16 00:00:19 +00:00
|
|
|
this->populate_job_from_job_node(job.get(), job_node, associated_block);
|
2014-01-15 09:40:40 +00:00
|
|
|
|
Introduce the internal jobs for functions
This PR is aimed at improving how job ids are assigned. In particular,
previous to this commit, a job id would be consumed by functions (and
thus aliases). Since it's usual to use functions as command wrappers
this results in awkward job id assignments.
For example if the user is like me and just made the jump from vim -> neovim
then the user might create the following alias:
```
alias vim=nvim
```
Previous to this commit if the user ran `vim` after setting up this
alias, backgrounded (^Z) and ran `jobs` then the output might be:
```
Job Group State Command
2 60267 stopped nvim $argv
```
If the user subsequently opened another vim (nvim) session, backgrounded
and ran jobs then they might see what follows:
```
Job Group State Command
4 70542 stopped nvim $argv
2 60267 stopped nvim $argv
```
These job ids feel unnatural, especially when transitioning away from
e.g. bash where job ids are sequentially incremented (and aliases/functions
don't consume a job id).
See #6053 for more details.
As @ridiculousfish pointed out in
https://github.com/fish-shell/fish-shell/issues/6053#issuecomment-559899400,
we want to elide a job's job id if it corresponds to a single function in the
foreground. This translates to the following prerequisites:
- A job must correspond to a single process (i.e. the job continuation
must be empty)
- A job must be in the foreground (i.e. `&` wasn't appended)
- The job's single process must resolve to a function invocation
If all of these conditions are true then we should mark a job as
"internal" and somehow remove it from consideration when any
infrastructure tries to interact with jobs / job ids.
I saw two paths to implement these requirements:
- At the time of job creation calculate whether or not a job is
"internal" and use a separate list of job ids to track their ids.
Additionally introduce a new flag denoting that a job is internal so
that e.g. `jobs` doesn't list internal jobs
- I started implementing this route but quickly realized I was
computing the same information that would be computed later on (e.g.
"is this job a single process" and "is this jobs statement a
function"). Specifically I was computing data that populate_job_process
would end up computing later anyway. Additionally this added some
weird complexities to the job system (after the change there were two
job id lists AND an additional flag that had to be taken into
consideration)
- Once a function is about to be executed we release the current jobs
job id if the prerequisites are satisfied (which at this point have
been fully computed).
- I opted for this solution since it seems cleaner. In this
implementation "releasing a job id" is done by both calling
`release_job_id` and by marking the internal job_id member variable to
-1. The former operation allows subsequent child jobs to reuse that
same job id (so e.g. the situation described in Motivation doesn't
occur), and the latter ensures that no other job / job id
infrastructure will interact with these jobs because valid jobs have
positive job ids. The second operation causes job_id to become
non-const which leads to the list of code changes outside of `exec.c`
(i.e. a codemod from `job_t::job_id` -> `job_t::job_id()` and moving the
old member variable to a non-const private `job_t::job_id_`)
Note: Its very possible I missed something and setting the job id to -1
will break some other infrastructure, please let me know if so!
I tried to run `make/ninja lint`, but a bunch of non-relevant issues
appeared (e.g. `fatal error: 'config.h' file not found`). I did
successfully clang-format (`git clang-format -f`) and run tests, though.
This PR closes #6053.
2019-12-29 15:46:07 +00:00
|
|
|
assert(libdata.caller_job_id == job->job_id() && "Caller job ID unexpectedly changed");
|
2019-05-12 21:42:18 +00:00
|
|
|
parser->libdata().caller_job_id = saved_caller_jid;
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Store time it took to 'parse' the command.
|
2019-11-19 02:34:50 +00:00
|
|
|
if (profile_item != nullptr) {
|
2013-12-26 20:24:00 +00:00
|
|
|
parse_time = get_time();
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-05-12 21:42:18 +00:00
|
|
|
// Clean up the job on failure or cancellation.
|
2020-01-24 00:45:00 +00:00
|
|
|
if (pop_result == end_execution_reason_t::ok) {
|
2020-01-30 00:39:44 +00:00
|
|
|
// Set the pgroup assignment mode, now that the job is populated.
|
2020-01-30 19:14:31 +00:00
|
|
|
job->pgroup_provenance = get_pgroup_provenance(job, lineage);
|
2020-01-30 00:39:44 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Success. Give the job to the parser - it will clean it up.
|
2017-01-26 22:47:32 +00:00
|
|
|
parser->job_add(job);
|
2013-12-27 09:38:43 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Check to see if this contained any external commands.
|
2013-12-26 20:55:10 +00:00
|
|
|
bool job_contained_external_command = false;
|
2017-01-26 22:47:32 +00:00
|
|
|
for (const auto &proc : job->processes) {
|
2019-03-24 19:29:25 +00:00
|
|
|
if (proc->type == process_type_t::external) {
|
2013-12-26 20:55:10 +00:00
|
|
|
job_contained_external_command = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Actually execute the job.
|
2019-12-12 00:34:20 +00:00
|
|
|
if (!exec_job(*this->parser, job, lineage)) {
|
2019-05-05 05:12:31 +00:00
|
|
|
remove_job(*this->parser, job.get());
|
2018-11-18 23:14:08 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-10-22 00:21:40 +00:00
|
|
|
// Update universal variables on external conmmands.
|
2019-04-10 23:15:47 +00:00
|
|
|
// TODO: justify this, why not on every command?
|
2016-05-02 19:31:33 +00:00
|
|
|
if (job_contained_external_command) {
|
2019-04-10 23:15:47 +00:00
|
|
|
parser->vars().universal_barrier();
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-11-19 02:34:50 +00:00
|
|
|
if (profile_item != nullptr) {
|
2013-12-26 20:24:00 +00:00
|
|
|
exec_time = get_time();
|
2018-02-12 05:42:23 +00:00
|
|
|
profile_item->level = parser->eval_level;
|
2019-11-19 01:08:16 +00:00
|
|
|
profile_item->parse = static_cast<int>(parse_time - start_time);
|
|
|
|
profile_item->exec = static_cast<int>(exec_time - parse_time);
|
2017-01-26 22:47:32 +00:00
|
|
|
profile_item->cmd = job ? job->command() : wcstring();
|
2020-01-24 00:45:00 +00:00
|
|
|
profile_item->skipped = (pop_result != end_execution_reason_t::ok);
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2019-04-30 03:58:58 +00:00
|
|
|
job_reap(*parser, false); // clean up jobs
|
2019-12-18 02:10:29 +00:00
|
|
|
return pop_result;
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::run_job_conjunction(
|
2018-03-02 02:30:48 +00:00
|
|
|
tnode_t<grammar::job_conjunction> job_expr, const block_t *associated_block) {
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t result = end_execution_reason_t::ok;
|
2018-03-02 02:30:48 +00:00
|
|
|
tnode_t<g::job_conjunction> cursor = job_expr;
|
2018-03-03 02:09:16 +00:00
|
|
|
// continuation is the parent of the cursor
|
2018-03-02 02:30:48 +00:00
|
|
|
tnode_t<g::job_conjunction_continuation> continuation;
|
|
|
|
while (cursor) {
|
2019-12-18 02:10:29 +00:00
|
|
|
if (auto reason = check_end_execution()) {
|
|
|
|
result = *reason;
|
|
|
|
break;
|
|
|
|
}
|
2018-03-02 02:30:48 +00:00
|
|
|
bool skip = false;
|
|
|
|
if (continuation) {
|
|
|
|
// Check the conjunction type.
|
2019-12-20 04:41:53 +00:00
|
|
|
parse_job_decoration_t conj = bool_statement_type(continuation);
|
2019-12-29 22:25:42 +00:00
|
|
|
assert((conj == parse_job_decoration_and || conj == parse_job_decoration_or) &&
|
|
|
|
"Unexpected conjunction");
|
2018-03-03 02:09:16 +00:00
|
|
|
skip = should_skip(conj);
|
2018-03-02 02:30:48 +00:00
|
|
|
}
|
2019-05-05 10:09:25 +00:00
|
|
|
if (!skip) {
|
2018-03-02 02:30:48 +00:00
|
|
|
result = run_1_job(cursor.child<0>(), associated_block);
|
|
|
|
}
|
|
|
|
continuation = cursor.child<1>();
|
|
|
|
cursor = continuation.try_get_child<g::job_conjunction, 2>();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-12-20 04:41:53 +00:00
|
|
|
bool parse_execution_context_t::should_skip(parse_job_decoration_t type) const {
|
2018-03-03 02:09:16 +00:00
|
|
|
switch (type) {
|
2019-12-20 04:41:53 +00:00
|
|
|
case parse_job_decoration_and:
|
2018-03-03 02:09:16 +00:00
|
|
|
// AND. Skip if the last job failed.
|
2019-05-12 21:00:44 +00:00
|
|
|
return parser->get_last_status() != 0;
|
2019-12-20 04:41:53 +00:00
|
|
|
case parse_job_decoration_or:
|
2018-03-03 02:09:16 +00:00
|
|
|
// OR. Skip if the last job succeeded.
|
2019-05-12 21:00:44 +00:00
|
|
|
return parser->get_last_status() == 0;
|
2018-03-03 02:09:16 +00:00
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-16 00:08:06 +00:00
|
|
|
template <typename Type>
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::run_job_list(tnode_t<Type> job_list,
|
|
|
|
const block_t *associated_block) {
|
2018-03-03 02:09:16 +00:00
|
|
|
// We handle both job_list and andor_job_list uniformly.
|
2018-01-16 00:08:06 +00:00
|
|
|
static_assert(Type::token == symbol_job_list || Type::token == symbol_andor_job_list,
|
|
|
|
"Not a job list");
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t result = end_execution_reason_t::ok;
|
2018-03-03 02:09:16 +00:00
|
|
|
while (auto job_conj = job_list.template next_in_list<g::job_conjunction>()) {
|
2019-12-18 02:10:29 +00:00
|
|
|
if (auto reason = check_end_execution()) {
|
|
|
|
result = *reason;
|
|
|
|
break;
|
|
|
|
}
|
2018-03-03 02:09:16 +00:00
|
|
|
|
|
|
|
// Maybe skip the job if it has a leading and/or.
|
|
|
|
// Skipping is treated as success.
|
|
|
|
if (should_skip(get_decorator(job_conj))) {
|
2020-01-24 00:45:00 +00:00
|
|
|
result = end_execution_reason_t::ok;
|
2018-03-03 02:09:16 +00:00
|
|
|
} else {
|
|
|
|
result = this->run_job_conjunction(job_conj, associated_block);
|
|
|
|
}
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2018-03-03 02:09:16 +00:00
|
|
|
// Returns the result of the last job executed or skipped.
|
2013-12-26 21:24:10 +00:00
|
|
|
return result;
|
2013-12-26 20:24:00 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::eval_node(tnode_t<g::statement> statement,
|
|
|
|
const block_t *associated_block) {
|
2018-02-11 03:16:35 +00:00
|
|
|
assert(statement && "Empty node in eval_node");
|
|
|
|
assert(statement.matches_node_tree(tree()) && "statement has unexpected tree");
|
2020-01-24 00:45:00 +00:00
|
|
|
enum end_execution_reason_t status = end_execution_reason_t::ok;
|
2018-02-11 03:16:35 +00:00
|
|
|
if (auto block = statement.try_get_child<g::block_statement, 0>()) {
|
2018-03-31 21:57:24 +00:00
|
|
|
status = this->run_block_statement(block, associated_block);
|
2018-02-11 03:16:35 +00:00
|
|
|
} else if (auto ifstat = statement.try_get_child<g::if_statement, 0>()) {
|
2018-03-31 21:57:24 +00:00
|
|
|
status = this->run_if_statement(ifstat, associated_block);
|
2018-02-11 03:16:35 +00:00
|
|
|
} else if (auto switchstat = statement.try_get_child<g::switch_statement, 0>()) {
|
|
|
|
status = this->run_switch_statement(switchstat);
|
|
|
|
} else {
|
2019-05-30 11:04:40 +00:00
|
|
|
FLOGF(error, L"Unexpected node %ls found in %s", statement.node()->describe().c_str(),
|
2019-06-04 03:30:48 +00:00
|
|
|
__FUNCTION__);
|
2018-02-11 03:16:35 +00:00
|
|
|
abort();
|
2013-12-27 09:38:43 +00:00
|
|
|
}
|
2018-02-11 03:16:35 +00:00
|
|
|
return status;
|
|
|
|
}
|
2014-01-15 09:40:40 +00:00
|
|
|
|
2020-01-24 00:45:00 +00:00
|
|
|
end_execution_reason_t parse_execution_context_t::eval_node(tnode_t<g::job_list> job_list,
|
|
|
|
const block_t *associated_block) {
|
2018-02-11 03:16:35 +00:00
|
|
|
// Apply this block IO for the duration of this function.
|
|
|
|
assert(job_list && "Empty node in eval_node");
|
|
|
|
assert(job_list.matches_node_tree(tree()) && "job_list has unexpected tree");
|
2019-11-10 20:36:46 +00:00
|
|
|
assert(associated_block && "Null block");
|
|
|
|
|
|
|
|
// Check for infinite recursion: a function which immediately calls itself..
|
2018-02-11 03:16:35 +00:00
|
|
|
wcstring func_name;
|
|
|
|
auto infinite_recursive_node =
|
|
|
|
this->infinite_recursive_statement_in_job_list(job_list, &func_name);
|
|
|
|
if (infinite_recursive_node) {
|
|
|
|
// We have an infinite recursion.
|
2020-01-24 01:34:46 +00:00
|
|
|
return this->report_error(STATUS_CMD_ERROR, infinite_recursive_node,
|
|
|
|
INFINITE_FUNC_RECURSION_ERR_MSG, func_name.c_str());
|
2018-02-11 03:16:35 +00:00
|
|
|
}
|
2019-11-10 20:36:46 +00:00
|
|
|
|
|
|
|
// Check for stack overflow. The TOP check ensures we only do this for function calls.
|
2019-12-22 23:37:14 +00:00
|
|
|
if (associated_block->type() == block_type_t::top && parser->function_stack_is_overflowing()) {
|
2020-01-24 01:34:46 +00:00
|
|
|
return this->report_error(STATUS_CMD_ERROR, job_list, CALL_STACK_LIMIT_EXCEEDED_ERR_MSG);
|
2019-11-10 20:36:46 +00:00
|
|
|
}
|
|
|
|
return this->run_job_list(job_list, associated_block);
|
2013-12-24 21:17:24 +00:00
|
|
|
}
|
2014-03-02 00:04:13 +00:00
|
|
|
|
2018-02-12 04:08:40 +00:00
|
|
|
int parse_execution_context_t::line_offset_of_node(tnode_t<g::job> node) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// If we're not executing anything, return -1.
|
2018-02-12 04:08:40 +00:00
|
|
|
if (!node) {
|
2014-03-02 00:04:13 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// If for some reason we're executing a node without source, return -1.
|
2018-02-12 04:08:40 +00:00
|
|
|
auto range = node.source_range();
|
|
|
|
if (!range) {
|
2014-03-02 00:04:13 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2016-05-02 19:31:33 +00:00
|
|
|
|
2018-02-12 04:08:40 +00:00
|
|
|
return this->line_offset_of_character_at_offset(range->start);
|
2014-09-28 00:32:54 +00:00
|
|
|
}
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
int parse_execution_context_t::line_offset_of_character_at_offset(size_t offset) {
|
|
|
|
// Count the number of newlines, leveraging our cache.
|
2017-12-22 22:40:15 +00:00
|
|
|
assert(offset <= pstree->src.size());
|
2014-03-02 00:04:13 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// Easy hack to handle 0.
|
|
|
|
if (offset == 0) {
|
2014-03-16 23:45:00 +00:00
|
|
|
return 0;
|
2014-03-02 00:04:13 +00:00
|
|
|
}
|
2014-03-31 17:01:39 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
// We want to return (one plus) the number of newlines at offsets less than the given offset.
|
|
|
|
// cached_lineno_count is the number of newlines at indexes less than cached_lineno_offset.
|
2017-12-22 22:40:15 +00:00
|
|
|
const wchar_t *str = pstree->src.c_str();
|
2016-05-02 19:31:33 +00:00
|
|
|
if (offset > cached_lineno_offset) {
|
2014-03-02 00:04:13 +00:00
|
|
|
size_t i;
|
2018-11-14 10:24:55 +00:00
|
|
|
for (i = cached_lineno_offset; i < offset && str[i] != L'\0'; i++) {
|
2016-05-02 19:31:33 +00:00
|
|
|
// Add one for every newline we find in the range [cached_lineno_offset, offset).
|
|
|
|
if (str[i] == L'\n') {
|
2014-03-02 00:04:13 +00:00
|
|
|
cached_lineno_count++;
|
|
|
|
}
|
|
|
|
}
|
2016-05-02 19:31:33 +00:00
|
|
|
cached_lineno_offset =
|
|
|
|
i; // note: i, not offset, in case offset is beyond the length of the string
|
|
|
|
} else if (offset < cached_lineno_offset) {
|
|
|
|
// Subtract one for every newline we find in the range [offset, cached_lineno_offset).
|
|
|
|
for (size_t i = offset; i < cached_lineno_offset; i++) {
|
|
|
|
if (str[i] == L'\n') {
|
2014-03-02 00:04:13 +00:00
|
|
|
cached_lineno_count--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cached_lineno_offset = offset;
|
|
|
|
}
|
2014-03-16 23:45:00 +00:00
|
|
|
return cached_lineno_count;
|
|
|
|
}
|
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
int parse_execution_context_t::get_current_line_number() {
|
2014-03-16 23:45:00 +00:00
|
|
|
int line_number = -1;
|
2018-02-12 04:08:40 +00:00
|
|
|
int line_offset = this->line_offset_of_node(this->executing_job_node);
|
2016-05-02 19:31:33 +00:00
|
|
|
if (line_offset >= 0) {
|
|
|
|
// The offset is 0 based; the number is 1 based.
|
2014-03-16 23:45:00 +00:00
|
|
|
line_number = line_offset + 1;
|
|
|
|
}
|
|
|
|
return line_number;
|
2014-03-02 00:04:13 +00:00
|
|
|
}
|
2014-03-17 05:06:32 +00:00
|
|
|
|
2016-05-02 19:31:33 +00:00
|
|
|
int parse_execution_context_t::get_current_source_offset() const {
|
2014-03-17 05:06:32 +00:00
|
|
|
int result = -1;
|
2018-02-12 04:08:40 +00:00
|
|
|
if (executing_job_node) {
|
|
|
|
if (auto range = executing_job_node.source_range()) {
|
|
|
|
result = static_cast<int>(range->start);
|
2014-03-17 05:06:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|