aboutsummaryrefslogtreecommitdiffhomepage
path: root/parser.cpp
diff options
context:
space:
mode:
authorGravatar ridiculousfish <corydoras@ridiculousfish.com>2013-12-23 14:53:56 -0800
committerGravatar ridiculousfish <corydoras@ridiculousfish.com>2013-12-23 14:53:56 -0800
commitb6af3e51abd54963948d5523ce01459fadedebbf (patch)
tree6a4d364895f8b0ec96b5167423b01542dab33774 /parser.cpp
parentebc8bd6ff51762e7037a1e72feebc9ecdd0b0380 (diff)
Very early work in adopting new parser for actual execution of commands.
Not turned on yet.
Diffstat (limited to 'parser.cpp')
-rw-r--r--parser.cpp572
1 files changed, 399 insertions, 173 deletions
diff --git a/parser.cpp b/parser.cpp
index 31aff34f..2a34f0dc 100644
--- a/parser.cpp
+++ b/parser.cpp
@@ -1648,13 +1648,12 @@ void parser_t::parse_job_argument_list(process_t *p,
}
*/
-#if 0
process_t *parser_t::create_boolean_process(job_t *job, const parse_node_t &bool_statement, const parser_context_t &ctx)
{
// Handle a boolean statement
bool skip_job = false;
assert(bool_statement.type == symbol_boolean_statement);
- switch (specific_statement.production_idx)
+ switch (bool_statement.production_idx)
{
// These magic numbers correspond to productions for boolean_statement
case 0:
@@ -1689,15 +1688,280 @@ process_t *parser_t::create_boolean_process(job_t *job, const parse_node_t &bool
return result;
}
+process_t *parser_t::create_for_process(job_t *job, const parse_node_t &header, const parse_node_t &statement, const parser_context_t &ctx)
+{
+ return NULL;
+}
+
+process_t *parser_t::create_while_process(job_t *job, const parse_node_t &header, const parse_node_t &statement, const parser_context_t &ctx)
+{
+ return NULL;
+}
+
+process_t *parser_t::create_begin_process(job_t *job, const parse_node_t &header, const parse_node_t &statement, const parser_context_t &ctx)
+{
+ return NULL;
+}
+
+process_t *parser_t::create_plain_process(job_t *job, const parse_node_t &statement, const parser_context_t &ctx)
+{
+ /* Get the decoration */
+ assert(statement.type == symbol_plain_statement);
+
+ /* Get the command. We expect to always get it here. */
+ wcstring cmd;
+ bool got_cmd = ctx.tree.command_for_plain_statement(statement, ctx.src, &cmd);
+ assert(got_cmd);
+
+ /* Expand it as a command */
+ bool expanded = expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES);
+ if (! expanded)
+ {
+ error(SYNTAX_ERROR,
+ statement.source_start,
+ ILLEGAL_CMD_ERR_MSG,
+ cmd.c_str());
+ return 0;
+ }
+
+ /* The list of arguments. The command is the first argument. TODO: count hack */
+ const parse_node_t *unmatched_wildcard = NULL;
+ wcstring_list_t argument_list = this->determine_arguments(statement, &unmatched_wildcard, ctx);
+ argument_list.insert(argument_list.begin(), cmd);
+
+ /* We were not able to expand any wildcards. Here is the first one that failed */
+ if (unmatched_wildcard != NULL)
+ {
+ job_set_flag(job, JOB_WILDCARD_ERROR, 1);
+ proc_set_last_status(STATUS_UNMATCHED_WILDCARD);
+ error(EVAL_ERROR, unmatched_wildcard->source_start, WILDCARD_ERR_MSG, unmatched_wildcard->get_source(ctx.src).c_str());
+ }
+
+ /* The set of IO redirections that we construct for the process */
+ const io_chain_t process_io_chain = this->determine_io_chain(statement, ctx);
+
+ /* Determine the process type, which depends on the statement decoration (command, builtin, etc) */
+ enum parse_statement_decoration_t decoration = ctx.tree.decoration_for_plain_statement(statement);
+ enum process_type_t process_type = EXTERNAL;
+
+ /* exec hack */
+ if (decoration != parse_statement_decoration_command && cmd == L"exec")
+ {
+ /* Either 'builtin exec' or just plain 'exec', and definitely not 'command exec'. Note we don't allow overriding exec with a function. */
+ process_type = INTERNAL_EXEC;
+ }
+ else if (decoration == parse_statement_decoration_command)
+ {
+ /* Always a command */
+ process_type = EXTERNAL;
+ }
+ else if (decoration == parse_statement_decoration_builtin)
+ {
+ /* What happens if this builtin is not valid? */
+ process_type = INTERNAL_BUILTIN;
+ }
+ else if (function_exists(cmd))
+ {
+ process_type = INTERNAL_FUNCTION;
+ }
+ else if (builtin_exists(cmd))
+ {
+ process_type = INTERNAL_BUILTIN;
+ }
+ else
+ {
+ process_type = EXTERNAL;
+ }
+
+ wcstring actual_cmd;
+ if (process_type == EXTERNAL)
+ {
+ /* Determine the actual command. Need to support implicit cd here */
+ bool has_command = path_get_path(cmd, &actual_cmd);
+
+ if (! has_command)
+ {
+ /* TODO: support fish_command_not_found, implicit cd, etc. here */
+ }
+
+ }
+
+ /* Return the process */
+ process_t *result = new process_t();
+ result->type = process_type;
+ result->set_argv(argument_list);
+ result->set_io_chain(process_io_chain);
+ result->actual_cmd = actual_cmd;
+ return result;
+}
+
+/* Determine the list of arguments, expanding stuff. If we have a wildcard and none could be expanded, return the unexpandable wildcard node by reference. */
+wcstring_list_t parser_t::determine_arguments(const parse_node_t &statement, const parse_node_t **out_unmatched_wildcard_node, const parser_context_t &ctx)
+{
+ wcstring_list_t argument_list;
+
+ /* Whether we failed to match any wildcards, and succeeded in matching any wildcards */
+ bool unmatched_wildcard = false, matched_wildcard = false;
+
+ /* First node that failed to expand as a wildcard (if any) */
+ const parse_node_t *unmatched_wildcard_node = NULL;
+
+ /* Get all argument nodes underneath the statement */
+ const parse_node_tree_t::parse_node_list_t argument_nodes = ctx.tree.find_nodes(statement, symbol_argument);
+ argument_list.reserve(argument_nodes.size());
+ for (size_t i=0; i < argument_nodes.size(); i++)
+ {
+ const parse_node_t &arg_node = *argument_nodes.at(i);
+
+ /* Expect all arguments to have source */
+ assert(arg_node.has_source());
+ const wcstring arg_str = arg_node.get_source(ctx.src);
+
+ /* Expand this string */
+ std::vector<completion_t> arg_expanded;
+ int expand_ret = expand_string(arg_str, arg_expanded, 0);
+ switch (expand_ret)
+ {
+ case EXPAND_ERROR:
+ {
+ error(SYNTAX_ERROR,
+ arg_node.source_start,
+ _(L"Could not expand string '%ls'"),
+ arg_str.c_str());
+ break;
+ }
+
+ case EXPAND_WILDCARD_NO_MATCH:
+ {
+ /* Store the node that failed to expand */
+ unmatched_wildcard = true;
+ if (! unmatched_wildcard_node)
+ {
+ unmatched_wildcard_node = &arg_node;
+ }
+ break;
+ }
+
+ case EXPAND_WILDCARD_MATCH:
+ {
+ matched_wildcard = true;
+ break;
+ }
+
+ case EXPAND_OK:
+ {
+ break;
+ }
+ }
+
+ /* Now copy over any expanded arguments */
+ for (size_t i=0; i < arg_expanded.size(); i++)
+ {
+ argument_list.push_back(arg_expanded.at(i).completion);
+ }
+ }
+
+ /* Return if we had a wildcard problem */
+ if (unmatched_wildcard && ! matched_wildcard)
+ {
+ *out_unmatched_wildcard_node = unmatched_wildcard_node;
+ }
+
+ return argument_list;
+}
+
+io_chain_t parser_t::determine_io_chain(const parse_node_t &statement,const parser_context_t &ctx)
+{
+ io_chain_t result;
+
+ /* Get all redirection nodes underneath the statement */
+ const parse_node_tree_t::parse_node_list_t redirect_nodes = ctx.tree.find_nodes(statement, symbol_redirection);
+ for (size_t i=0; i < redirect_nodes.size(); i++)
+ {
+ const parse_node_t &redirect_node = *redirect_nodes.at(i);
+
+ int source_fd = -1; /* source fd */
+ wcstring target; /* file path or target fd */
+ enum token_type redirect_type = ctx.tree.type_for_redirection(redirect_node, ctx.src, &source_fd, &target);
+
+ /* PCA: I can't justify this EXPAND_SKIP_VARIABLES flag. It was like this when I got here. */
+ bool target_expanded = expand_one(target, no_exec ? EXPAND_SKIP_VARIABLES : 0);
+ if (! target_expanded || target.empty())
+ {
+ /* Should improve this error message */
+ error(SYNTAX_ERROR,
+ redirect_node.source_start,
+ _(L"Invalid redirection target: %ls"),
+ target.c_str());
+ }
+
+
+ /* Generate the actual IO redirection */
+ shared_ptr<io_data_t> new_io;
+ assert(redirect_type != TOK_NONE);
+ switch (redirect_type)
+ {
+ case TOK_REDIRECT_FD:
+ {
+ if (target == L"-")
+ {
+ new_io.reset(new io_close_t(source_fd));
+ }
+ else
+ {
+ wchar_t *end = NULL;
+ errno = 0;
+ int old_fd = fish_wcstoi(target.c_str(), &end, 10);
+ if (old_fd < 0 || errno || *end)
+ {
+ error(SYNTAX_ERROR,
+ redirect_node.source_start,
+ _(L"Requested redirection to something that is not a file descriptor %ls"),
+ target.c_str());
+ }
+ else
+ {
+ new_io.reset(new io_fd_t(source_fd, old_fd));
+ }
+ }
+ break;
+ }
+
+ case TOK_REDIRECT_OUT:
+ case TOK_REDIRECT_APPEND:
+ case TOK_REDIRECT_IN:
+ case TOK_REDIRECT_NOCLOB:
+ {
+ int oflags = oflags_for_redirection_type(redirect_type);
+ io_file_t *new_io_file = new io_file_t(source_fd, target, oflags);
+ new_io.reset(new_io_file);
+ break;
+ }
+
+ default:
+ {
+ // Should be unreachable
+ fprintf(stderr, "Unexpected redirection type %ld. aborting.\n", (long)redirect_type);
+ PARSER_DIE();
+ break;
+ }
+ }
+
+ /* Append the new_io if we got one */
+ if (new_io.get() != NULL)
+ {
+ result.push_back(new_io);
+ }
+ }
+ return result;
+}
+
/* Returns a process_t allocated with new. It's the caller's responsibility to delete it (!) */
process_t *parser_t::create_job_process(job_t *job, const parse_node_t &statement_node, const parser_context_t &ctx)
{
assert(statement_node.type == symbol_statement);
assert(statement_node.child_count == 1);
- // We may skip this job entirely, e.g. with an 'and' statement
- bool skip_job = false;
-
// Get the "specific statement" which is boolean / block / if / switch / decorated
const parse_node_t &specific_statement = *ctx.tree.get_child(statement_node, 0);
@@ -1732,7 +1996,7 @@ process_t *parser_t::create_job_process(job_t *job, const parse_node_t &statemen
break;
case symbol_begin_header:
-
+ result = this->create_begin_process(job, specific_header, specific_statement, ctx);
break;
default:
@@ -1740,17 +2004,22 @@ process_t *parser_t::create_job_process(job_t *job, const parse_node_t &statemen
PARSER_DIE();
break;
}
+ break;
+ }
+
+ case symbol_decorated_statement:
+ {
+ const parse_node_t &plain_statement = ctx.tree.find_child(specific_statement, symbol_plain_statement);
+ result = this->create_plain_process(job, plain_statement, ctx);
+ break;
}
+
+ default:
+ fprintf(stderr, "'%ls' not handled by new parser yet\n", specific_statement.describe().c_str());
}
- // expand_one command
- // handle booleans (and, not, or)
- // set INTERNAL_EXEC
- // implicit CD
-
- return proc;
+ return result;
}
-#endif
/**
Fully parse a single job. Does not call exec on it, but any command substitutions in the job will be executed.
@@ -2456,7 +2725,6 @@ static bool job_should_skip_elseif(const job_t *job, const block_t *current_bloc
Evaluates a job from a node tree.
*/
-#if 0
void parser_t::eval_job(const parse_node_t &job_node, const parser_context_t &ctx)
{
assert(job_node.type == symbol_job);
@@ -2499,7 +2767,7 @@ void parser_t::eval_job(const parse_node_t &job_node, const parser_context_t &ct
|| is_event \
|| (!get_is_interactive()));
- current_block->job = j;
+ current_block()->job = j;
/* Tell the job what its command is */
j->set_command(job_node.get_source(ctx.src));
@@ -2533,124 +2801,7 @@ void parser_t::eval_job(const parse_node_t &job_node, const parser_context_t &ct
last_process = last_process->next;
job_cont = ctx.tree.get_child(*job_cont, 2, symbol_job_continuation);
}
-
- bool skip = false;
- if (this->parse_job(j->first_process, j, job_node, ctx) && j->first_process->get_argv())
- {
- if (do_profile)
- {
- t2 = get_time();
- profile_item->cmd = j->command();
- profile_item->skipped=current_block->skip;
- }
-
- /* If we're an ELSEIF, then we may want to unskip, if we're skipping because of an IF */
- if (job_get_flag(j, JOB_ELSEIF))
- {
- bool skip_elseif = job_should_skip_elseif(j, current_block);
-
- /* Record that we're entering an elseif */
- if (! skip_elseif)
- {
- /* We must be an IF block here */
- assert(current_block->type() == IF);
- static_cast<if_block_t *>(current_block)->is_elseif_entry = true;
- }
-
- /* Record that in the block too. This is similar to what builtin_else does. */
- current_block->skip = skip_elseif;
- }
-
- skip = skip || current_block->skip;
- skip = skip || job_get_flag(j, JOB_WILDCARD_ERROR);
- skip = skip || job_get_flag(j, JOB_SKIP);
-
- if (!skip)
- {
- int was_builtin = 0;
- if (j->first_process->type==INTERNAL_BUILTIN && !j->first_process->next)
- was_builtin = 1;
- scoped_push<int> tokenizer_pos_push(&current_tokenizer_pos, job_begin_pos);
- exec_job(*this, j);
-
- /* Only external commands require a new fishd barrier */
- if (!was_builtin)
- set_proc_had_barrier(false);
- }
- else
- {
- this->skipped_exec(j);
- }
-
- if (do_profile)
- {
- t3 = get_time();
- profile_item->level=eval_level;
- profile_item->parse = (int)(t2-t1);
- profile_item->exec=(int)(t3-t2);
- }
-
- if (current_block->type() == WHILE)
- {
- while_block_t *wb = static_cast<while_block_t *>(current_block);
- switch (wb->status)
- {
- case WHILE_TEST_FIRST:
- {
- // PCA I added the 'wb->skip ||' part because we couldn't reliably
- // control-C out of loops like this: while test 1 -eq 1; end
- wb->skip = wb->skip || proc_get_last_status()!= 0;
- wb->status = WHILE_TESTED;
- }
- break;
- }
- }
-
- if (current_block->type() == IF)
- {
- if_block_t *ib = static_cast<if_block_t *>(current_block);
-
- if (ib->skip)
- {
- /* Nothing */
- }
- else if (! ib->if_expr_evaluated)
- {
- /* Execute the IF */
- bool if_result = (proc_get_last_status() == 0);
- ib->any_branch_taken = if_result;
-
- /* Don't execute if the expression failed */
- current_block->skip = ! if_result;
- ib->if_expr_evaluated = true;
- }
- else if (ib->is_elseif_entry && ! ib->any_branch_taken)
- {
- /* Maybe mark an ELSEIF branch as taken */
- bool elseif_taken = (proc_get_last_status() == 0);
- ib->any_branch_taken = elseif_taken;
- current_block->skip = ! elseif_taken;
- ib->is_elseif_entry = false;
- }
- }
-
- }
- else
- {
- /*
- This job could not be properly parsed. We free it
- instead, and set the status to 1. This should be
- rare, since most errors should be detected by the
- ahead of time validator.
- */
- job_free(j);
-
- proc_set_last_status(1);
- }
- current_block->job = 0;
- break;
}
-#endif
/**
Evaluates a job from the specified tokenizer. First calls
@@ -2889,10 +3040,118 @@ void parser_t::eval_job(tokenizer_t *tok)
}
job_reap(0);
+}
+static void push_all_children(std::vector<node_offset_t> *execution_stack, const parse_node_t &node)
+{
+ // push nodes in reverse order, so the first node ends up on top
+ unsigned child_idx = node.child_count;
+ while (child_idx--)
+ {
+ execution_stack->push_back(node.child_offset(child_idx));
+ }
+}
+
+void parser_t::execute_next(std::vector<node_offset_t> *execution_stack, const parser_context_t &ctx)
+{
+ assert(execution_stack != NULL);
+ assert(! execution_stack->empty());
+
+ /* Get the offset of the next node and remove it from the stack */
+ node_offset_t next_offset = execution_stack->back();
+ execution_stack->pop_back();
+
+ /* Get the node */
+ assert(next_offset < ctx.tree.size());
+ const parse_node_t &node = ctx.tree.at(next_offset);
+
+ /* Do something with it */
+ switch (node.type)
+ {
+ case symbol_job_list:
+ // These correspond to the three productions of job_list
+ switch (node.production_idx)
+ {
+ case 0: // empty
+ break;
+
+ case 1: //job, job_list
+ push_all_children(execution_stack, node);
+ break;
+
+ case 2: //blank line, job_list
+ execution_stack->push_back(node.child_offset(1));
+ break;
+
+ default: //if we get here, it means more productions have been added to job_list, which is bad
+ PARSER_DIE();
+ break;
+ }
+ break;
+
+ case symbol_job: //statement, job_continuation
+ push_all_children(execution_stack, node);
+ break;
+
+ case symbol_job_continuation:
+ switch (node.production_idx)
+ {
+ case 0: //empty
+ break;
+
+ case 1: //pipe, statement, job_continuation
+ execution_stack->push_back(node.child_offset(2));
+ execution_stack->push_back(node.child_offset(1));
+ break;
+
+ default:
+ PARSER_DIE();
+ break;
+ }
+ break;
+
+ }
+}
+
+/* Executes the job list at the given node offset */
+void parser_t::execute_job_list(node_offset_t idx, const parser_context_t &ctx)
+{
+ assert(idx < ctx.tree.size());
+
+ const parse_node_t *job_list = &ctx.tree.at(idx);
+ assert(job_list->type == symbol_job_list);
+ while (job_list != NULL)
+ {
+ // These correspond to the three productions of job_list
+ // Try pulling out a job
+ const parse_node_t *job = NULL;
+ switch (job_list->production_idx)
+ {
+ case 0: // empty
+ job_list = NULL;
+ break;
+
+ case 1: //job, job_list
+ job = ctx.tree.get_child(*job_list, 0, symbol_job);
+ job_list = ctx.tree.get_child(*job_list, 1, symbol_job_list);
+ break;
+
+ case 2: //blank line, job_list
+ job = NULL;
+ job_list = ctx.tree.get_child(*job_list, 1, symbol_job_list);
+ break;
+
+ default: //if we get here, it means more productions have been added to job_list, which is bad
+ PARSER_DIE();
+ }
+
+ if (job != NULL)
+ {
+ this->eval_job(*job, ctx);
+ }
+ }
}
-#if 0
int parser_t::eval2(const wcstring &cmd_str, const io_chain_t &io, enum block_type_t block_type)
{
parser_context_t mut_ctx;
@@ -2913,7 +3172,7 @@ int parser_t::eval2(const wcstring &cmd_str, const io_chain_t &io, enum block_ty
scoped_push<io_chain_t> block_io_push(&block_io, io);
scoped_push<wcstring_list_t> forbidden_function_push(&forbidden_function);
const size_t forbid_count = forbidden_function.size();
- const block_t *start_current_block = current_block;
+ const block_t * const start_current_block = this->current_block();
/* Do some stuff I haven't figured out yet */
job_reap(0);
@@ -2936,47 +3195,15 @@ int parser_t::eval2(const wcstring &cmd_str, const io_chain_t &io, enum block_ty
error_code = 0;
event_fire(NULL);
-
- /* Execute the top job list */
- assert(! ctx.tree.empty());
- const parse_node_t *job_list = &ctx.tree.at(0);
- assert(job_list->type == symbol_job_list);
- while (job_list != NULL)
- {
- // These correspond to the three productions of job_list
- // Try pulling out a job
- const parse_node_t *job = NULL;
- switch (job_list->production_idx)
- {
- case 0: // empty
- job_list = NULL;
- break;
-
- case 1: //job, job_list
- job = ctx.tree.get_child(*job_list, 0, symbol_job);
- job_list = ctx.tree.get_child(*job_list, 1, symbol_job_list);
- break;
-
- case 2: //blank line, job_list
- job = NULL;
- job_list = ctx.tree.get_child(*job_list, 1, symbol_job_list);
- break;
-
- default: //if we get here, it means more productions have been added to job_list, which is bad
- PARSER_DIE();
- }
-
- if (job != NULL)
- {
- this->eval_job(*job, ctx);
- }
- }
+
+ /* Execute the top level job list */
+ execute_job_list(0, ctx);
parser_t::pop_block();
- while (start_current_block != current_block)
+ while (start_current_block != this->current_block())
{
- if (current_block == 0)
+ if (this->current_block() == NULL)
{
debug(0,
_(L"End of block mismatch. Program terminating."));
@@ -2991,7 +3218,7 @@ int parser_t::eval2(const wcstring &cmd_str, const io_chain_t &io, enum block_ty
//debug( 2, L"Status %d\n", proc_get_last_status() );
debug(1,
- L"%ls", parser_t::get_block_desc(current_block->type()));
+ L"%ls", parser_t::get_block_desc(current_block()->type()));
debug(1,
BLOCK_END_ERR_MSG);
fwprintf(stderr, L"%ls", parser_t::current_line());
@@ -3022,7 +3249,6 @@ int parser_t::eval2(const wcstring &cmd_str, const io_chain_t &io, enum block_ty
return code;
}
-#endif
int parser_t::eval(const wcstring &cmd_str, const io_chain_t &io, enum block_type_t block_type)
{