mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-26 02:13:38 +08:00
Hook up for statements, if statements, and function definition in new
parser
This commit is contained in:
parent
a6ca809a4e
commit
6ce4b344e4
307
builtin.cpp
307
builtin.cpp
|
@ -165,7 +165,7 @@ static const io_chain_t *real_io;
|
|||
/**
|
||||
Counts the number of non null pointers in the specified array
|
||||
*/
|
||||
static int builtin_count_args(wchar_t **argv)
|
||||
static int builtin_count_args(const wchar_t * const * argv)
|
||||
{
|
||||
int argc = 1;
|
||||
while (argv[argc] != NULL)
|
||||
|
@ -1752,6 +1752,307 @@ static int builtin_pwd(parser_t &parser, wchar_t **argv)
|
|||
}
|
||||
}
|
||||
|
||||
/* This is nearly identical to builtin_function, and is intended to be the successor (with no block manipulation, no function/end split) */
|
||||
int define_function(parser_t &parser, const wcstring_list_t &args, const wcstring &contents, wcstring *out_err)
|
||||
{
|
||||
assert(out_err != NULL);
|
||||
|
||||
/* Hackish const_cast matches the one in builtin_run */
|
||||
const null_terminated_array_t<wchar_t> argv_array(args);
|
||||
wchar_t **argv = const_cast<wchar_t **>(argv_array.get());
|
||||
|
||||
int argc = builtin_count_args(argv);
|
||||
int res=STATUS_BUILTIN_OK;
|
||||
wchar_t *desc=0;
|
||||
std::vector<event_t> events;
|
||||
std::auto_ptr<wcstring_list_t> named_arguments(NULL);
|
||||
|
||||
wchar_t *name = 0;
|
||||
bool shadows = true;
|
||||
|
||||
woptind=0;
|
||||
|
||||
const struct woption long_options[] =
|
||||
{
|
||||
{ L"description", required_argument, 0, 'd' },
|
||||
{ L"on-signal", required_argument, 0, 's' },
|
||||
{ L"on-job-exit", required_argument, 0, 'j' },
|
||||
{ L"on-process-exit", required_argument, 0, 'p' },
|
||||
{ L"on-variable", required_argument, 0, 'v' },
|
||||
{ L"on-event", required_argument, 0, 'e' },
|
||||
{ L"help", no_argument, 0, 'h' },
|
||||
{ L"argument-names", no_argument, 0, 'a' },
|
||||
{ L"no-scope-shadowing", no_argument, 0, 'S' },
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
while (1 && (!res))
|
||||
{
|
||||
int opt_index = 0;
|
||||
|
||||
int opt = wgetopt_long(argc,
|
||||
argv,
|
||||
L"d:s:j:p:v:e:haS",
|
||||
long_options,
|
||||
&opt_index);
|
||||
if (opt == -1)
|
||||
break;
|
||||
|
||||
switch (opt)
|
||||
{
|
||||
case 0:
|
||||
if (long_options[opt_index].flag != 0)
|
||||
break;
|
||||
|
||||
|
||||
|
||||
append_format(*out_err,
|
||||
BUILTIN_ERR_UNKNOWN,
|
||||
argv[0],
|
||||
long_options[opt_index].name);
|
||||
|
||||
res = 1;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
desc=woptarg;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
{
|
||||
int sig = wcs2sig(woptarg);
|
||||
|
||||
if (sig < 0)
|
||||
{
|
||||
append_format(*out_err,
|
||||
_(L"%ls: Unknown signal '%ls'\n"),
|
||||
argv[0],
|
||||
woptarg);
|
||||
res=1;
|
||||
break;
|
||||
}
|
||||
events.push_back(event_t::signal_event(sig));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'v':
|
||||
{
|
||||
if (wcsvarname(woptarg))
|
||||
{
|
||||
append_format(*out_err,
|
||||
_(L"%ls: Invalid variable name '%ls'\n"),
|
||||
argv[0],
|
||||
woptarg);
|
||||
res=STATUS_BUILTIN_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
events.push_back(event_t::variable_event(woptarg));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 'e':
|
||||
{
|
||||
events.push_back(event_t::generic_event(woptarg));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'j':
|
||||
case 'p':
|
||||
{
|
||||
pid_t pid;
|
||||
wchar_t *end;
|
||||
event_t e(EVENT_ANY);
|
||||
|
||||
if ((opt == 'j') &&
|
||||
(wcscasecmp(woptarg, L"caller") == 0))
|
||||
{
|
||||
int job_id = -1;
|
||||
|
||||
if (is_subshell)
|
||||
{
|
||||
size_t block_idx = 0;
|
||||
|
||||
/* Find the outermost substitution block */
|
||||
for (block_idx = 0; ; block_idx++)
|
||||
{
|
||||
const block_t *b = parser.block_at_index(block_idx);
|
||||
if (b == NULL || b->type() == SUBST)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Go one step beyond that, to get to the caller */
|
||||
const block_t *caller_block = parser.block_at_index(block_idx + 1);
|
||||
if (caller_block != NULL && caller_block->job != NULL)
|
||||
{
|
||||
job_id = caller_block->job->job_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (job_id == -1)
|
||||
{
|
||||
append_format(*out_err,
|
||||
_(L"%ls: Cannot find calling job for event handler\n"),
|
||||
argv[0]);
|
||||
res=1;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.type = EVENT_JOB_ID;
|
||||
e.param1.job_id = job_id;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = 0;
|
||||
pid = fish_wcstoi(woptarg, &end, 10);
|
||||
if (errno || !end || *end)
|
||||
{
|
||||
append_format(*out_err,
|
||||
_(L"%ls: Invalid process id %ls\n"),
|
||||
argv[0],
|
||||
woptarg);
|
||||
res=1;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
e.type = EVENT_EXIT;
|
||||
e.param1.pid = (opt=='j'?-1:1)*abs(pid);
|
||||
}
|
||||
if (res)
|
||||
{
|
||||
/* nothing */
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back(e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'a':
|
||||
if (named_arguments.get() == NULL)
|
||||
named_arguments.reset(new wcstring_list_t);
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
shadows = 0;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
builtin_print_help(parser, argv[0], stdout_buffer);
|
||||
return STATUS_BUILTIN_OK;
|
||||
|
||||
case '?':
|
||||
builtin_unknown_option(parser, argv[0], argv[woptind-1]);
|
||||
res = 1;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!res)
|
||||
{
|
||||
|
||||
if (argc == woptind)
|
||||
{
|
||||
append_format(*out_err,
|
||||
_(L"%ls: Expected function name\n"),
|
||||
argv[0]);
|
||||
res=1;
|
||||
}
|
||||
else if (wcsfuncname(argv[woptind]))
|
||||
{
|
||||
append_format(*out_err,
|
||||
_(L"%ls: Illegal function name '%ls'\n"),
|
||||
argv[0],
|
||||
argv[woptind]);
|
||||
|
||||
res=1;
|
||||
}
|
||||
else if (parser_keywords_is_reserved(argv[woptind]))
|
||||
{
|
||||
|
||||
append_format(*out_err,
|
||||
_(L"%ls: The name '%ls' is reserved,\nand can not be used as a function name\n"),
|
||||
argv[0],
|
||||
argv[woptind]);
|
||||
|
||||
res=1;
|
||||
}
|
||||
else if (! wcslen(argv[woptind]))
|
||||
{
|
||||
append_format(*out_err, _(L"%ls: No function name given\n"), argv[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
name = argv[woptind++];
|
||||
|
||||
if (named_arguments.get())
|
||||
{
|
||||
while (woptind < argc)
|
||||
{
|
||||
if (wcsvarname(argv[woptind]))
|
||||
{
|
||||
append_format(*out_err,
|
||||
_(L"%ls: Invalid variable name '%ls'\n"),
|
||||
argv[0],
|
||||
argv[woptind]);
|
||||
res = STATUS_BUILTIN_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
named_arguments->push_back(argv[woptind++]);
|
||||
}
|
||||
}
|
||||
else if (woptind != argc)
|
||||
{
|
||||
append_format(*out_err,
|
||||
_(L"%ls: Expected one argument, got %d\n"),
|
||||
argv[0],
|
||||
argc);
|
||||
res=1;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res)
|
||||
{
|
||||
builtin_print_help(parser, argv[0], *out_err);
|
||||
}
|
||||
else
|
||||
{
|
||||
function_data_t d;
|
||||
|
||||
d.name = name;
|
||||
if (desc)
|
||||
d.description = desc;
|
||||
d.events.swap(events);
|
||||
d.shadows = shadows;
|
||||
if (named_arguments.get())
|
||||
d.named_arguments.swap(*named_arguments);
|
||||
|
||||
for (size_t i=0; i<d.events.size(); i++)
|
||||
{
|
||||
event_t &e = d.events.at(i);
|
||||
e.function_name = d.name;
|
||||
}
|
||||
|
||||
d.definition = contents.c_str();
|
||||
|
||||
// TODO: fix def_offset inside function_add
|
||||
function_add(d, parser);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
The function builtin, used for providing subroutines.
|
||||
It calls various functions from function.c to perform any heavy lifting.
|
||||
|
@ -1983,6 +2284,10 @@ static int builtin_function(parser_t &parser, wchar_t **argv)
|
|||
|
||||
res=1;
|
||||
}
|
||||
else if (! wcslen(argv[woptind]))
|
||||
{
|
||||
append_format(stderr_buffer, _(L"%ls: No function name given\n"), argv[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
|
|
@ -176,7 +176,10 @@ const wchar_t *builtin_complete_get_temporary_buffer();
|
|||
Run the __fish_print_help function to obtain the help information
|
||||
for the specified command.
|
||||
*/
|
||||
|
||||
wcstring builtin_help_get(parser_t &parser, const wchar_t *cmd);
|
||||
|
||||
/** Defines a function, like builtin_function. Returns 0 on success. */
|
||||
int define_function(parser_t &parser, const wcstring_list_t &args, const wcstring &contents, wcstring *out_err);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
51
exec.cpp
51
exec.cpp
|
@ -394,12 +394,13 @@ static void io_cleanup_fds(const std::vector<int> &opened_fds)
|
|||
repeatedly reopened for every command in the block, which would
|
||||
reset the cursor position.
|
||||
|
||||
\return the transmogrified chain on sucess, or 0 on failiure
|
||||
\return true on success, false on failure. Returns the output chain and opened_fds by reference
|
||||
*/
|
||||
static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t &out_chain, std::vector<int> &out_opened_fds)
|
||||
static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t *out_chain, std::vector<int> *out_opened_fds)
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
assert(out_chain.empty());
|
||||
assert(out_chain != NULL && out_opened_fds != NULL);
|
||||
assert(out_chain->empty());
|
||||
|
||||
/* Just to be clear what we do for an empty chain */
|
||||
if (in_chain.empty())
|
||||
|
@ -479,8 +480,8 @@ static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t &out_chain, s
|
|||
if (success)
|
||||
{
|
||||
/* Yay */
|
||||
out_chain.swap(result_chain);
|
||||
out_opened_fds.swap(opened_fds);
|
||||
out_chain->swap(result_chain);
|
||||
out_opened_fds->swap(opened_fds);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -496,19 +497,24 @@ static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t &out_chain, s
|
|||
Morph an io redirection chain into redirections suitable for
|
||||
passing to eval, call eval, and clean up morphed redirections.
|
||||
|
||||
\param def the code to evaluate
|
||||
\param def the code to evaluate, or the empty string if none
|
||||
\param node_offset the offset of the node to evalute, or NODE_OFFSET_INVALID
|
||||
\param block_type the type of block to push on evaluation
|
||||
\param io the io redirections to be performed on this block
|
||||
*/
|
||||
|
||||
static void internal_exec_helper(parser_t &parser,
|
||||
const wchar_t *def,
|
||||
const wcstring &def,
|
||||
node_offset_t node_offset,
|
||||
enum block_type_t block_type,
|
||||
const io_chain_t &ios)
|
||||
{
|
||||
// If we have a valid node offset, then we must not have a string to execute
|
||||
assert(node_offset == NODE_OFFSET_INVALID || def.empty());
|
||||
|
||||
io_chain_t morphed_chain;
|
||||
std::vector<int> opened_fds;
|
||||
bool transmorgrified = io_transmogrify(ios, morphed_chain, opened_fds);
|
||||
bool transmorgrified = io_transmogrify(ios, &morphed_chain, &opened_fds);
|
||||
|
||||
int is_block_old=is_block;
|
||||
is_block=1;
|
||||
|
@ -524,7 +530,14 @@ static void internal_exec_helper(parser_t &parser,
|
|||
|
||||
signal_unblock();
|
||||
|
||||
parser.eval(def, morphed_chain, block_type);
|
||||
if (node_offset == NODE_OFFSET_INVALID)
|
||||
{
|
||||
parser.eval(def, morphed_chain, block_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
parser.eval_block_node(node_offset, morphed_chain, block_type);
|
||||
}
|
||||
|
||||
signal_block();
|
||||
|
||||
|
@ -926,7 +939,7 @@ void exec_job(parser_t &parser, job_t *j)
|
|||
|
||||
if (! exec_error)
|
||||
{
|
||||
internal_exec_helper(parser, def.c_str(), TOP, process_net_io_chain);
|
||||
internal_exec_helper(parser, def, NODE_OFFSET_INVALID, TOP, process_net_io_chain);
|
||||
}
|
||||
|
||||
parser.allow_function();
|
||||
|
@ -936,12 +949,14 @@ void exec_job(parser_t &parser, job_t *j)
|
|||
}
|
||||
|
||||
case INTERNAL_BLOCK:
|
||||
case INTERNAL_BLOCK_NODE:
|
||||
{
|
||||
if (p->next)
|
||||
{
|
||||
block_output_io_buffer.reset(io_buffer_t::create(0));
|
||||
if (block_output_io_buffer.get() == NULL)
|
||||
{
|
||||
/* We failed (e.g. no more fds could be created). */
|
||||
exec_error = true;
|
||||
job_mark_process_as_failed(j, p);
|
||||
}
|
||||
|
@ -954,12 +969,21 @@ void exec_job(parser_t &parser, job_t *j)
|
|||
|
||||
if (! exec_error)
|
||||
{
|
||||
internal_exec_helper(parser, p->argv0(), TOP, process_net_io_chain);
|
||||
if (p->type == INTERNAL_BLOCK)
|
||||
{
|
||||
/* The block contents (as in, fish code) are stored in argv0 (ugh) */
|
||||
assert(p->argv0() != NULL);
|
||||
internal_exec_helper(parser, p->argv0(), NODE_OFFSET_INVALID, TOP, process_net_io_chain);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(p->type == INTERNAL_BLOCK_NODE);
|
||||
internal_exec_helper(parser, wcstring(), p->internal_block_node, TOP, process_net_io_chain);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
case INTERNAL_BUILTIN:
|
||||
{
|
||||
int builtin_stdin=0;
|
||||
|
@ -1115,6 +1139,7 @@ void exec_job(parser_t &parser, job_t *j)
|
|||
{
|
||||
|
||||
case INTERNAL_BLOCK:
|
||||
case INTERNAL_BLOCK_NODE:
|
||||
case INTERNAL_FUNCTION:
|
||||
{
|
||||
int status = proc_get_last_status();
|
||||
|
|
|
@ -186,7 +186,6 @@ void function_add(const function_data_t &data, const parser_t &parser)
|
|||
/* Remove the old function */
|
||||
function_remove(data.name);
|
||||
|
||||
|
||||
/* Create and store a new function */
|
||||
const wchar_t *filename = reader_current_filename();
|
||||
|
||||
|
|
|
@ -48,7 +48,196 @@ bool parse_execution_context_t::should_cancel() const
|
|||
return false;
|
||||
}
|
||||
|
||||
void parse_execution_context_t::run_while_process(const parse_node_t &header, const parse_node_t &statement)
|
||||
int parse_execution_context_t::run_if_statement(const parse_node_t &statement)
|
||||
{
|
||||
assert(statement.type == symbol_if_statement);
|
||||
|
||||
/* Push an if block */
|
||||
if_block_t *ib = new if_block_t();
|
||||
ib->node_offset = this->get_offset(statement);
|
||||
parser->push_block(ib);
|
||||
|
||||
/* We have a sequence of if clauses, with a final else, resulting in a single job list that we execute */
|
||||
const parse_node_t *job_list_to_execute = NULL;
|
||||
const parse_node_t *if_clause = get_child(statement, 0, symbol_if_clause);
|
||||
const parse_node_t *else_clause = get_child(statement, 1, symbol_else_clause);
|
||||
for (;;)
|
||||
{
|
||||
assert(if_clause != NULL && else_clause != NULL);
|
||||
const parse_node_t &condition = *get_child(*if_clause, 1, symbol_job);
|
||||
fprintf(stderr, "run %ls\n", get_source(condition).c_str());
|
||||
if (run_1_job(condition) == EXIT_SUCCESS)
|
||||
{
|
||||
/* condition succeeded */
|
||||
job_list_to_execute = get_child(*if_clause, 3, symbol_job_list);
|
||||
break;
|
||||
}
|
||||
else if (else_clause->child_count > 0)
|
||||
{
|
||||
/* 'if' condition failed, no else clause, we're done */
|
||||
job_list_to_execute = NULL;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We have an 'else continuation' (either else-if or else) */
|
||||
const parse_node_t &else_cont = *get_child(*else_clause, 1, symbol_else_continuation);
|
||||
assert(else_cont.production_idx < 2);
|
||||
if (else_cont.production_idx == 0)
|
||||
{
|
||||
/* it's an 'else if', go to the next one */
|
||||
if_clause = get_child(else_cont, 0, symbol_if_clause);
|
||||
else_clause = get_child(else_cont, 1, symbol_else_clause);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* it's the final 'else', we're done */
|
||||
assert(else_cont.production_idx == 1);
|
||||
job_list_to_execute = get_child(else_cont, 1, symbol_job_list);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Execute any job list we got */
|
||||
if (job_list_to_execute != NULL)
|
||||
{
|
||||
run_job_list(*job_list_to_execute);
|
||||
}
|
||||
|
||||
/* Done */
|
||||
parser->pop_block(ib);
|
||||
|
||||
return proc_get_last_status();
|
||||
}
|
||||
|
||||
int parse_execution_context_t::run_begin_statement(const parse_node_t &header, const parse_node_t &contents)
|
||||
{
|
||||
assert(header.type == symbol_begin_header);
|
||||
assert(contents.type == symbol_job_list);
|
||||
|
||||
/* Basic begin/end block. Push a scope block. */
|
||||
scope_block_t *sb = new scope_block_t(BEGIN);
|
||||
parser->push_block(sb);
|
||||
parser->current_block()->tok_pos = parser->get_pos();
|
||||
|
||||
/* Run the job list */
|
||||
run_job_list(contents);
|
||||
|
||||
/* Pop the block */
|
||||
parser->pop_block(sb);
|
||||
|
||||
return proc_get_last_status();
|
||||
}
|
||||
|
||||
/* Define a function */
|
||||
int parse_execution_context_t::run_function_statement(const parse_node_t &header, const parse_node_t &contents)
|
||||
{
|
||||
assert(header.type == symbol_function_header);
|
||||
assert(contents.type == symbol_job_list);
|
||||
|
||||
/* Get arguments */
|
||||
const parse_node_t *unmatched_wildcard = NULL;
|
||||
const wcstring_list_t argument_list = this->determine_arguments(header, &unmatched_wildcard);
|
||||
|
||||
bool errored = false;
|
||||
if (unmatched_wildcard != NULL)
|
||||
{
|
||||
errored = append_unmatched_wildcard_error(*unmatched_wildcard);
|
||||
}
|
||||
|
||||
if (! errored)
|
||||
{
|
||||
const wcstring contents_str = get_source(contents);
|
||||
wcstring error_str;
|
||||
int err = define_function(*parser, argument_list, contents_str, &error_str);
|
||||
proc_set_last_status(err);
|
||||
}
|
||||
return proc_get_last_status();
|
||||
}
|
||||
|
||||
int parse_execution_context_t::run_block_statement(const parse_node_t &statement)
|
||||
{
|
||||
assert(statement.type == symbol_block_statement);
|
||||
|
||||
const parse_node_t &block_header = *get_child(statement, 0, symbol_block_header); //block header
|
||||
const parse_node_t &header = *get_child(block_header, 0); //specific header type (e.g. for loop)
|
||||
const parse_node_t &contents = *get_child(statement, 2, symbol_job_list); //block contents
|
||||
|
||||
int ret = 1;
|
||||
switch (header.type)
|
||||
{
|
||||
case symbol_for_header:
|
||||
ret = run_for_statement(header, contents);
|
||||
break;
|
||||
|
||||
case symbol_while_header:
|
||||
ret = run_while_statement(header, contents);
|
||||
break;
|
||||
|
||||
case symbol_function_header:
|
||||
ret = run_function_statement(header, contents);
|
||||
break;
|
||||
|
||||
case symbol_begin_header:
|
||||
ret = run_begin_statement(header, contents);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unexpected block header: %ls\n", header.describe().c_str());
|
||||
PARSER_DIE();
|
||||
break;
|
||||
}
|
||||
|
||||
return proc_get_last_status();
|
||||
}
|
||||
|
||||
int parse_execution_context_t::run_for_statement(const parse_node_t &header, const parse_node_t &block_contents)
|
||||
{
|
||||
assert(header.type == symbol_for_header);
|
||||
assert(block_contents.type == symbol_job_list);
|
||||
|
||||
/* get the variable name: `for var_name in ...` */
|
||||
const parse_node_t &var_name_node = *get_child(header, 1, parse_token_type_string);
|
||||
const wcstring for_var_name = get_source(var_name_node);
|
||||
|
||||
/* get the contents to iterate over */
|
||||
const parse_node_t *unmatched_wildcard = NULL;
|
||||
wcstring_list_t argument_list = this->determine_arguments(header, &unmatched_wildcard);
|
||||
|
||||
/* Here we could do something with unmatched_wildcard. However it seems nicer to not make for loops complain about this, i.e. just iterate over a potentially empty list */
|
||||
|
||||
for_block_t *fb = new for_block_t(for_var_name);
|
||||
parser->push_block(fb);
|
||||
fb->tok_pos = parser->get_pos();
|
||||
|
||||
/* Note that we store the sequence of values in opposite order */
|
||||
std::reverse(argument_list.begin(), argument_list.end());
|
||||
fb->sequence = argument_list;
|
||||
|
||||
/* Now drive the for loop. TODO: handle break, etc. */
|
||||
while (! fb->sequence.empty())
|
||||
{
|
||||
const wcstring &for_variable = fb->variable;
|
||||
const wcstring &val = fb->sequence.back();
|
||||
env_set(for_variable, val.c_str(), ENV_LOCAL);
|
||||
fb->sequence.pop_back();
|
||||
fb->loop_status = LOOP_NORMAL;
|
||||
fb->skip = 0;
|
||||
|
||||
this->run_job_list(block_contents);
|
||||
}
|
||||
|
||||
return proc_get_last_status();
|
||||
}
|
||||
|
||||
|
||||
int parse_execution_context_t::run_switch_statement(const parse_node_t &statement)
|
||||
{
|
||||
return proc_get_last_status();
|
||||
}
|
||||
|
||||
int parse_execution_context_t::run_while_statement(const parse_node_t &header, const parse_node_t &statement)
|
||||
{
|
||||
assert(header.type == symbol_while_header);
|
||||
assert(statement.type == symbol_block_statement);
|
||||
|
@ -71,6 +260,8 @@ void parse_execution_context_t::run_while_process(const parse_node_t &header, co
|
|||
|
||||
/* Done */
|
||||
parser->pop_block(wb);
|
||||
|
||||
return proc_get_last_status();
|
||||
}
|
||||
|
||||
/* Appends an error to the error list. Always returns true, so you can assign the result to an 'errored' variable */
|
||||
|
@ -90,6 +281,13 @@ bool parse_execution_context_t::append_error(const parse_node_t &node, const wch
|
|||
return true;
|
||||
}
|
||||
|
||||
/* Appends an unmatched wildcard error to the error list, and returns true. */
|
||||
bool parse_execution_context_t::append_unmatched_wildcard_error(const parse_node_t &unmatched_wildcard)
|
||||
{
|
||||
proc_set_last_status(STATUS_UNMATCHED_WILDCARD);
|
||||
return append_error(unmatched_wildcard, WILDCARD_ERR_MSG, get_source(unmatched_wildcard).c_str());
|
||||
}
|
||||
|
||||
/* Creates a 'normal' (non-block) process */
|
||||
process_t *parse_execution_context_t::create_plain_process(job_t *job, const parse_node_t &statement)
|
||||
{
|
||||
|
@ -122,8 +320,7 @@ process_t *parse_execution_context_t::create_plain_process(job_t *job, const par
|
|||
if (unmatched_wildcard != NULL)
|
||||
{
|
||||
job_set_flag(job, JOB_WILDCARD_ERROR, 1);
|
||||
proc_set_last_status(STATUS_UNMATCHED_WILDCARD);
|
||||
errored = append_error(*unmatched_wildcard, WILDCARD_ERR_MSG, unmatched_wildcard->get_source(src).c_str());
|
||||
errored = append_unmatched_wildcard_error(*unmatched_wildcard);
|
||||
}
|
||||
|
||||
if (errored)
|
||||
|
@ -179,7 +376,6 @@ process_t *parse_execution_context_t::create_plain_process(job_t *job, const par
|
|||
/* TODO: support fish_command_not_found, implicit cd, etc. here */
|
||||
errored = true;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Return the process, or NULL on error */
|
||||
|
@ -401,10 +597,10 @@ process_t *parse_execution_context_t::create_boolean_process(job_t *job, const p
|
|||
|
||||
process_t *parse_execution_context_t::create_block_process(job_t *job, const parse_node_t &statement_node)
|
||||
{
|
||||
/* We handle block statements by creating INTERNAL_BLOCKs, that will bounce back to us when it's time to execute them */
|
||||
/* We handle block statements by creating INTERNAL_BLOCK_NODE, that will bounce back to us when it's time to execute them */
|
||||
assert(statement_node.type == symbol_block_statement || statement_node.type == symbol_if_statement || statement_node.type == symbol_switch_statement);
|
||||
process_t *result = new process_t();
|
||||
result->type = INTERNAL_BLOCK;
|
||||
result->type = INTERNAL_BLOCK_NODE;
|
||||
result->internal_block_node = this->get_offset(statement_node);
|
||||
return result;
|
||||
}
|
||||
|
@ -513,7 +709,7 @@ int parse_execution_context_t::run_1_job(const parse_node_t &job_node)
|
|||
}
|
||||
}
|
||||
|
||||
/* Increment the eval_level for the duration of this command */
|
||||
/* Increment the eval_level for the duration of this command */
|
||||
scoped_push<int> saved_eval_level(&eval_level, eval_level + 1);
|
||||
|
||||
/* TODO: blocks-without-redirections optimization */
|
||||
|
@ -530,7 +726,7 @@ int parse_execution_context_t::run_1_job(const parse_node_t &job_node)
|
|||
start_time = get_time();
|
||||
}
|
||||
|
||||
job_t *j = parser->job_create(this->block_io);
|
||||
job_t *j = new job_t(acquire_job_id(), block_io);
|
||||
job_set_flag(j, JOB_FOREGROUND, 1);
|
||||
job_set_flag(j, JOB_TERMINAL, job_get_flag(j, JOB_CONTROL));
|
||||
job_set_flag(j, JOB_TERMINAL, job_get_flag(j, JOB_CONTROL) \
|
||||
|
@ -539,20 +735,20 @@ int parse_execution_context_t::run_1_job(const parse_node_t &job_node)
|
|||
|| is_block \
|
||||
|| is_event \
|
||||
|| (!get_is_interactive()));
|
||||
|
||||
parser->current_block()->job = j;
|
||||
job_set_flag(j, JOB_CONTROL,
|
||||
(job_control_mode==JOB_CONTROL_ALL) ||
|
||||
((job_control_mode == JOB_CONTROL_INTERACTIVE) && (get_is_interactive())));
|
||||
|
||||
/* Populate the job. This may fail for reasons like command_not_found */
|
||||
bool process_errored = ! this->populate_job_from_job_node(j, job_node);
|
||||
|
||||
/* If we errored, we have to clean up the job */
|
||||
|
||||
/* Clean up the job on failure */
|
||||
if (process_errored)
|
||||
{
|
||||
assert(parser->current_block()->job == j);
|
||||
parser->current_block()->job = NULL;
|
||||
job_free(j);
|
||||
delete j;
|
||||
j = NULL;
|
||||
}
|
||||
|
||||
|
||||
/* Store time it took to 'parse' the command */
|
||||
if (do_profile)
|
||||
{
|
||||
|
@ -563,6 +759,10 @@ int parse_execution_context_t::run_1_job(const parse_node_t &job_node)
|
|||
|
||||
if (! process_errored)
|
||||
{
|
||||
/* Success. Give the job to the parser - it will clean it up. */
|
||||
parser->job_add(j);
|
||||
parser->current_block()->job = j;
|
||||
|
||||
/* Check to see if this contained any external commands */
|
||||
bool job_contained_external_command = false;
|
||||
for (const process_t *proc = j->first_process; proc != NULL; proc = proc->next)
|
||||
|
@ -602,6 +802,13 @@ int parse_execution_context_t::run_1_job(const parse_node_t &job_node)
|
|||
/* Clean up jobs. Do this after we've determined the return value, since this may trigger event handlers */
|
||||
job_reap(0);
|
||||
|
||||
/* Output any errors (hack) */
|
||||
if (! this->errors.empty())
|
||||
{
|
||||
fprintf(stderr, "%ls\n", parse_errors_description(this->errors, this->src).c_str());
|
||||
this->errors.clear();
|
||||
}
|
||||
|
||||
/* All done */
|
||||
return ret;
|
||||
}
|
||||
|
@ -636,6 +843,7 @@ int parse_execution_context_t::run_job_list(const parse_node_t &job_list_node)
|
|||
break;
|
||||
|
||||
default: //if we get here, it means more productions have been added to job_list, which is bad
|
||||
fprintf(stderr, "Unexpected production in job_list: %lu\n", (unsigned long)job_list->production_idx);
|
||||
PARSER_DIE();
|
||||
}
|
||||
|
||||
|
@ -649,13 +857,53 @@ int parse_execution_context_t::run_job_list(const parse_node_t &job_list_node)
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
int parse_execution_context_t::eval_top_level_job_list()
|
||||
int parse_execution_context_t::eval_node_at_offset(node_offset_t offset)
|
||||
{
|
||||
if (tree.empty())
|
||||
return EXIT_FAILURE;
|
||||
bool log_it = false;
|
||||
|
||||
const parse_node_t &job_list = tree.at(0);
|
||||
assert(job_list.type == symbol_job_list);
|
||||
return this->run_job_list(job_list);
|
||||
/* Don't ever expect to have an empty tree if this is called */
|
||||
assert(! tree.empty());
|
||||
assert(offset < tree.size());
|
||||
|
||||
const parse_node_t &node = tree.at(offset);
|
||||
|
||||
if (log_it)
|
||||
{
|
||||
fprintf(stderr, "eval node: %ls\n", get_source(node).c_str());
|
||||
}
|
||||
|
||||
/* Currently, we only expect to execute the top level job list, or a block node. Assert that. */
|
||||
assert(node.type == symbol_job_list ||
|
||||
node.type == symbol_block_statement ||
|
||||
node.type == symbol_if_statement ||
|
||||
node.type == symbol_switch_statement);
|
||||
|
||||
int ret = 1;
|
||||
switch (node.type)
|
||||
{
|
||||
case symbol_job_list:
|
||||
/* We should only get a job list if it's top level. This is because this is the entry point for both top-level execution (the first node) and INTERNAL_BLOCK_NODE execution (which does block statements, but never job lists) */
|
||||
assert(offset == 0);
|
||||
ret = this->run_job_list(node);
|
||||
break;
|
||||
|
||||
case symbol_block_statement:
|
||||
ret = this->run_block_statement(node);
|
||||
break;
|
||||
|
||||
case symbol_if_statement:
|
||||
ret = this->run_if_statement(node);
|
||||
break;
|
||||
|
||||
case symbol_switch_statement:
|
||||
ret = this->run_switch_statement(node);
|
||||
break;
|
||||
|
||||
default:
|
||||
/* In principle, we could support other node types. However we never expect to be passed them - see above. */
|
||||
fprintf(stderr, "Unexpected node %ls found in %s\n", node.describe().c_str(), __FUNCTION__);
|
||||
PARSER_DIE();
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -35,18 +35,28 @@ class parse_execution_context_t
|
|||
|
||||
/* Report an error. Always returns true. */
|
||||
bool append_error(const parse_node_t &node, const wchar_t *fmt, ...);
|
||||
/* Wildcard error helper */
|
||||
bool append_unmatched_wildcard_error(const parse_node_t &unmatched_wildcard);
|
||||
|
||||
/* Utilities */
|
||||
wcstring get_source(const parse_node_t &node) const;
|
||||
const parse_node_t *get_child(const parse_node_t &parent, node_offset_t which, parse_token_type_t expected_type = token_type_invalid) const;
|
||||
node_offset_t get_offset(const parse_node_t &node) const;
|
||||
|
||||
/* These create process_t structures from statements */
|
||||
process_t *create_job_process(job_t *job, const parse_node_t &statement_node);
|
||||
process_t *create_boolean_process(job_t *job, const parse_node_t &bool_statement);
|
||||
process_t *create_plain_process(job_t *job, const parse_node_t &statement);
|
||||
process_t *create_block_process(job_t *job, const parse_node_t &statement_node);
|
||||
|
||||
void run_while_process(const parse_node_t &header, const parse_node_t &statement);
|
||||
/* These encapsulate the actual logic of various (block) statements. They just do what the statement says. */
|
||||
int run_block_statement(const parse_node_t &statement);
|
||||
int run_for_statement(const parse_node_t &header, const parse_node_t &contents);
|
||||
int run_if_statement(const parse_node_t &statement);
|
||||
int run_switch_statement(const parse_node_t &statement);
|
||||
int run_while_statement(const parse_node_t &header, const parse_node_t &contents);
|
||||
int run_function_statement(const parse_node_t &header, const parse_node_t &contents);
|
||||
int run_begin_statement(const parse_node_t &header, const parse_node_t &contents);
|
||||
|
||||
wcstring_list_t determine_arguments(const parse_node_t &parent, const parse_node_t **out_unmatched_wildcard_node);
|
||||
|
||||
|
@ -57,13 +67,11 @@ class parse_execution_context_t
|
|||
int run_job_list(const parse_node_t &job_list_node);
|
||||
bool populate_job_from_job_node(job_t *j, const parse_node_t &job_node);
|
||||
|
||||
void eval_next_stack_elem();
|
||||
|
||||
public:
|
||||
parse_execution_context_t(const parse_node_tree_t &t, const wcstring &s, const io_chain_t &io, parser_t *p);
|
||||
|
||||
/* Actually execute the job list described by the tree */
|
||||
int eval_top_level_job_list();
|
||||
/* Start executing at the given node offset, returning the exit status of the last process. */
|
||||
int eval_node_at_offset(node_offset_t offset);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -320,7 +320,7 @@ RESOLVE_ONLY(begin_header)
|
|||
|
||||
PRODUCTIONS(function_header) =
|
||||
{
|
||||
{KEYWORD(parse_keyword_function), parse_token_type_string, symbol_argument_list}
|
||||
{KEYWORD(parse_keyword_function), symbol_argument, symbol_argument_list}
|
||||
};
|
||||
RESOLVE_ONLY(function_header)
|
||||
|
||||
|
|
|
@ -997,6 +997,12 @@ bool parse_t::parse_internal(const wcstring &str, parse_tree_flags_t parse_flags
|
|||
queue[0] = queue[1];
|
||||
queue[1] = next_parse_token(&tok);
|
||||
|
||||
/* If we are leaving things unterminated, then don't pass parse_token_type_terminate */
|
||||
if (queue[0].type == parse_token_type_terminate && (parse_flags & parse_flag_leave_unterminated))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
/* Pass these two tokens. We know that queue[0] is valid; queue[1] may be invalid. */
|
||||
this->parser->accept_tokens(queue[0], queue[1]);
|
||||
|
||||
|
|
12
parse_tree.h
12
parse_tree.h
|
@ -65,7 +65,11 @@ enum
|
|||
parse_flag_include_comments = 1 << 1,
|
||||
|
||||
/* Indicate that the tokenizer should accept incomplete tokens */
|
||||
parse_flag_accept_incomplete_tokens = 1 << 2
|
||||
parse_flag_accept_incomplete_tokens = 1 << 2,
|
||||
|
||||
/* Indicate that the parser should not generate the terminate token, allowing an 'unfinished' tree where some nodes may have no productions. */
|
||||
parse_flag_leave_unterminated = 1 << 3
|
||||
|
||||
};
|
||||
typedef unsigned int parse_tree_flags_t;
|
||||
|
||||
|
@ -124,7 +128,7 @@ public:
|
|||
wcstring describe(void) const;
|
||||
|
||||
/* Constructor */
|
||||
explicit parse_node_t(parse_token_type_t ty) : type(ty), source_start(-1), source_length(0), parent(NODE_OFFSET_INVALID), child_start(0), child_count(0)
|
||||
explicit parse_node_t(parse_token_type_t ty) : type(ty), source_start(-1), source_length(0), parent(NODE_OFFSET_INVALID), child_start(0), child_count(0), production_idx(-1)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -244,7 +248,9 @@ public:
|
|||
for_header = FOR var_name IN argument_list
|
||||
while_header = WHILE job
|
||||
begin_header = BEGIN
|
||||
function_header = FUNCTION function_name argument_list
|
||||
|
||||
# Functions take arguments, and require at least one (the name)
|
||||
function_header = FUNCTION argument argument_list
|
||||
|
||||
# A boolean statement is AND or OR or NOT
|
||||
|
||||
|
|
|
@ -994,7 +994,7 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src, pars
|
|||
|
||||
// Parse the input string into a parse tree
|
||||
// Some errors are detected here
|
||||
bool parsed = parse_t::parse(buff_src, 0, &node_tree, &parse_errors);
|
||||
bool parsed = parse_t::parse(buff_src, parse_flag_leave_unterminated, &node_tree, &parse_errors);
|
||||
if (! parsed)
|
||||
{
|
||||
errored = true;
|
||||
|
|
68
parser.cpp
68
parser.cpp
|
@ -598,6 +598,12 @@ void parser_t::error(int ec, size_t p, const wchar_t *str, ...)
|
|||
va_start(va, str);
|
||||
err_buff = vformat_string(str, va);
|
||||
va_end(va);
|
||||
|
||||
if (parser_use_ast())
|
||||
{
|
||||
fprintf(stderr, "parser error: %ls\n", err_buff.c_str());
|
||||
err_buff.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1190,6 +1196,13 @@ int parser_t::is_help(const wchar_t *s, int min_match)
|
|||
(len >= (size_t)min_match && (wcsncmp(L"--help", s, len) == 0));
|
||||
}
|
||||
|
||||
void parser_t::job_add(job_t *job)
|
||||
{
|
||||
assert(job != NULL);
|
||||
assert(job->first_process != NULL);
|
||||
this->my_job_list.push_front(job);
|
||||
}
|
||||
|
||||
job_t *parser_t::job_create(const io_chain_t &io)
|
||||
{
|
||||
job_t *res = new job_t(acquire_job_id(), io);
|
||||
|
@ -2588,6 +2601,40 @@ void parser_t::eval_job(tokenizer_t *tok)
|
|||
int parser_t::eval_new_parser(const wcstring &cmd, const io_chain_t &io, enum block_type_t block_type)
|
||||
{
|
||||
CHECK_BLOCK(1);
|
||||
|
||||
/* Parse the source into a tree, if we can */
|
||||
parse_node_tree_t tree;
|
||||
if (! parse_t::parse(cmd, parse_flag_none, &tree, NULL))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Append to the execution context stack */
|
||||
parse_execution_context_t *ctx = new parse_execution_context_t(tree, cmd, io, this);
|
||||
execution_contexts.push_back(ctx);
|
||||
|
||||
/* Execute the first node */
|
||||
int result = 1;
|
||||
if (! tree.empty())
|
||||
{
|
||||
result = this->eval_block_node(0, io_chain_t(), block_type);
|
||||
}
|
||||
|
||||
/* Clean up the execution context stack */
|
||||
assert(! execution_contexts.empty() && execution_contexts.back() == ctx);
|
||||
execution_contexts.pop_back();
|
||||
delete ctx;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int parser_t::eval_block_node(node_offset_t node_idx, const io_chain_t &io, enum block_type_t block_type)
|
||||
{
|
||||
// Paranoia. It's a little frightening that we're given only a node_idx and we interpret this in the topmost execution context's tree. What happens if these were to be interleaved? Fortunately that cannot happen.
|
||||
parse_execution_context_t *ctx = execution_contexts.back();
|
||||
assert(ctx != NULL);
|
||||
|
||||
CHECK_BLOCK(1);
|
||||
|
||||
/* Only certain blocks are allowed */
|
||||
if ((block_type != TOP) &&
|
||||
|
@ -2600,29 +2647,13 @@ int parser_t::eval_new_parser(const wcstring &cmd, const io_chain_t &io, enum bl
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* Parse the source into a tree, if we can */
|
||||
parse_node_tree_t tree;
|
||||
if (! parse_t::parse(cmd, parse_flag_none, &tree, NULL))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Not sure why we reap jobs here */
|
||||
job_reap(0);
|
||||
|
||||
/* Append to the execution context stack */
|
||||
parse_execution_context_t *ctx = new parse_execution_context_t(tree, cmd, io, this);
|
||||
execution_contexts.push_back(ctx);
|
||||
|
||||
/* Start it up */
|
||||
const block_t * const start_current_block = current_block();
|
||||
this->push_block(new scope_block_t(block_type));
|
||||
int result = ctx->eval_top_level_job_list();
|
||||
|
||||
/* Clean up the execution context stack */
|
||||
assert(! execution_contexts.empty() && execution_contexts.back() == ctx);
|
||||
execution_contexts.pop_back();
|
||||
delete ctx;
|
||||
int result = ctx->eval_node_at_offset(node_idx);
|
||||
|
||||
/* Clean up the block stack */
|
||||
this->pop_block();
|
||||
|
@ -2643,6 +2674,7 @@ int parser_t::eval_new_parser(const wcstring &cmd, const io_chain_t &io, enum bl
|
|||
job_reap(0);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
int parser_t::eval(const wcstring &cmd_str, const io_chain_t &io, enum block_type_t block_type)
|
||||
|
@ -2983,7 +3015,7 @@ void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &erro
|
|||
assert(output != NULL);
|
||||
if (! errors.empty())
|
||||
{
|
||||
const parse_error_t err = errors.at(0);
|
||||
const parse_error_t &err = errors.at(0);
|
||||
|
||||
// Determine which line we're on
|
||||
assert(err.source_start <= src.size());
|
||||
|
|
6
parser.h
6
parser.h
|
@ -336,6 +336,9 @@ private:
|
|||
|
||||
/** Create a job */
|
||||
job_t *job_create(const io_chain_t &io);
|
||||
|
||||
/** Adds a job to the beginning of the job list. */
|
||||
void job_add(job_t *job);
|
||||
|
||||
public:
|
||||
std::vector<profile_item_t*> profile_items;
|
||||
|
@ -377,6 +380,9 @@ public:
|
|||
int eval(const wcstring &cmd_str, const io_chain_t &io, enum block_type_t block_type);
|
||||
int eval_new_parser(const wcstring &cmd, const io_chain_t &io, enum block_type_t block_type);
|
||||
|
||||
/** Evaluates a block node at the given node offset in the topmost execution context */
|
||||
int eval_block_node(node_offset_t node_idx, const io_chain_t &io, enum block_type_t block_type);
|
||||
|
||||
/**
|
||||
Evaluate line as a list of parameters, i.e. tokenize it and perform parameter expansion and cmdsubst execution on the tokens.
|
||||
The output is inserted into output.
|
||||
|
|
Loading…
Reference in New Issue
Block a user