fish-shell/parse_exec.cpp

455 lines
14 KiB
C++
Raw Normal View History

#include "parse_exec.h"
2013-06-11 16:37:51 +00:00
#include <stack>
struct exec_node_t
{
node_offset_t parse_node_idx;
node_offset_t body_parse_node_idx;
bool visited;
2013-06-23 09:09:46 +00:00
explicit exec_node_t(node_offset_t pni) : parse_node_idx(pni), body_parse_node_idx(NODE_OFFSET_INVALID), visited(false)
2013-06-23 09:09:46 +00:00
{
}
explicit exec_node_t(node_offset_t pni, node_offset_t body_pni) : parse_node_idx(pni), body_parse_node_idx(body_pni), visited(false)
2013-06-23 09:09:46 +00:00
{
}
2013-06-11 16:37:51 +00:00
};
exec_basic_statement_t::exec_basic_statement_t() : command_idx(0), decoration(decoration_plain)
2013-06-23 09:09:46 +00:00
{
}
2013-06-23 09:09:46 +00:00
2013-06-11 16:37:51 +00:00
class parse_exec_t
{
parse_node_tree_t parse_tree;
wcstring src;
/* The stack of nodes as we execute them */
2013-06-11 16:37:51 +00:00
std::vector<exec_node_t> exec_nodes;
/* The stack of commands being built */
std::vector<exec_basic_statement_t> assembling_statements;
/* Current visitor (very transient) */
struct parse_execution_visitor_t * visitor;
2013-06-23 09:09:46 +00:00
const parse_node_t &get_child(parse_node_t &parent, node_offset_t which) const
{
return parse_tree.at(parent.child_offset(which));
}
void pop_push_specific(node_offset_t idx1, node_offset_t idx2 = NODE_OFFSET_INVALID, node_offset_t idx3 = NODE_OFFSET_INVALID, node_offset_t idx4 = NODE_OFFSET_INVALID, node_offset_t idx5 = NODE_OFFSET_INVALID)
{
PARSE_ASSERT(! exec_nodes.empty());
// Figure out the offset of the children
exec_node_t &top = exec_nodes.back();
const parse_node_t &parse_node = parse_tree.at(top.parse_node_idx);
node_offset_t child_node_idx = parse_node.child_start;
// Remove the top node
exec_nodes.pop_back();
2013-06-23 09:09:46 +00:00
// Append the given children, backwards
const node_offset_t idxs[] = {idx5, idx4, idx3, idx2, idx1};
for (size_t q=0; q < sizeof idxs / sizeof *idxs; q++)
{
node_offset_t idx = idxs[q];
if (idx != (node_offset_t)(-1))
{
PARSE_ASSERT(idx < parse_node.child_count);
exec_nodes.push_back(exec_node_t(child_node_idx + idx));
2013-06-23 09:09:46 +00:00
}
}
}
void push(node_offset_t global_idx)
{
exec_nodes.push_back(exec_node_t(global_idx));
}
void push(const exec_node_t &node)
{
exec_nodes.push_back(node);
}
void pop_push(node_offset_t child_idx, node_offset_t child_count = 1)
{
PARSE_ASSERT(! exec_nodes.empty());
if (child_count == 0)
{
// No children, just remove the top node
exec_nodes.pop_back();
}
else
{
// Figure out the offset of the children
exec_node_t &top = exec_nodes.back();
const parse_node_t &parse_node = parse_tree.at(top.parse_node_idx);
PARSE_ASSERT(child_idx < parse_node.child_count);
node_offset_t child_node_idx = parse_node.child_start + child_idx;
// Remove the top node
exec_nodes.pop_back();
// Append the given children, backwards
node_offset_t cursor = child_count;
while (cursor--)
{
exec_nodes.push_back(exec_node_t(child_node_idx + cursor));
}
}
}
void pop()
{
PARSE_ASSERT(! exec_nodes.empty());
exec_nodes.pop_back();
2013-06-11 16:37:51 +00:00
}
void pop_push_all()
2013-06-11 16:37:51 +00:00
{
exec_node_t &top = exec_nodes.back();
const parse_node_t &parse_node = parse_tree.at(top.parse_node_idx);
pop_push(0, parse_node.child_count);
}
2013-06-23 09:09:46 +00:00
void assemble_1_argument_or_redirection(node_offset_t idx, exec_arguments_and_redirections_t *output) const
{
const parse_node_t &node = parse_tree.at(idx);
2013-06-23 09:09:46 +00:00
PARSE_ASSERT(output != NULL);
PARSE_ASSERT(node.type == symbol_argument_or_redirection);
PARSE_ASSERT(node.child_count == 1);
node_offset_t child_idx = node.child_offset(0);
const parse_node_t &child = parse_tree.at(child_idx);
switch (child.type)
{
case parse_token_type_string:
// Argument
{
exec_argument_t arg = exec_argument_t();
arg.parse_node_idx = child_idx;
2013-06-23 09:09:46 +00:00
output->arguments.push_back(arg);
}
break;
case parse_token_type_redirection:
// Redirection
2013-06-23 09:09:46 +00:00
{
exec_redirection_t redirect = exec_redirection_t();
redirect.parse_node_idx = child_idx;
2013-06-23 09:09:46 +00:00
output->redirections.push_back(redirect);
}
break;
default:
PARSER_DIE();
break;
}
}
2013-06-23 09:09:46 +00:00
void assemble_arguments_and_redirections(node_offset_t start_idx, exec_arguments_and_redirections_t *output) const
{
node_offset_t idx = start_idx;
for (;;)
{
const parse_node_t &node = parse_tree.at(idx);
PARSE_ASSERT(node.type == symbol_arguments_or_redirections_list);
PARSE_ASSERT(node.child_count == 0 || node.child_count == 2);
if (node.child_count == 0)
{
// No more children
break;
}
else
{
// Skip to next child
assemble_1_argument_or_redirection(node.child_offset(0), output);
idx = node.child_offset(1);
}
}
}
void assemble_command_for_plain_statement(node_offset_t idx, parse_keyword_t decoration)
{
const parse_node_t &node = parse_tree.at(idx);
PARSE_ASSERT(node.type == symbol_plain_statement);
PARSE_ASSERT(node.child_count == 2);
exec_basic_statement_t statement;
statement.set_decoration(decoration);
statement.command_idx = node.child_offset(0);
assemble_arguments_and_redirections(node.child_offset(1), &statement.arguments_and_redirections);
visitor->visit_basic_statement(statement);
2013-06-23 09:09:46 +00:00
}
void assemble_block_statement(node_offset_t parse_node_idx)
{
2013-06-11 16:37:51 +00:00
const parse_node_t &node = parse_tree.at(parse_node_idx);
PARSE_ASSERT(node.type == symbol_block_statement);
PARSE_ASSERT(node.child_count == 5);
// Fetch arguments and redirections. These ought to be evaluated before the job list
exec_block_statement_t statement;
assemble_arguments_and_redirections(node.child_offset(4), &statement.arguments_and_redirections);
// Generic visit
visitor->enter_block_statement(statement);
// Dig into the header to discover the type
const parse_node_t &header_parent = parse_tree.at(node.child_offset(0));
PARSE_ASSERT(header_parent.type == symbol_block_header);
PARSE_ASSERT(header_parent.child_count == 1);
const node_offset_t header_idx = header_parent.child_offset(0);
// Fetch body (job list)
node_offset_t body_idx = node.child_offset(2);
PARSE_ASSERT(parse_tree.at(body_idx).type == symbol_job_list);
pop();
push(exec_node_t(header_idx, body_idx));
}
void assemble_function_header(const exec_node_t &exec_node, const parse_node_t &header)
{
PARSE_ASSERT(header.type == symbol_function_header);
PARSE_ASSERT(&header == &parse_tree.at(exec_node.parse_node_idx));
PARSE_ASSERT(exec_node.body_parse_node_idx != NODE_OFFSET_INVALID);
exec_function_header_t function_info;
function_info.name_idx = header.child_offset(1);
function_info.body_idx = exec_node.body_parse_node_idx;
assemble_arguments_and_redirections(header.child_offset(2), &function_info.arguments_and_redirections);
visitor->visit_function(function_info);
// Always pop
pop();
}
void assemble_if_header(exec_node_t &exec_node, const parse_node_t &header)
{
PARSE_ASSERT(header.type == symbol_if_header);
PARSE_ASSERT(&header == &parse_tree.at(exec_node.parse_node_idx));
PARSE_ASSERT(exec_node.body_parse_node_idx != NODE_OFFSET_INVALID);
if_header_t if_header;
if_header.body = exec_node.body_parse_node_idx;
// We may hit this on enter or exit
if (! exec_node.visited)
{
// Entry. Don't pop the header - just push the job. We'll pop it on exit.
exec_node.visited = true;
visitor->enter_if_header(if_header);
push(header.child_offset(1));
}
else
{
// Exit. Pop it.
visitor->exit_if_header(if_header);
pop();
}
2013-06-11 16:37:51 +00:00
}
void enter_parse_node(size_t idx);
void run_top_node(void);
public:
void get_node_string(node_offset_t idx, wcstring *output) const
{
const parse_node_t &node = parse_tree.at(idx);
PARSE_ASSERT(node.source_start <= src.size());
PARSE_ASSERT(node.source_start + node.source_length <= src.size());
output->assign(src, node.source_start, node.source_length);
}
bool visit_next_node(parse_execution_visitor_t *v);
parse_exec_t(const parse_node_tree_t &tree, const wcstring &s) : parse_tree(tree), src(s), visitor(NULL)
{
if (! parse_tree.empty())
{
exec_nodes.push_back(exec_node_t(0));
}
}
2013-06-11 16:37:51 +00:00
};
void parse_exec_t::run_top_node()
{
PARSE_ASSERT(! exec_nodes.empty());
exec_node_t &exec_node = exec_nodes.back();
const node_offset_t parse_node_idx = exec_node.parse_node_idx;
const parse_node_t &parse_node = parse_tree.at(exec_node.parse_node_idx);
2013-06-23 09:09:46 +00:00
bool log = true;
if (log)
{
wcstring tmp;
tmp.append(exec_nodes.size(), L' ');
tmp.append(parse_node.describe());
printf("%ls\n", tmp.c_str());
}
2013-06-11 16:37:51 +00:00
switch (parse_node.type)
{
2013-06-23 09:09:46 +00:00
case symbol_job_list:
2013-06-11 16:37:51 +00:00
PARSE_ASSERT(parse_node.child_count == 0 || parse_node.child_count == 2);
2013-06-23 09:09:46 +00:00
if (parse_node.child_count == 0)
{
// No more jobs, done
visitor->exit_job_list();
2013-06-23 09:09:46 +00:00
pop();
}
else if (parse_tree.at(parse_node.child_start + 0).type == parse_token_type_end)
{
// Empty job, so just skip it
pop_push(1, 1);
}
else
{
// Normal job
visitor->enter_job_list();
2013-06-23 09:09:46 +00:00
pop_push(0, 2);
}
2013-06-11 16:37:51 +00:00
break;
2013-06-23 09:09:46 +00:00
case symbol_job:
{
2013-06-23 09:09:46 +00:00
PARSE_ASSERT(parse_node.child_count == 2);
visitor->enter_job();
2013-06-23 09:09:46 +00:00
pop_push_all();
break;
}
case symbol_job_continuation:
PARSE_ASSERT(parse_node.child_count == 0 || parse_node.child_count == 3);
if (parse_node.child_count == 0)
{
2013-06-23 09:09:46 +00:00
// All done with this job
visitor->exit_job();
pop();
}
else
{
2013-06-23 09:09:46 +00:00
// Skip the pipe
pop_push(1, 2);
}
2013-06-23 09:09:46 +00:00
break;
case symbol_statement:
{
PARSE_ASSERT(parse_node.child_count == 1);
pop_push_all();
break;
}
case symbol_block_statement:
{
PARSE_ASSERT(parse_node.child_count == 5);
assemble_block_statement(parse_node_idx);
2013-06-23 09:09:46 +00:00
break;
}
case symbol_block_header:
{
PARSE_ASSERT(parse_node.child_count == 1);
pop_push_all();
break;
}
case symbol_function_header:
{
PARSE_ASSERT(parse_node.child_count == 3);
assemble_function_header(exec_node, parse_node);
break;
}
case symbol_if_header:
{
PARSE_ASSERT(parse_node.child_count == 2);
assemble_if_header(exec_node, parse_node);
2013-06-11 16:37:51 +00:00
break;
}
2013-06-11 16:37:51 +00:00
case symbol_decorated_statement:
{
2013-06-23 09:09:46 +00:00
PARSE_ASSERT(parse_node.child_count == 1 || parse_node.child_count == 2);
2013-06-23 09:09:46 +00:00
node_offset_t plain_statement_idx = parse_node.child_offset(parse_node.child_count - 1);
parse_keyword_t decoration = static_cast<parse_keyword_t>(parse_node.tag);
assemble_command_for_plain_statement(plain_statement_idx, decoration);
pop();
break;
}
2013-06-23 09:09:46 +00:00
// The following symbols should be handled by their parents, i.e. never pushed on our stack
case symbol_plain_statement:
case symbol_arguments_or_redirections_list:
case symbol_argument_or_redirection:
fprintf(stderr, "Unhandled token type %ls at index %ld\n", token_type_description(parse_node.type).c_str(), exec_node.parse_node_idx);
2013-06-23 09:09:46 +00:00
PARSER_DIE();
break;
case parse_token_type_end:
PARSE_ASSERT(parse_node.child_count == 0);
pop();
2013-06-11 16:37:51 +00:00
break;
default:
fprintf(stderr, "Unhandled token type %ls at index %ld\n", token_type_description(parse_node.type).c_str(), exec_node.parse_node_idx);
2013-06-11 16:37:51 +00:00
PARSER_DIE();
break;
}
}
bool parse_exec_t::visit_next_node(parse_execution_visitor_t *v)
{
PARSE_ASSERT(v != NULL);
PARSE_ASSERT(visitor == NULL);
if (exec_nodes.empty())
{
return false;
}
visitor = v;
run_top_node();
visitor = NULL;
return true;
}
2013-06-11 16:37:51 +00:00
void parse_exec_t::enter_parse_node(size_t idx)
{
PARSE_ASSERT(idx < parse_tree.size());
exec_node_t exec(idx);
exec_nodes.push_back(exec);
}
parse_execution_context_t::parse_execution_context_t(const parse_node_tree_t &n, const wcstring &s)
{
ctx = new parse_exec_t(n, s);
}
parse_execution_context_t::~parse_execution_context_t()
2013-06-11 16:37:51 +00:00
{
delete ctx;
2013-06-11 16:37:51 +00:00
}
bool parse_execution_context_t::visit_next_node(parse_execution_visitor_t *visitor)
{
return ctx->visit_next_node(visitor);
}
void parse_execution_context_t::get_source(node_offset_t idx, wcstring *result) const
{
return ctx->get_node_string(idx, result);
}