Hook up for statements, if statements, and function definition in new

parser
This commit is contained in:
ridiculousfish 2013-12-27 01:38:43 -08:00
parent a6ca809a4e
commit 6ce4b344e4
13 changed files with 709 additions and 67 deletions

View File

@ -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
{

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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)

View File

@ -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]);

View File

@ -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

View File

@ -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;

View File

@ -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());

View File

@ -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.

4
proc.h
View File

@ -73,6 +73,10 @@ enum process_type_t
A block of commands
*/
INTERNAL_BLOCK,
/** A block of commands, represented as a node */
INTERNAL_BLOCK_NODE,
/**
The exec builtin
*/