aboutsummaryrefslogtreecommitdiffhomepage
path: root/parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'parser.cpp')
-rw-r--r--parser.cpp1338
1 files changed, 492 insertions, 846 deletions
diff --git a/parser.cpp b/parser.cpp
index 62b45dcc..c93ae071 100644
--- a/parser.cpp
+++ b/parser.cpp
@@ -44,6 +44,7 @@ The fish parser. Contains functions for parsing and evaluating code.
#include "path.h"
#include "signal.h"
#include "complete.h"
+#include "parse_tree.h"
/**
Maximum number of function calls, i.e. recursion depth.
@@ -87,11 +88,6 @@ The fish parser. Contains functions for parsing and evaluating code.
#define BLOCK_END_ERR_MSG _( L"Could not locate end of block. The 'end' command is missing, misspelled or a ';' is missing.")
/**
- Error message on reaching maximum number of block calls
-*/
-#define BLOCK_ERR_MSG _( L"Maximum number of nested blocks reached.")
-
-/**
Error message when a non-string token is found when expecting a command name
*/
#define CMD_ERR_MSG _( L"Expected a command name, got token of type '%ls'")
@@ -316,6 +312,13 @@ static const struct block_lookup_entry block_lookup[]=
static bool job_should_skip_elseif(const job_t *job, const block_t *current_block);
+// Given a file path, return something nicer. Currently we just "unexpand" tildes.
+static wcstring user_presentable_path(const wcstring &path)
+{
+ return replace_home_directory_with_tilde(path);
+}
+
+
parser_t::parser_t(enum parser_type_t type, bool errors) :
parser_type(type),
show_errors(errors),
@@ -327,7 +330,6 @@ parser_t::parser_t(enum parser_type_t type, bool errors) :
eval_level(-1),
block_io(shared_ptr<io_data_t>())
{
-
}
/* A pointer to the principal parser (which is a static local) */
@@ -575,19 +577,20 @@ void parser_t::allow_function()
forbidden_function.pop_back();
}
-void parser_t::error(int ec, int p, const wchar_t *str, ...)
+void parser_t::error(int ec, size_t p, const wchar_t *str, ...)
{
va_list va;
CHECK(str,);
error_code = ec;
- err_pos = p;
+
+ // note : p may be -1
+ err_pos = static_cast<int>(p);
va_start(va, str);
err_buff = vformat_string(str, va);
va_end(va);
-
}
/**
@@ -742,7 +745,7 @@ void parser_t::print_errors_stderr()
}
-int parser_t::eval_args(const wchar_t *line, std::vector<completion_t> &args)
+void parser_t::eval_args(const wchar_t *line, std::vector<completion_t> &args)
{
expand_flags_t eflags = 0;
@@ -751,10 +754,9 @@ int parser_t::eval_args(const wchar_t *line, std::vector<completion_t> &args)
if (this->parser_type != PARSER_TYPE_GENERAL)
eflags |= EXPAND_SKIP_CMDSUBST;
- int do_loop=1;
+ bool do_loop=1;
- CHECK(line, 1);
-// CHECK( args, 1 );
+ if (! line) return;
// PCA we need to suppress calling proc_push_interactive off of the main thread. I'm not sure exactly what it does.
if (this->parser_type == PARSER_TYPE_GENERAL)
@@ -823,11 +825,9 @@ int parser_t::eval_args(const wchar_t *line, std::vector<completion_t> &args)
if (this->parser_type == PARSER_TYPE_GENERAL)
proc_pop_interactive();
-
- return 1;
}
-void parser_t::stack_trace(size_t block_idx, wcstring &buff)
+void parser_t::stack_trace(size_t block_idx, wcstring &buff) const
{
/*
Check if we should end the recursion
@@ -872,13 +872,13 @@ void parser_t::stack_trace(size_t block_idx, wcstring &buff)
{
const source_block_t *sb = static_cast<const source_block_t*>(b);
const wchar_t *source_dest = sb->source_file;
- append_format(buff, _(L"in . (source) call of file '%ls',\n"), source_dest);
+ append_format(buff, _(L"from sourcing file %ls\n"), user_presentable_path(source_dest).c_str());
break;
}
case FUNCTION_CALL:
{
const function_block_t *fb = static_cast<const function_block_t*>(b);
- append_format(buff, _(L"in function '%ls',\n"), fb->name.c_str());
+ append_format(buff, _(L"in function '%ls'\n"), fb->name.c_str());
break;
}
case SUBST:
@@ -896,14 +896,14 @@ void parser_t::stack_trace(size_t block_idx, wcstring &buff)
if (file)
{
append_format(buff,
- _(L"\tcalled on line %d of file '%ls',\n"),
+ _(L"\tcalled on line %d of file %ls\n"),
b->src_lineno,
- file);
+ user_presentable_path(file).c_str());
}
else
{
append_format(buff,
- _(L"\tcalled on standard input,\n"));
+ _(L"\tcalled on standard input\n"));
}
if (b->type() == FUNCTION_CALL)
@@ -1171,7 +1171,7 @@ const wchar_t *parser_t::get_buffer() const
}
-int parser_t::is_help(const wchar_t *s, int min_match) const
+int parser_t::is_help(const wchar_t *s, int min_match)
{
CHECK(s, 0);
@@ -1329,6 +1329,7 @@ void parser_t::parse_job_argument_list(process_t *p,
case TOK_BACKGROUND:
{
job_set_flag(j, JOB_FOREGROUND, 0);
+ // PCA note fall through, this is deliberate. The background modifier & terminates a command
}
case TOK_END:
@@ -1647,6 +1648,110 @@ 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)
+ {
+ // These magic numbers correspond to productions for boolean_statement
+ case 0:
+ // AND. Skip if the last job failed.
+ skip_job = (proc_get_last_status() != 0);
+ break;
+
+ case 1:
+ // OR. Skip if the last job succeeded.
+ skip_job = (proc_get_last_status() == 0);
+ break;
+
+ case 2:
+ // NOT. Negate it.
+ job_set_flag(job, JOB_NEGATE, !job_get_flag(job, JOB_NEGATE));
+ break;
+
+ default:
+ {
+ fprintf(stderr, "Unexpected production in boolean statement\n");
+ PARSER_DIE();
+ break;
+ }
+ }
+
+ process_t *result = NULL;
+ if (! skip_job)
+ {
+ const parse_node_t &subject = *ctx.tree.get_child(bool_statement, 1, symbol_statement);
+ result = this->create_job_process(job, subject, ctx);
+ }
+ 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);
+
+ process_t *result = NULL;
+
+ switch (specific_statement.type)
+ {
+ case symbol_boolean_statement:
+ {
+ result = this->create_boolean_process(job, specific_statement, ctx);
+ break;
+ }
+
+ case symbol_block_statement:
+ {
+ const parse_node_t &header = *ctx.tree.get_child(specific_statement, 0, symbol_block_header);
+ const parse_node_t &specific_header = *ctx.tree.get_child(header, 0);
+ switch (specific_header.type)
+ {
+ case symbol_for_header:
+ result = this->create_for_process(job, specific_header, specific_statement, ctx);
+ break;
+
+ case symbol_while_header:
+ result = this->create_while_process(job, specific_header, specific_statement, ctx);
+ break;
+
+ case symbol_function_header:
+ // No process is associated with creating a function
+ // TODO: create the darn function!
+ result = NULL;
+ break;
+
+ case symbol_begin_header:
+
+ break;
+
+ default:
+ fprintf(stderr, "Unexpected header type\n");
+ PARSER_DIE();
+ break;
+ }
+ }
+ }
+
+ // expand_one command
+ // handle booleans (and, not, or)
+ // set INTERNAL_EXEC
+ // implicit CD
+
+ return proc;
+}
+#endif
+
/**
Fully parse a single job. Does not call exec on it, but any command substitutions in the job will be executed.
@@ -1656,9 +1761,7 @@ void parser_t::parse_job_argument_list(process_t *p,
f
\return 1 on success, 0 on error
*/
-int parser_t::parse_job(process_t *p,
- job_t *j,
- tokenizer_t *tok)
+int parser_t::parse_job(process_t *p, job_t *j, tokenizer_t *tok)
{
std::vector<completion_t> args; // The list that will become the argv array for the program
int use_function = 1; // May functions be considered when checking what action this command represents
@@ -2350,6 +2453,206 @@ 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);
+ this->job_start_pos = (int)job_node.source_start;
+
+ // Get terminal modes
+ struct termios tmodes = {};
+ if (get_is_interactive())
+ {
+ if (tcgetattr(STDIN_FILENO, &tmodes))
+ {
+ // need real error handling here
+ wperror(L"tcgetattr");
+ return;
+ }
+ }
+
+ /* Track whether we had an error */
+ bool process_errored = false;
+
+ /* Profiling support */
+ long long t1 = 0, t2 = 0, t3 = 0;
+ const bool do_profile = profile;
+ profile_item_t *profile_item = NULL;
+ if (do_profile)
+ {
+ profile_item = new profile_item_t();
+ profile_item->skipped = 1;
+ profile_items.push_back(profile_item);
+ t1 = get_time();
+ }
+
+ job_t *j = this->job_create();
+ 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) \
+ && (!is_subshell && !is_event));
+ job_set_flag(j, JOB_SKIP_NOTIFICATION, is_subshell \
+ || is_block \
+ || is_event \
+ || (!get_is_interactive()));
+
+ current_block->job = j;
+
+ /* Tell the job what its command is */
+ j->set_command(job_node.get_source(ctx.src));
+
+ /* Construct process_t structures for every statement in the job */
+ const parse_node_t *statement_node = ctx.tree.get_child(job_node, 0, symbol_statement);
+ assert(statement_node != NULL);
+
+ /* Create the process (may fail!) */
+ j->first_process = this->create_job_process(j, *statement_node, ctx);
+ if (j->first_process == NULL)
+ process_errored = true;
+
+ /* Construct process_ts for job continuations (pipelines), by walking the list until we hit the terminal (empty) job continuationf */
+ const parse_node_t *job_cont = ctx.tree.get_child(job_node, 1, symbol_job_continuation);
+ process_t *last_process = j->first_process;
+ while (! process_errored && job_cont != NULL && job_cont->child_count > 0)
+ {
+ assert(job_cont->type == symbol_job_continuation);
+
+ /* Get the statement node and make a process from it */
+ const parse_node_t *statement_node = ctx.tree.get_child(*job_cont, 1, symbol_statement);
+ assert(statement_node != NULL);
+
+ /* Store the new process (and maybe with an error) */
+ last_process->next = this->create_job_process(j, *statement_node, ctx);
+ if (last_process->next == NULL)
+ process_errored = true;
+
+ /* Link the process and get the next continuation */
+ 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
parse_job to parse the job and then calls exec to execute it.
@@ -2589,9 +2892,141 @@ void parser_t::eval_job(tokenizer_t *tok)
}
-int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type_t block_type)
+#if 0
+int parser_t::eval2(const wcstring &cmd_str, const io_chain_t &io, enum block_type_t block_type)
{
- const wchar_t * const cmd = cmdStr.c_str();
+ parser_context_t mut_ctx;
+ mut_ctx.src = cmd_str;
+
+ /* Parse the tree */
+ if (! parse_t::parse(cmd_str, parse_flag_none, &mut_ctx.tree, NULL))
+ {
+ return 1;
+ }
+
+ /* Make a const version for safety's sake */
+ const parser_context_t &ctx = mut_ctx;
+
+ CHECK_BLOCK(1);
+
+ /* Record the current chain so we can put it back later */
+ 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;
+
+ /* Do some stuff I haven't figured out yet */
+ job_reap(0);
+
+ /* Only certain blocks are allowed */
+ if ((block_type != TOP) &&
+ (block_type != SUBST))
+ {
+ debug(1,
+ INVALID_SCOPE_ERR_MSG,
+ parser_t::get_block_desc(block_type));
+ bugreport();
+ return 1;
+ }
+
+ eval_level++;
+
+ this->push_block(new scope_block_t(block_type));
+
+ 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);
+ }
+ }
+
+ parser_t::pop_block();
+
+ while (start_current_block != current_block)
+ {
+ if (current_block == 0)
+ {
+ debug(0,
+ _(L"End of block mismatch. Program terminating."));
+ bugreport();
+ FATAL_EXIT();
+ break;
+ }
+
+ if ((!error_code) && (!exit_status()) && (!proc_get_last_status()))
+ {
+
+ //debug( 2, L"Status %d\n", proc_get_last_status() );
+
+ debug(1,
+ L"%ls", parser_t::get_block_desc(current_block->type()));
+ debug(1,
+ BLOCK_END_ERR_MSG);
+ fwprintf(stderr, L"%ls", parser_t::current_line());
+
+ const wcstring h = builtin_help_get(*this, L"end");
+ if (h.size())
+ fwprintf(stderr, L"%ls", h.c_str());
+ break;
+
+ }
+ parser_t::pop_block();
+ }
+
+ this->print_errors_stderr();
+
+ while (forbidden_function.size() > forbid_count)
+ parser_t::allow_function();
+
+ /*
+ Restore previous eval state
+ */
+ eval_level--;
+
+ int code=error_code;
+ error_code=0;
+
+ job_reap(0);
+
+ return code;
+}
+#endif
+
+int parser_t::eval(const wcstring &cmd_str, const io_chain_t &io, enum block_type_t block_type)
+{
+ const wchar_t * const cmd = cmd_str.c_str();
size_t forbid_count;
int code;
const block_t *start_current_block = current_block();
@@ -2614,13 +3049,6 @@ int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type
debug(4, L"eval: %ls", cmd);
- if (!cmd)
- {
- debug(1,
- EVAL_NULL_ERR_MSG);
- bugreport();
- return 1;
- }
if ((block_type != TOP) &&
(block_type != SUBST))
@@ -2638,6 +3066,7 @@ int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type
tokenizer_t local_tokenizer(cmd, 0);
scoped_push<tokenizer_t *> tokenizer_push(&current_tokenizer, &local_tokenizer);
+ scoped_push<int> tokenizer_pos_push(&current_tokenizer_pos, 0);
error_code = 0;
@@ -2779,7 +3208,7 @@ int parser_t::parser_test_argument(const wchar_t *arg, wcstring *out, const wcha
case 1:
{
- wchar_t *subst = wcsndup(paran_begin+1, paran_end-paran_begin-1);
+ const wcstring subst(paran_begin + 1, paran_end);
wcstring tmp;
tmp.append(arg_cpy, paran_begin - arg_cpy);
@@ -2788,17 +3217,16 @@ int parser_t::parser_test_argument(const wchar_t *arg, wcstring *out, const wcha
// debug( 1, L"%ls -> %ls %ls", arg_cpy, subst, tmp.buff );
- err |= parser_t::test(subst, 0, out, prefix);
+ parse_error_list_t errors;
+ err |= parse_util_detect_errors(subst, &errors);
+ if (out && ! errors.empty())
+ {
+ out->append(parse_errors_description(errors, subst, prefix));
+ }
- free(subst);
free(arg_cpy);
arg_cpy = wcsdup(tmp.c_str());
- /*
- Do _not_ call sb_destroy on this stringbuffer - it's
- buffer is used as the new 'arg_cpy'. It is free'd at
- the end of the loop.
- */
break;
}
}
@@ -2923,819 +3351,37 @@ struct block_info_t
{
int position; //tokenizer position
block_type_t type; //type of the block
- int indentation; //indentation associated with the block
-
- bool has_had_case; //if we are a switch, whether we've encountered a case
};
-int parser_t::test(const wchar_t *buff, int *block_level, wcstring *out, const wchar_t *prefix)
+void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &errors, wcstring *output) const
{
- ASSERT_IS_MAIN_THREAD();
-
- /*
- Set to one if a command name has been given for the currently
- parsed process specification
- */
- int had_cmd=0;
- int err=0;
- int unfinished = 0;
-
- // These are very nearly stacks, but sometimes we have to inspect non-top elements (e.g. return)
- std::vector<struct block_info_t> block_infos;
- int indentation_sum = 0; //sum of indentation in block_infos
- int res = 0;
-
- /*
- Set to 1 if the current command is inside a pipeline
- */
- int is_pipeline = 0;
-
- /*
- Set to one if the currently specified process can not be used inside a pipeline
- */
- int forbid_pipeline = 0;
-
- /*
- Set to one if an additional process specification is needed
- */
- bool needs_cmd = false;
-
- /*
- Counter on the number of arguments this function has encountered
- so far. Is set to -1 when the count is unknown, i.e. after
- encountering an argument that contains substitutions that can
- expand to more/less arguemtns then 1.
- */
- int arg_count=0;
-
- /*
- The currently validated command.
- */
- wcstring command;
- bool has_command = false;
-
- CHECK(buff, 1);
-
- if (block_level)
- {
- size_t len = wcslen(buff);
- for (size_t i=0; i<len; i++)
- {
- block_level[i] = -1;
- }
-
- }
-
- tokenizer_t tok(buff, 0);
-
- scoped_push<tokenizer_t*> tokenizer_push(&current_tokenizer, &tok);
- scoped_push<int> tokenizer_pos_push(&current_tokenizer_pos);
-
- for (;; tok_next(&tok))
- {
- current_tokenizer_pos = tok_get_pos(&tok);
-
- int last_type = tok_last_type(&tok);
- int end_of_cmd = 0;
-
- switch (last_type)
- {
- case TOK_STRING:
- {
- if (!had_cmd)
- {
- int mark = tok_get_pos(&tok);
- had_cmd = 1;
- arg_count=0;
-
- command = tok_last(&tok);
-
- // Pass SKIP_HOME_DIRECTORIES for https://github.com/fish-shell/fish-shell/issues/512
- has_command = expand_one(command, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_HOME_DIRECTORIES);
- if (! has_command)
- {
- command = L"";
- err=1;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- ILLEGAL_CMD_ERR_MSG,
- tok_last(&tok));
-
- print_errors(*out, prefix);
- }
- break;
- }
-
- if (needs_cmd)
- {
- /*
- end is not a valid command when a followup
- command is needed, such as after 'and' or
- 'while'
- */
- if (contains(command,
- L"end"))
- {
- err=1;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- COND_ERR_MSG);
-
- print_errors(*out, prefix);
- }
- }
-
- needs_cmd = false;
- }
-
- /*
- Decrement block count on end command
- */
- if (command == L"end")
- {
- tok_next(&tok);
- tok_set_pos(&tok, mark);
-
- /* Test that end is not used when not inside any block */
- if (block_infos.empty())
- {
- err = 1;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- INVALID_END_ERR_MSG);
- print_errors(*out, prefix);
- const wcstring h = builtin_help_get(*this, L"end");
- if (! h.empty())
- append_format(*out, L"%ls", h.c_str());
- }
- }
- else
- {
- indentation_sum -= block_infos.back().indentation;
- block_infos.pop_back();
-
- }
- }
-
- /*
- Store the block level. This needs to be done
- _after_ checking for end commands, but _before_
- checking for block opening commands.
- */
- if (block_level != NULL)
- {
- int indentation_adjust = 0;
- if (command == L"else")
- {
- // if or else if goes back
- indentation_adjust = -1;
- }
- else if (command == L"case")
- {
- if (! block_infos.empty() && block_infos.back().type == SWITCH)
- {
- // mark that we've encountered a case, and increase the indentation
- // by doing this now, we avoid overly indenting the first case as the user types it
- if (! block_infos.back().has_had_case)
- {
- block_infos.back().has_had_case = true;
- block_infos.back().indentation += 1;
- indentation_sum += 1;
- }
- // unindent this case
- indentation_adjust = -1;
- }
- }
-
- block_level[tok_get_pos(&tok)] = indentation_sum + indentation_adjust;
- }
-
- /*
- Handle block commands
- */
- if (parser_keywords_is_block(command))
- {
- struct block_info_t info = {current_tokenizer_pos, parser_get_block_type(command), 1 /* indent */};
- block_infos.push_back(info);
- indentation_sum += info.indentation;
- tok_next(&tok);
- tok_set_pos(&tok, mark);
- }
-
- /*
- If parser_keywords_is_subcommand is true, the command
- accepts a second command as it's first
- argument. If parser_skip_arguments is true, the
- second argument is optional.
- */
- if (parser_keywords_is_subcommand(command) && !parser_keywords_skip_arguments(command))
- {
- needs_cmd = true;
- had_cmd = 0;
- }
-
- if (contains(command,
- L"or",
- L"and"))
- {
- /*
- 'or' and 'and' can not be used inside pipelines
- */
- if (is_pipeline)
- {
- err=1;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- EXEC_ERR_MSG);
-
- print_errors(*out, prefix);
-
- }
- }
- }
-
- /*
- There are a lot of situations where pipelines
- are forbidden, including when using the exec
- builtin.
- */
- if (parser_is_pipe_forbidden(command))
- {
- if (is_pipeline)
- {
- err=1;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- EXEC_ERR_MSG);
-
- print_errors(*out, prefix);
-
- }
- }
- forbid_pipeline = 1;
- }
-
- /*
- Test that the case builtin is only used directly in a switch block
- */
- if (command == L"case")
- {
- if (block_infos.empty() || block_infos.back().type != SWITCH)
- {
- err=1;
-
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- INVALID_CASE_ERR_MSG);
-
- print_errors(*out, prefix);
- const wcstring h = builtin_help_get(*this, L"case");
- if (h.size())
- append_format(*out, L"%ls", h.c_str());
- }
- }
- }
-
- /*
- Test that the return bultin is only used within function definitions
- */
- if (command == L"return")
- {
- bool found_func = false;
- size_t block_idx = block_infos.size();
- while (block_idx--)
- {
- if (block_infos.at(block_idx).type == FUNCTION_DEF)
- {
- found_func = true;
- break;
- }
- }
-
- if (!found_func)
- {
- /*
- Peek to see if the next argument is
- --help, in which case we'll allow it to
- show the help.
- */
-
- int old_pos = tok_get_pos(&tok);
- int is_help = 0;
-
- tok_next(&tok);
- if (tok_last_type(&tok) == TOK_STRING)
- {
- wcstring first_arg = tok_last(&tok);
- if (expand_one(first_arg, EXPAND_SKIP_CMDSUBST) && parser_t::is_help(first_arg.c_str(), 3))
- {
- is_help = 1;
- }
- }
-
- tok_set_pos(&tok, old_pos);
-
- if (!is_help)
- {
- err=1;
-
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- INVALID_RETURN_ERR_MSG);
- print_errors(*out, prefix);
- }
- }
- }
- }
-
-
- /*
- Test that break and continue are only used within loop blocks
- */
- if (contains(command, L"break", L"continue"))
- {
- bool found_loop = false;
- size_t block_idx = block_infos.size();
- while (block_idx--)
- {
- block_type_t type = block_infos.at(block_idx).type;
- if (type == WHILE || type == FOR)
- {
- found_loop = true;
- break;
- }
- }
-
- if (!found_loop)
- {
- /*
- Peek to see if the next argument is
- --help, in which case we'll allow it to
- show the help.
- */
-
- int old_pos = tok_get_pos(&tok);
- int is_help = 0;
-
- tok_next(&tok);
- if (tok_last_type(&tok) == TOK_STRING)
- {
- wcstring first_arg = tok_last(&tok);
- if (expand_one(first_arg, EXPAND_SKIP_CMDSUBST) && parser_t::is_help(first_arg.c_str(), 3))
- {
- is_help = 1;
- }
- }
-
- tok_set_pos(&tok, old_pos);
-
- if (!is_help)
- {
- err=1;
-
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- INVALID_LOOP_ERR_MSG);
- print_errors(*out, prefix);
- }
- }
- }
- }
-
- /*
- Test that else and else-if are only used directly in an if-block
- */
- if (command == L"else")
- {
- if (block_infos.empty() || block_infos.back().type != IF)
- {
- err=1;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- INVALID_ELSE_ERR_MSG,
- command.c_str());
-
- print_errors(*out, prefix);
- }
- }
- }
- }
- else
- {
- err |= parser_test_argument(tok_last(&tok), out, prefix, tok_get_pos(&tok));
-
- /* If possible, keep track of number of supplied arguments */
- if (arg_count >= 0 && expand_is_clean(tok_last(&tok)))
- {
- arg_count++;
- }
- else
- {
- arg_count = -1;
- }
-
- if (has_command)
- {
-
- /*
- Try to make sure the second argument to 'for' is 'in'
- */
- if (command == L"for")
- {
- if (arg_count == 1)
- {
-
- if (wcsvarname(tok_last(&tok)))
- {
-
- err = 1;
-
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- BUILTIN_FOR_ERR_NAME,
- L"for",
- tok_last(&tok));
-
- print_errors(*out, prefix);
- }
- }
-
- }
- else if (arg_count == 2)
- {
- if (wcscmp(tok_last(&tok), L"in") != 0)
- {
- err = 1;
-
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- BUILTIN_FOR_ERR_IN,
- L"for");
-
- print_errors(*out, prefix);
- }
- }
- }
- }
- else if (command == L"else")
- {
- if (arg_count == 1)
- {
- /* Any second argument must be "if" */
- if (wcscmp(tok_last(&tok), L"if") != 0)
- {
- err = 1;
-
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- BUILTIN_ELSEIF_ERR_ARGUMENT,
- L"else");
- print_errors(*out, prefix);
- }
- }
- else
- {
- /* Successfully detected "else if". Now we need a new command. */
- needs_cmd = true;
- had_cmd = false;
- }
- }
- }
- }
-
- }
-
- break;
- }
-
- case TOK_REDIRECT_OUT:
- case TOK_REDIRECT_IN:
- case TOK_REDIRECT_APPEND:
- case TOK_REDIRECT_FD:
- case TOK_REDIRECT_NOCLOB:
- {
- if (!had_cmd)
- {
- err = 1;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- INVALID_REDIRECTION_ERR_MSG);
- print_errors(*out, prefix);
- }
- }
- break;
- }
-
- case TOK_END:
- {
- if (needs_cmd && !had_cmd)
- {
- err = 1;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- CMD_ERR_MSG,
- tok_get_desc(tok_last_type(&tok)));
- print_errors(*out, prefix);
- }
- }
- needs_cmd = false;
- had_cmd = 0;
- is_pipeline=0;
- forbid_pipeline=0;
- end_of_cmd = 1;
-
- break;
- }
-
- case TOK_PIPE:
- {
- if (!had_cmd)
- {
- err=1;
- if (out)
- {
- if (tok_get_pos(&tok)>0 && buff[tok_get_pos(&tok)-1] == L'|')
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- CMD_OR_ERR_MSG,
- tok_get_desc(tok_last_type(&tok)));
-
- }
- else
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- CMD_ERR_MSG,
- tok_get_desc(tok_last_type(&tok)));
- }
-
- print_errors(*out, prefix);
- }
- }
- else if (forbid_pipeline)
- {
- err=1;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- EXEC_ERR_MSG);
-
- print_errors(*out, prefix);
- }
- }
- else
- {
- needs_cmd = true;
- is_pipeline=1;
- had_cmd=0;
- end_of_cmd = 1;
-
- }
- break;
- }
-
- case TOK_BACKGROUND:
- {
- if (!had_cmd)
- {
- err = 1;
- if (out)
- {
- if (tok_get_pos(&tok)>0 && buff[tok_get_pos(&tok)-1] == L'&')
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- CMD_AND_ERR_MSG,
- tok_get_desc(tok_last_type(&tok)));
-
- }
- else
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- CMD_ERR_MSG,
- tok_get_desc(tok_last_type(&tok)));
- }
-
- print_errors(*out, prefix);
- }
- }
-
- had_cmd = 0;
- end_of_cmd = 1;
-
- break;
- }
-
- case TOK_ERROR:
- default:
- if (tok_get_error(&tok) == TOK_UNTERMINATED_QUOTE)
- {
- unfinished = 1;
- }
- else
- {
- // Only print errors once
- if (out && ! err)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- TOK_ERR_MSG,
- tok_last(&tok));
-
-
- print_errors(*out, prefix);
- }
- err = 1;
- }
-
- break;
- }
-
- if (end_of_cmd)
- {
- if (has_command && command == L"for")
- {
- if (arg_count >= 0 && arg_count < 2)
- {
- /*
- Not enough arguments to the for builtin
- */
- err = 1;
-
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- BUILTIN_FOR_ERR_COUNT,
- L"for",
- arg_count);
-
- print_errors(*out, prefix);
- }
- }
- }
- else if (has_command && command == L"else")
- {
- if (arg_count == 1)
- {
- /* If we have any arguments, we must have at least two...either "else" or "else if foo..." */
- err = true;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- BUILTIN_ELSEIF_ERR_COUNT,
- L"else",
- arg_count);
-
- print_errors(*out, prefix);
-
- }
- }
- }
-
- }
-
- if (!tok_has_next(&tok))
- break;
-
- }
-
- if (needs_cmd)
- {
- err=1;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- COND_ERR_MSG);
-
- print_errors(*out, prefix);
- }
- }
-
-
- if (out != NULL && ! block_infos.empty())
- {
- const wchar_t *cmd;
- int bad_pos = block_infos.back().position;
- block_type_t bad_type = block_infos.back().type;
-
- error(SYNTAX_ERROR, bad_pos, BLOCK_END_ERR_MSG);
-
- print_errors(*out, prefix);
-
- cmd = parser_get_block_command(bad_type);
- if (cmd)
- {
- const wcstring h = builtin_help_get(*this, cmd);
- if (h.size())
- {
- append_format(*out, L"%ls", h.c_str());
- }
- }
-
-
- }
-
- /*
- Fill in the unset block_level entries. Until now, only places
- where the block level _changed_ have been filled out. This fills
- in the rest.
- */
-
- if (block_level)
+ assert(output != NULL);
+ if (! errors.empty())
{
- int last_level = 0;
- size_t i, len = wcslen(buff);
- for (i=0; i<len; i++)
+ const parse_error_t err = errors.at(0);
+
+ // Determine which line we're on
+ assert(err.source_start <= src.size());
+ size_t which_line = 1 + std::count(src.begin(), src.begin() + err.source_start, L'\n');
+
+ const wchar_t *filename = this->current_filename();
+ if (filename)
{
- if (block_level[i] >= 0)
- {
- last_level = block_level[i];
- /*
- Make all whitespace before a token have the new
- level. This avoid using the wrong indentation level
- if a new line starts with whitespace.
- */
- size_t prev_char_idx = i;
- while (prev_char_idx--)
- {
- if (!wcschr(L" \n\t\r", buff[prev_char_idx]))
- break;
- block_level[prev_char_idx] = last_level;
- }
- }
- block_level[i] = last_level;
+ append_format(*output, _(L"fish: line %lu of %ls:\n"), which_line, user_presentable_path(filename).c_str());
}
-
- /*
- Make all trailing whitespace have the block level that the
- validator had at exit. This makes sure a new line is
- correctly indented even if it is empty.
- */
- int last_indent = block_infos.empty() ? 0 : block_infos.back().indentation;
- size_t suffix_idx = len;
- while (suffix_idx--)
+ else
{
- if (!wcschr(L" \n\t\r", buff[suffix_idx]))
- break;
- block_level[suffix_idx] = last_indent;
+ output->append(L"fish: ");
}
+
+ // Don't include the caret if we're interactive, this is the first line of text, and our source is at its beginning, because then it's obvious
+ bool skip_caret = (get_is_interactive() && which_line == 1 && err.source_start == 0);
+
+ output->append(err.describe(src, skip_caret));
+ output->push_back(L'\n');
+
+ this->stack_trace(current_block, *output);
}
-
- /*
- Calculate exit status
- */
- if (! block_infos.empty())
- unfinished = 1;
-
- if (err)
- res |= PARSER_TEST_ERROR;
-
- if (unfinished)
- res |= PARSER_TEST_INCOMPLETE;
-
- /*
- Cleanup
- */
-
- error_code=0;
-
-
- return res;
-
}
block_t::block_t(block_type_t t) :