From 6ce4b344e45baaa06bf593a5c0983da7a22eb64e Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 27 Dec 2013 01:38:43 -0800 Subject: [PATCH] Hook up for statements, if statements, and function definition in new parser --- builtin.cpp | 307 +++++++++++++++++++++++++++++++++++++++++- builtin.h | 5 +- exec.cpp | 51 +++++-- function.cpp | 1 - parse_execution.cpp | 294 ++++++++++++++++++++++++++++++++++++---- parse_execution.h | 18 ++- parse_productions.cpp | 2 +- parse_tree.cpp | 6 + parse_tree.h | 12 +- parse_util.cpp | 2 +- parser.cpp | 68 +++++++--- parser.h | 6 + proc.h | 4 + 13 files changed, 709 insertions(+), 67 deletions(-) diff --git a/builtin.cpp b/builtin.cpp index a97efdbf6..11036e378 100644 --- a/builtin.cpp +++ b/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 argv_array(args); + wchar_t **argv = const_cast(argv_array.get()); + + int argc = builtin_count_args(argv); + int res=STATUS_BUILTIN_OK; + wchar_t *desc=0; + std::vector events; + std::auto_ptr 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 &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 &out_opened_fds) +static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t *out_chain, std::vector *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 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(); diff --git a/function.cpp b/function.cpp index d10698745..eadcca7c6 100644 --- a/function.cpp +++ b/function.cpp @@ -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(); diff --git a/parse_execution.cpp b/parse_execution.cpp index 2aa32f82e..1a2b16b1f 100644 --- a/parse_execution.cpp +++ b/parse_execution.cpp @@ -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 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; } diff --git a/parse_execution.h b/parse_execution.h index 0d679bb6e..f465a5934 100644 --- a/parse_execution.h +++ b/parse_execution.h @@ -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); }; diff --git a/parse_productions.cpp b/parse_productions.cpp index 3325f50dd..a4b1de957 100644 --- a/parse_productions.cpp +++ b/parse_productions.cpp @@ -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) diff --git a/parse_tree.cpp b/parse_tree.cpp index e231eb9d1..ad825a115 100644 --- a/parse_tree.cpp +++ b/parse_tree.cpp @@ -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]); diff --git a/parse_tree.h b/parse_tree.h index 9a5d7c238..91cab4262 100644 --- a/parse_tree.h +++ b/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 diff --git a/parse_util.cpp b/parse_util.cpp index ff7a022c2..cf196db1f 100644 --- a/parse_util.cpp +++ b/parse_util.cpp @@ -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; diff --git a/parser.cpp b/parser.cpp index 3ba3c89be..1f331dc53 100644 --- a/parser.cpp +++ b/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()); diff --git a/parser.h b/parser.h index 2f9291f03..f013a3b92 100644 --- a/parser.h +++ b/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_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. diff --git a/proc.h b/proc.h index 0a2949ca2..510f549a2 100644 --- a/proc.h +++ b/proc.h @@ -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 */