aboutsummaryrefslogtreecommitdiffhomepage
path: root/parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'parser.cpp')
-rw-r--r--parser.cpp3477
1 files changed, 434 insertions, 3043 deletions
diff --git a/parser.cpp b/parser.cpp
index 72b46c9f..ec8f2253 100644
--- a/parser.cpp
+++ b/parser.cpp
@@ -17,7 +17,6 @@ The fish parser. Contains functions for parsing and evaluating code.
#include <termios.h>
#include <pwd.h>
#include <dirent.h>
-#include <signal.h>
#include <algorithm>
#include "fallback.h"
@@ -37,28 +36,14 @@ The fish parser. Contains functions for parsing and evaluating code.
#include "expand.h"
#include "reader.h"
#include "sanity.h"
-#include "env_universal.h"
#include "event.h"
#include "intern.h"
#include "parse_util.h"
#include "path.h"
#include "signal.h"
#include "complete.h"
-
-/**
- Maximum number of function calls, i.e. recursion depth.
-*/
-#define MAX_RECURSION_DEPTH 128
-
-/**
- Error message for unknown builtin
-*/
-#define UNKNOWN_BUILTIN_ERR_MSG _(L"Unknown builtin '%ls'")
-
-/**
- Error message for improper use of the exec builtin
-*/
-#define EXEC_ERR_MSG _(L"This command can not be used in a pipeline")
+#include "parse_tree.h"
+#include "parse_execution.h"
/**
Error message for tokenizer error. The tokenizer message is
@@ -67,116 +52,10 @@ The fish parser. Contains functions for parsing and evaluating code.
#define TOK_ERR_MSG _( L"Tokenizer error: '%ls'")
/**
- Error message for short circuit command error.
-*/
-#define COND_ERR_MSG _( L"An additional command is required" )
-
-/**
- Error message on a function that calls itself immediately
-*/
-#define INFINITE_RECURSION_ERR_MSG _( L"The function calls itself immediately, which would result in an infinite loop.")
-
-/**
- Error message on reaching maximum recursion depth
-*/
-#define OVERFLOW_RECURSION_ERR_MSG _( L"Maximum recursion depth reached. Accidental infinite loop?")
-
-/**
- Error message used when the end of a block can't be located
-*/
-#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'")
-
-/**
- Error message when a non-string token is found when expecting a command name
-*/
-#define CMD_OR_ERR_MSG _( L"Expected a command name, got token of type '%ls'. Did you mean 'COMMAND; or COMMAND'? See the help section for the 'or' builtin command by typing 'help or'.")
-
-/**
- Error message when a non-string token is found when expecting a command name
-*/
-#define CMD_AND_ERR_MSG _( L"Expected a command name, got token of type '%ls'. Did you mean 'COMMAND; and COMMAND'? See the help section for the 'and' builtin command by typing 'help and'.")
-
-/**
- Error message when encountering an illegal command name
-*/
-#define ILLEGAL_CMD_ERR_MSG _( L"Illegal command name '%ls'")
-
-/**
- Error message when encountering an illegal file descriptor
-*/
-#define ILLEGAL_FD_ERR_MSG _( L"Illegal file descriptor '%ls'")
-
-/**
- Error message for wildcards with no matches
-*/
-#define WILDCARD_ERR_MSG _( L"No matches for wildcard '%ls'.")
-
-/**
- Error when using case builtin outside of switch block
-*/
-#define INVALID_CASE_ERR_MSG _( L"'case' builtin not inside of switch block")
-
-/**
- Error when using loop control builtins (break or continue) outside of loop
-*/
-#define INVALID_LOOP_ERR_MSG _( L"Loop control command while not inside of loop" )
-
-/**
- Error when using return builtin outside of function definition
-*/
-#define INVALID_RETURN_ERR_MSG _( L"'return' builtin command outside of function definition" )
-
-/**
- Error when using else builtin outside of if block
-*/
-#define INVALID_ELSE_ERR_MSG _( L"'%ls' builtin not inside of if block" )
-
-/**
- Error when using 'else if' past a naked 'else'
-*/
-#define INVALID_ELSEIF_PAST_ELSE_ERR_MSG _( L"'%ls' used past terminating 'else'" )
-
-/**
- Error when using end builtin outside of block
-*/
-#define INVALID_END_ERR_MSG _( L"'end' command outside of block")
-
-/**
- Error message for Posix-style assignment: foo=bar
-*/
-#define COMMAND_ASSIGN_ERR_MSG _( L"Unknown command '%ls'. Did you mean 'set %ls %ls'? See the help section on the set command by typing 'help set'.")
-
-/**
- Error for invalid redirection token
-*/
-#define REDIRECT_TOKEN_ERR_MSG _( L"Expected redirection specification, got token of type '%ls'")
-
-/**
- Error when encountering redirection without a command
-*/
-#define INVALID_REDIRECTION_ERR_MSG _( L"Encountered redirection when expecting a command name. Fish does not allow a redirection operation before a command.")
-
-/**
- Error for evaluating null pointer
-*/
-#define EVAL_NULL_ERR_MSG _( L"Tried to evaluate null pointer." )
-
-/**
Error for evaluating in illegal scope
*/
#define INVALID_SCOPE_ERR_MSG _( L"Tried to evaluate commands using invalid block type '%ls'" )
-
/**
Error for wrong token type
*/
@@ -197,20 +76,16 @@ The fish parser. Contains functions for parsing and evaluating code.
*/
#define BREAKPOINT_BLOCK N_( L"Block created by breakpoint" )
-
-
/**
If block description
*/
#define IF_BLOCK N_( L"'if' conditional block" )
-
/**
Function definition block description
*/
#define FUNCTION_DEF_BLOCK N_( L"function definition block" )
-
/**
Function invocation block description
*/
@@ -221,37 +96,31 @@ The fish parser. Contains functions for parsing and evaluating code.
*/
#define FUNCTION_CALL_NO_SHADOW_BLOCK N_( L"function invocation block with no variable shadowing" )
-
/**
Switch block description
*/
#define SWITCH_BLOCK N_( L"'switch' block" )
-
/**
Fake block description
*/
#define FAKE_BLOCK N_( L"unexecutable block" )
-
/**
Top block description
*/
#define TOP_BLOCK N_( L"global root block" )
-
/**
Command substitution block description
*/
#define SUBST_BLOCK N_( L"command substitution block" )
-
/**
Begin block description
*/
#define BEGIN_BLOCK N_( L"'begin' unconditional block" )
-
/**
Source block description
*/
@@ -262,7 +131,6 @@ The fish parser. Contains functions for parsing and evaluating code.
*/
#define EVENT_BLOCK N_( L"event handler block" )
-
/**
Unknown block description
*/
@@ -314,21 +182,19 @@ static const struct block_lookup_entry block_lookup[]=
{ (block_type_t)0, 0, 0 }
};
-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),
- error_code(0),
- err_pos(0),
- current_tokenizer(NULL),
- current_tokenizer_pos(0),
- job_start_pos(0),
- eval_level(-1),
- current_block(NULL),
- block_io(shared_ptr<io_data_t>())
+ cancellation_requested(false),
+ is_within_fish_initialization(false)
{
-
}
/* A pointer to the principal parser (which is a static local) */
@@ -346,45 +212,56 @@ parser_t &parser_t::principal_parser(void)
return parser;
}
+void parser_t::set_is_within_fish_initialization(bool flag)
+{
+ is_within_fish_initialization = flag;
+}
+
void parser_t::skip_all_blocks(void)
{
/* Tell all blocks to skip */
if (s_principal_parser)
{
+ s_principal_parser->cancellation_requested = true;
+
//write(2, "Cancelling blocks\n", strlen("Cancelling blocks\n"));
- block_t *c = s_principal_parser->current_block;
- while (c)
+ for (size_t i=0; i < s_principal_parser->block_count(); i++)
{
- c->skip = true;
- //fprintf(stderr, " Cancelled %p\n", c);
- c = c->outer;
+ s_principal_parser->block_at_index(i)->skip = true;
}
}
}
-void parser_t::push_block(block_t *newv)
+void parser_t::push_block(block_t *new_current)
{
- const enum block_type_t type = newv->type();
- newv->src_lineno = parser_t::get_lineno();
- newv->src_filename = parser_t::current_filename()?intern(parser_t::current_filename()):0;
+ const enum block_type_t type = new_current->type();
+ new_current->src_lineno = parser_t::get_lineno();
- newv->outer = current_block;
- if (current_block && current_block->skip)
- newv->mark_as_fake();
+ const wchar_t *filename = parser_t::current_filename();
+ if (filename != NULL)
+ {
+ new_current->src_filename = intern(filename);
+ }
+
+ const block_t *old_current = this->current_block();
+ if (old_current && old_current->skip)
+ {
+ new_current->skip = true;
+ }
/*
New blocks should be skipped if the outer block is skipped,
except TOP ans SUBST block, which open up new environments. Fake
blocks should always be skipped. Rather complicated... :-(
*/
- newv->skip=current_block?current_block->skip:0;
+ new_current->skip = old_current ? old_current->skip : 0;
/*
Type TOP and SUBST are never skipped
*/
if (type == TOP || type == SUBST)
{
- newv->skip = 0;
+ new_current->skip = 0;
}
/*
@@ -392,27 +269,32 @@ void parser_t::push_block(block_t *newv)
*/
if (type == FAKE || type == FUNCTION_DEF)
{
- newv->skip = 1;
+ new_current->skip = 1;
}
- newv->job = 0;
- newv->loop_status=LOOP_NORMAL;
+ new_current->job = 0;
+ new_current->loop_status=LOOP_NORMAL;
- current_block = newv;
+ this->block_stack.push_back(new_current);
- if ((newv->type() != FUNCTION_DEF) &&
- (newv->type() != FAKE) &&
- (newv->type() != TOP))
+ // Types TOP and SUBST are not considered blocks for the purposes of `status -b`
+ if (type != TOP && type != SUBST)
+ {
+ is_block = 1;
+ }
+
+ if ((new_current->type() != FUNCTION_DEF) &&
+ (new_current->type() != FAKE) &&
+ (new_current->type() != TOP))
{
env_push(type == FUNCTION_CALL);
- newv->wants_pop_env = true;
+ new_current->wants_pop_env = true;
}
}
void parser_t::pop_block()
{
- block_t *old = current_block;
- if (!current_block)
+ if (block_stack.empty())
{
debug(1,
L"function %s called on empty block stack.",
@@ -421,12 +303,32 @@ void parser_t::pop_block()
return;
}
- current_block = current_block->outer;
+ block_t *old = block_stack.back();
+ block_stack.pop_back();
if (old->wants_pop_env)
env_pop();
delete old;
+
+ // Figure out if `status -b` should consider us to be in a block now
+ int new_is_block=0;
+ for (std::vector<block_t*>::const_iterator it = block_stack.begin(), end = block_stack.end(); it != end; ++it)
+ {
+ const enum block_type_t type = (*it)->type();
+ if (type != TOP && type != SUBST)
+ {
+ new_is_block = 1;
+ break;
+ }
+ }
+ is_block = new_is_block;
+}
+
+void parser_t::pop_block(const block_t *expected)
+{
+ assert(expected == this->current_block());
+ this->pop_block();
}
const wchar_t *parser_t::get_block_desc(int block) const
@@ -441,105 +343,49 @@ const wchar_t *parser_t::get_block_desc(int block) const
return _(UNKNOWN_BLOCK);
}
-/**
- Returns 1 if the specified command is a builtin that may not be used in a pipeline
-*/
-static int parser_is_pipe_forbidden(const wcstring &word)
+wcstring parser_t::block_stack_description() const
{
- return contains(word,
- L"exec",
- L"case",
- L"break",
- L"return",
- L"continue");
-}
-
-/**
- Search the text for the end of the current block
-*/
-static const wchar_t *parser_find_end(const wchar_t * buff)
-{
- int had_cmd=0;
- int count = 0;
- int error=0;
- int mark=0;
-
- CHECK(buff, 0);
-
- tokenizer_t tok(buff, 0);
- for (; tok_has_next(&tok) && !error; tok_next(&tok))
+ wcstring result;
+ size_t idx = this->block_count();
+ size_t spaces = 0;
+ while (idx--)
{
- int last_type = tok_last_type(&tok);
- switch (last_type)
+ if (spaces > 0)
{
- case TOK_STRING:
- {
- if (!had_cmd)
- {
- if (wcscmp(tok_last(&tok), L"end")==0)
- {
- count--;
- }
- else if (parser_keywords_is_block(tok_last(&tok)))
- {
- count++;
- }
-
- if (count < 0)
- {
- error = 1;
- }
- had_cmd = 1;
- }
- break;
- }
-
- case TOK_END:
- {
- had_cmd = 0;
- break;
- }
-
- case TOK_PIPE:
- case TOK_BACKGROUND:
- {
- if (had_cmd)
- {
- had_cmd = 0;
- }
- else
- {
- error = 1;
- }
- break;
-
- }
-
- case TOK_ERROR:
- error = 1;
- break;
-
- default:
- break;
-
+ result.push_back(L'\n');
}
- if (!count)
+ for (size_t j=0; j < spaces; j++)
{
- tok_next(&tok);
- mark = tok_get_pos(&tok);
- break;
+ result.push_back(L' ');
}
-
+ result.append(this->block_at_index(idx)->description());
+ spaces++;
}
- if (!count && !error)
- {
+ return result;
+}
- return buff+mark;
- }
- return 0;
+const block_t *parser_t::block_at_index(size_t idx) const
+{
+ /* 0 corresponds to the last element in our vector */
+ size_t count = block_stack.size();
+ return idx < count ? block_stack.at(count - idx - 1) : NULL;
+}
+block_t *parser_t::block_at_index(size_t idx)
+{
+ size_t count = block_stack.size();
+ return idx < count ? block_stack.at(count - idx - 1) : NULL;
}
+const block_t *parser_t::current_block() const
+{
+ return block_stack.empty() ? NULL : block_stack.back();
+}
+
+block_t *parser_t::current_block()
+{
+ return block_stack.empty() ? NULL : block_stack.back();
+}
void parser_t::forbid_function(const wcstring &function)
{
@@ -548,36 +394,16 @@ void parser_t::forbid_function(const wcstring &function)
void parser_t::allow_function()
{
- /*
- if( al_peek( &forbidden_function) )
- debug( 2, L"Allow %ls\n", al_peek( &forbidden_function) );
- */
forbidden_function.pop_back();
}
-void parser_t::error(int ec, int p, const wchar_t *str, ...)
-{
- va_list va;
-
- CHECK(str,);
-
- error_code = ec;
- err_pos = p;
-
- va_start(va, str);
- err_buff = vformat_string(str, va);
- va_end(va);
-
-}
-
/**
Print profiling information to the specified stream
*/
static void print_profile(const std::vector<profile_item_t*> &items,
FILE *out)
{
- size_t pos;
- for (pos = 0; pos < items.size(); pos++)
+ for (size_t pos = 0; pos < items.size(); pos++)
{
const profile_item_t *me, *prev;
size_t i;
@@ -634,187 +460,98 @@ static void print_profile(const std::vector<profile_item_t*> &items,
}
}
- delete me;
}
}
}
-void parser_t::destroy()
+void parser_t::emit_profiling(const char *path) const
{
- if (profile)
+ /* Save profiling information. OK to not use CLO_EXEC here because this is called while fish is dying (and hence will not fork) */
+ FILE *f = fopen(path, "w");
+ if (!f)
+ {
+ debug(1,
+ _(L"Could not write profiling information to file '%s'"),
+ path);
+ }
+ else
{
- /* Save profiling information. OK to not use CLO_EXEC here because this is called while fish is dying (and hence will not fork) */
- FILE *f = fopen(profile, "w");
- if (!f)
+ if (fwprintf(f,
+ _(L"Time\tSum\tCommand\n"),
+ profile_items.size()) < 0)
{
- debug(1,
- _(L"Could not write profiling information to file '%s'"),
- profile);
+ wperror(L"fwprintf");
}
else
{
- if (fwprintf(f,
- _(L"Time\tSum\tCommand\n"),
- profile_items.size()) < 0)
- {
- wperror(L"fwprintf");
- }
- else
- {
- print_profile(profile_items, f);
- }
-
- if (fclose(f))
- {
- wperror(L"fclose");
- }
+ print_profile(profile_items, f);
}
- }
-
- lineinfo.clear();
-
- forbidden_function.clear();
-
-}
-
-/**
- Print error message to string if an error has occured while parsing
-
- \param target the buffer to write to
- \param prefix: The string token to prefix the each line with. Usually the name of the command trying to parse something.
-*/
-void parser_t::print_errors(wcstring &target, const wchar_t *prefix)
-{
- CHECK(prefix,);
-
- if (error_code && ! err_buff.empty())
- {
- int tmp;
-
- append_format(target, L"%ls: %ls\n", prefix, err_buff.c_str());
-
- tmp = current_tokenizer_pos;
- current_tokenizer_pos = err_pos;
-
- append_format(target, L"%ls", this->current_line());
-
- current_tokenizer_pos=tmp;
- }
-}
-
-/**
- Print error message to stderr if an error has occured while parsing
-*/
-void parser_t::print_errors_stderr()
-{
- if (error_code && ! err_buff.empty())
- {
- debug(0, L"%ls", err_buff.c_str());
- int tmp;
-
- tmp = current_tokenizer_pos;
- current_tokenizer_pos = err_pos;
-
- fwprintf(stderr, L"%ls", this->current_line());
- current_tokenizer_pos=tmp;
+ if (fclose(f))
+ {
+ wperror(L"fclose");
+ }
}
-
}
-int parser_t::eval_args(const wchar_t *line, std::vector<completion_t> &args)
+void parser_t::expand_argument_list(const wcstring &arg_list_src, std::vector<completion_t> &output_arg_list)
{
-
expand_flags_t eflags = 0;
if (! show_errors)
eflags |= EXPAND_NO_DESCRIPTIONS;
if (this->parser_type != PARSER_TYPE_GENERAL)
eflags |= EXPAND_SKIP_CMDSUBST;
- int do_loop=1;
-
- CHECK(line, 1);
-// CHECK( args, 1 );
-
- // PCA we need to suppress calling proc_push_interactive off of the main thread. I'm not sure exactly what it does.
+ /* Suppress calling proc_push_interactive off of the main thread. */
if (this->parser_type == PARSER_TYPE_GENERAL)
+ {
proc_push_interactive(0);
+ }
- tokenizer_t tok(line, (show_errors ? 0 : TOK_SQUASH_ERRORS));
-
- /*
- eval_args may be called while evaulating another command, so we
- save the previous tokenizer and restore it on exit
- */
- scoped_push<tokenizer_t*> tokenizer_push(&current_tokenizer, &tok);
- scoped_push<int> tokenizer_pos_push(&current_tokenizer_pos, 0);
+ /* Parse the string as an argument list */
+ parse_node_tree_t tree;
+ if (! parse_tree_from_string(arg_list_src, parse_flag_none, &tree, NULL /* errors */, symbol_freestanding_argument_list))
+ {
+ /* Failed to parse. Here we expect to have reported any errors in test_args */
+ return;
+ }
- error_code=0;
+ /* Get the root argument list */
+ assert(! tree.empty());
+ const parse_node_t *arg_list = &tree.at(0);
+ assert(arg_list->type == symbol_freestanding_argument_list);
- for (; do_loop && tok_has_next(&tok) ; tok_next(&tok))
+ /* Extract arguments from it */
+ while (arg_list != NULL)
{
- current_tokenizer_pos = tok_get_pos(&tok);
- switch (tok_last_type(&tok))
+ const parse_node_t *arg_node = tree.next_node_in_node_list(*arg_list, symbol_argument, &arg_list);
+ if (arg_node != NULL)
{
- case TOK_STRING:
- {
- const wcstring tmp = tok_last(&tok);
- if (expand_string(tmp, args, eflags) == EXPAND_ERROR)
- {
- err_pos=tok_get_pos(&tok);
- do_loop=0;
- }
- break;
- }
-
- case TOK_END:
- {
- break;
- }
-
- case TOK_ERROR:
- {
- if (show_errors)
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- TOK_ERR_MSG,
- tok_last(&tok));
-
- do_loop=0;
- break;
- }
-
- default:
+ const wcstring arg_src = arg_node->get_source(arg_list_src);
+ if (expand_string(arg_src, output_arg_list, eflags, NULL) == EXPAND_ERROR)
{
- if (show_errors)
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- UNEXPECTED_TOKEN_ERR_MSG,
- tok_get_desc(tok_last_type(&tok)));
-
- do_loop=0;
+ /* Failed to expand a string */
break;
}
}
}
- if (show_errors)
- this->print_errors_stderr();
-
if (this->parser_type == PARSER_TYPE_GENERAL)
+ {
proc_pop_interactive();
-
- return 1;
+ }
}
-void parser_t::stack_trace(block_t *b, wcstring &buff)
+void parser_t::stack_trace(size_t block_idx, wcstring &buff) const
{
/*
Check if we should end the recursion
*/
- if (!b)
+ if (block_idx >= this->block_count())
return;
+ const block_t *b = this->block_at_index(block_idx);
+
if (b->type()==EVENT)
{
/*
@@ -836,7 +573,7 @@ void parser_t::stack_trace(block_t *b, wcstring &buff)
return;
}
- if (b->type() == FUNCTION_CALL || b->type()==SOURCE || b->type()==SUBST)
+ if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW || b->type()==SOURCE || b->type()==SUBST)
{
/*
These types of blocks should be printed
@@ -850,13 +587,14 @@ void parser_t::stack_trace(block_t *b, 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:
+ case FUNCTION_CALL_NO_SHADOW:
{
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:
@@ -874,14 +612,17 @@ void parser_t::stack_trace(block_t *b, 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 if (is_within_fish_initialization)
+ {
+ append_format(buff, _(L"\tcalled during startup\n"));
}
else
{
- append_format(buff,
- _(L"\tcalled on standard input,\n"));
+ append_format(buff, _(L"\tcalled on standard input\n"));
}
if (b->type() == FUNCTION_CALL)
@@ -908,7 +649,7 @@ void parser_t::stack_trace(block_t *b, wcstring &buff)
/*
Recursively print the next block
*/
- parser_t::stack_trace(b->outer, buff);
+ parser_t::stack_trace(block_idx + 1, buff);
}
/**
@@ -921,263 +662,132 @@ const wchar_t *parser_t::is_function() const
{
// PCA: Have to make this a string somehow
ASSERT_IS_MAIN_THREAD();
- wcstring result;
- block_t *b = current_block;
- while (1)
+ const wchar_t *result = NULL;
+ for (size_t block_idx = 0; block_idx < this->block_count(); block_idx++)
{
- if (!b)
+ const block_t *b = this->block_at_index(block_idx);
+ if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW)
{
- return NULL;
+ const function_block_t *fb = static_cast<const function_block_t *>(b);
+ result = fb->name.c_str();
+ break;
}
- if (b->type() == FUNCTION_CALL)
+ else if (b->type() == SOURCE)
{
- const function_block_t *fb = static_cast<const function_block_t *>(b);
- return fb->name.c_str();
+ /* If a function sources a file, obviously that function's offset doesn't contribute */
+ break;
}
- b=b->outer;
}
+ return result;
}
int parser_t::get_lineno() const
{
- int lineno;
-
- if (! current_tokenizer || ! tok_string(current_tokenizer))
- return -1;
+ int lineno = -1;
+ if (! execution_contexts.empty())
+ {
+ lineno = execution_contexts.back()->get_current_line_number();
- lineno = current_tokenizer->line_number_of_character_at_offset(current_tokenizer_pos);
+ /* If we are executing a function, we have to add in its offset */
+ const wchar_t *function_name = is_function();
+ if (function_name != NULL)
+ {
+ lineno += function_get_definition_offset(function_name);
+ }
- const wchar_t *function_name;
- if ((function_name = is_function()))
- {
- lineno += function_get_definition_offset(function_name);
}
-
return lineno;
}
-int parser_t::line_number_of_character_at_offset(size_t idx) const
-{
- if (! current_tokenizer)
- return -1;
-
- int result = current_tokenizer->line_number_of_character_at_offset(idx);
- //assert(result == parse_util_lineno(tok_string( current_tokenizer ), idx));
- return result;
-}
-
const wchar_t *parser_t::current_filename() const
{
- /* We query a global array for the current file name, so it only makes sense to ask this on the principal parser. */
ASSERT_IS_MAIN_THREAD();
- assert(this == &principal_parser());
-
- block_t *b = current_block;
- while (1)
+ for (size_t i=0; i < this->block_count(); i++)
{
- if (!b)
- {
- return reader_current_filename();
- }
- if (b->type() == FUNCTION_CALL)
+ const block_t *b = this->block_at_index(i);
+ if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW)
{
const function_block_t *fb = static_cast<const function_block_t *>(b);
return function_get_definition_file(fb->name);
}
- b=b->outer;
- }
-}
-
-/**
- Calculates the on-screen width of the specified substring of the
- specified string. This function takes into account the width and
- alignment of the tab character, but other wise behaves like
- repeatedly calling wcwidth.
-*/
-static int printed_width(const wchar_t *str, int len)
-{
- int res=0;
- int i;
-
- CHECK(str, 0);
-
- for (i=0; str[i] && i<len; i++)
- {
- if (str[i] == L'\t')
- {
- res=(res+8)&~7;
- }
- else
+ else if (b->type() == SOURCE)
{
- res += fish_wcwidth(str[i]);
+ const source_block_t *sb = static_cast<const source_block_t *>(b);
+ return sb->source_file;
}
}
- return res;
-}
-
-const wchar_t *parser_t::current_line()
-{
- int lineno=1;
-
- const wchar_t *file;
- const wchar_t *whole_str;
- const wchar_t *line;
- const wchar_t *line_end;
- int i;
- int offset;
- int current_line_width;
- const wchar_t *function_name=0;
- int current_line_start=0;
-
- if (!current_tokenizer)
+ /* We query a global array for the current file name, but only do that if we are the principal parser */
+ if (this == &principal_parser())
{
- return L"";
+ return reader_current_filename();
}
+ return NULL;
+}
- file = parser_t::current_filename();
- whole_str = tok_string(current_tokenizer);
- line = whole_str;
-
- if (!line)
- return L"";
-
-
- lineinfo.clear();
-
- /*
- Calculate line number, line offset, etc.
- */
- for (i=0; i<current_tokenizer_pos && whole_str[i]; i++)
+wcstring parser_t::current_line()
+{
+ if (execution_contexts.empty())
{
- if (whole_str[i] == L'\n')
- {
- lineno++;
- current_line_start=i+1;
- line = &whole_str[i+1];
- }
+ return wcstring();
}
+ const parse_execution_context_t *context = execution_contexts.back();
+ assert(context != NULL);
-// lineno = current_tokenizer_pos;
-
-
- current_line_width=printed_width(whole_str+current_line_start,
- current_tokenizer_pos-current_line_start);
-
- if ((function_name = is_function()))
+ int source_offset = context->get_current_source_offset();
+ if (source_offset < 0)
{
- lineno += function_get_definition_offset(function_name);
+ return wcstring();
}
- /*
- Copy current line from whole string
- */
- line_end = wcschr(line, L'\n');
- if (!line_end)
- line_end = line+wcslen(line);
+ const int lineno = this->get_lineno();
+ const wchar_t *file = this->current_filename();
- line = wcsndup(line, line_end-line);
+ wcstring prefix;
- /**
- If we are not going to print a stack trace, at least print the line number and filename
- */
+ /* If we are not going to print a stack trace, at least print the line number and filename */
if (!get_is_interactive() || is_function())
{
- int prev_width = my_wcswidth(lineinfo.c_str());
if (file)
- append_format(lineinfo,
- _(L"%ls (line %d): "),
- file,
- lineno);
- else
- append_format(lineinfo,
- L"%ls: ",
- _(L"Standard input"),
- lineno);
- offset = my_wcswidth(lineinfo.c_str()) - prev_width;
- }
- else
- {
- offset=0;
- }
-
-// debug( 1, L"Current pos %d, line pos %d, file_length %d, is_interactive %d, offset %d\n", current_tokenizer_pos, current_line_pos, wcslen(whole_str), is_interactive, offset);
- /*
- Skip printing character position if we are in interactive mode
- and the error was on the first character of the line.
- */
- if (!get_is_interactive() || is_function() || (current_line_width!=0))
- {
- // Workaround since it seems impossible to print 0 copies of a character using %*lc
- if (offset+current_line_width)
{
- append_format(lineinfo,
- L"%ls\n%*lc^\n",
- line,
- offset+current_line_width,
- L' ');
+ append_format(prefix, _(L"%ls (line %d): "), user_presentable_path(file).c_str(), lineno);
+ }
+ else if (is_within_fish_initialization)
+ {
+ append_format(prefix, L"%ls: ", _(L"Startup"), lineno);
}
else
{
- append_format(lineinfo,
- L"%ls\n^\n",
- line);
+ append_format(prefix, L"%ls: ", _(L"Standard input"), lineno);
}
}
- free((void *)line);
- parser_t::stack_trace(current_block, lineinfo);
-
- return lineinfo.c_str();
-}
-
-int parser_t::get_pos() const
-{
- return tok_get_pos(current_tokenizer);
-}
-
-int parser_t::get_job_pos() const
-{
- return job_start_pos;
-}
+ bool is_interactive = get_is_interactive();
+ bool skip_caret = is_interactive && ! is_function();
+ /* Use an error with empty text */
+ assert(source_offset >= 0);
+ parse_error_t empty_error = {};
+ empty_error.source_start = source_offset;
-void parser_t::set_pos(int p)
-{
- tok_set_pos(current_tokenizer, p);
-}
-
-const wchar_t *parser_t::get_buffer() const
-{
- return tok_string(current_tokenizer);
-}
-
-
-int parser_t::is_help(const wchar_t *s, int min_match) const
-{
- CHECK(s, 0);
-
- size_t len = wcslen(s);
-
- min_match = maxi(min_match, 3);
+ wcstring line_info = empty_error.describe_with_prefix(context->get_source(), prefix, is_interactive, skip_caret);
+ if (! line_info.empty())
+ {
+ line_info.push_back(L'\n');
+ }
- return (wcscmp(L"-h", s) == 0) ||
- (len >= (size_t)min_match && (wcsncmp(L"--help", s, len) == 0));
+ parser_t::stack_trace(0, line_info);
+ return line_info;
}
-job_t *parser_t::job_create()
+void parser_t::job_add(job_t *job)
{
- job_t *res = new job_t(acquire_job_id(), this->block_io);
- this->my_job_list.push_front(res);
-
- job_set_flag(res,
- JOB_CONTROL,
- (job_control_mode==JOB_CONTROL_ALL) ||
- ((job_control_mode == JOB_CONTROL_INTERACTIVE) && (get_is_interactive())));
- return res;
+ assert(job != NULL);
+ assert(job->first_process != NULL);
+ this->my_job_list.push_front(job);
}
bool parser_t::job_remove(job_t *j)
@@ -1198,14 +808,11 @@ bool parser_t::job_remove(job_t *j)
void parser_t::job_promote(job_t *job)
{
- signal_block();
-
job_list_t::iterator loc = std::find(my_job_list.begin(), my_job_list.end(), job);
assert(loc != my_job_list.end());
/* Move the job to the beginning */
my_job_list.splice(my_job_list.begin(), my_job_list, loc);
- signal_unblock();
}
job_t *parser_t::job_get(job_id_t id)
@@ -1232,1381 +839,94 @@ job_t *parser_t::job_get_from_pid(int pid)
return 0;
}
-/**
- Parse options for the specified job
-
- \param p the process to parse options for
- \param j the job to which the process belongs to
- \param tok the tokenizer to read options from
- \param args the argument list to insert options into
- \param args unskip whether we should ignore current_block->skip. Big hack because of our dumb handling of if statements.
-*/
-void parser_t::parse_job_argument_list(process_t *p,
- job_t *j,
- tokenizer_t *tok,
- std::vector<completion_t> &args,
- bool unskip)
+profile_item_t *parser_t::create_profile_item()
{
- int is_finished=0;
-
- int proc_is_count=0;
-
- int matched_wildcard = 0, unmatched_wildcard = 0;
-
- wcstring unmatched;
- int unmatched_pos=0;
-
- /* The set of IO redirections that we construct for the process */
- io_chain_t process_io_chain;
-
- /*
- Test if this is the 'count' command. We need to special case
- count in the shell, since it should display a help message on
- 'count -h', but not on 'set foo -h; count $foo'. This is an ugly
- workaround and a huge hack, but as near as I can tell, the
- alternatives are worse.
- */
- proc_is_count = (args.at(0).completion == L"count");
-
- while (1)
- {
-
- switch (tok_last_type(tok))
- {
- case TOK_PIPE:
- {
- wchar_t *end;
-
- if (p->type == INTERNAL_EXEC)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- EXEC_ERR_MSG);
- return;
- }
-
- errno = 0;
- p->pipe_write_fd = fish_wcstoi(tok_last(tok), &end, 10);
- if (p->pipe_write_fd < 0 || errno || *end)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- ILLEGAL_FD_ERR_MSG,
- tok_last(tok));
- return;
- }
-
- p->set_argv(completions_to_wcstring_list(args));
- p->next = new process_t();
-
- tok_next(tok);
-
- /*
- Don't do anything on failure. parse_job will notice
- the error flag and report any errors for us
- */
- parse_job(p->next, j, tok);
-
- is_finished = 1;
- break;
- }
-
- case TOK_BACKGROUND:
- {
- job_set_flag(j, JOB_FOREGROUND, 0);
- }
-
- case TOK_END:
- {
- if (!p->get_argv())
- p->set_argv(completions_to_wcstring_list(args));
- if (tok_has_next(tok))
- tok_next(tok);
-
- is_finished = 1;
-
- break;
- }
-
- case TOK_STRING:
- {
- int skip=0;
-
- if (job_get_flag(j, JOB_SKIP))
- {
- skip = 1;
- }
- else if (current_block->skip && ! unskip)
- {
- /*
- If this command should be skipped, we do not expand the arguments
- */
- skip=1;
-
- /* But if this is in fact a case statement or an elseif statement, then it should be evaluated */
- block_type_t type = current_block->type();
- if (type == SWITCH && args.at(0).completion == L"case" && p->type == INTERNAL_BUILTIN)
- {
- skip=0;
- }
- else if (job_get_flag(j, JOB_ELSEIF) && ! job_should_skip_elseif(j, current_block))
- {
- skip=0;
- }
- }
- else
- {
- /* If this is an else if, and we should skip it, then don't expand any arguments */
- if (job_get_flag(j, JOB_ELSEIF) && job_should_skip_elseif(j, current_block))
- {
- skip = 1;
- }
- }
-
- if (!skip)
- {
- if ((proc_is_count) &&
- (args.size() == 1) &&
- (parser_t::is_help(tok_last(tok), 0)) &&
- (p->type == INTERNAL_BUILTIN))
- {
- /*
- Display help for count
- */
- p->count_help_magic = 1;
- }
-
- switch (expand_string(tok_last(tok), args, 0))
- {
- case EXPAND_ERROR:
- {
- err_pos=tok_get_pos(tok);
- if (error_code == 0)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- _(L"Could not expand string '%ls'"),
- tok_last(tok));
-
- }
- break;
- }
-
- case EXPAND_WILDCARD_NO_MATCH:
- {
- unmatched_wildcard = 1;
- if (unmatched.empty())
- {
- unmatched = tok_last(tok);
- unmatched_pos = tok_get_pos(tok);
- }
-
- break;
- }
-
- case EXPAND_WILDCARD_MATCH:
- {
- matched_wildcard = 1;
- break;
- }
-
- case EXPAND_OK:
- {
- break;
- }
-
- }
-
- }
-
- break;
- }
-
- case TOK_REDIRECT_OUT:
- case TOK_REDIRECT_IN:
- case TOK_REDIRECT_APPEND:
- case TOK_REDIRECT_FD:
- case TOK_REDIRECT_NOCLOB:
- {
- int type = tok_last_type(tok);
- shared_ptr<io_data_t> new_io;
- wcstring target;
- bool has_target = false;
- wchar_t *end;
-
- /*
- Don't check redirections in skipped part
-
- Otherwise, bogus errors may be the result. (Do check
- that token is string, though)
- */
- if (current_block->skip && ! unskip)
- {
- tok_next(tok);
- if (tok_last_type(tok) != TOK_STRING)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- REDIRECT_TOKEN_ERR_MSG,
- tok_get_desc(tok_last_type(tok)));
- }
-
- break;
- }
-
-
- errno = 0;
- int fd = fish_wcstoi(tok_last(tok),
- &end,
- 10);
- if (fd < 0 || errno || *end)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- ILLEGAL_FD_ERR_MSG,
- tok_last(tok));
- }
- else
- {
-
- tok_next(tok);
-
- switch (tok_last_type(tok))
- {
- case TOK_STRING:
- {
- target = tok_last(tok);
- has_target = expand_one(target, no_exec ? EXPAND_SKIP_VARIABLES : 0);
-
- if (! has_target && error_code == 0)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- REDIRECT_TOKEN_ERR_MSG,
- tok_last(tok));
-
- }
- break;
- }
-
- default:
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- REDIRECT_TOKEN_ERR_MSG,
- tok_get_desc(tok_last_type(tok)));
- }
-
- if (! has_target || target.empty())
- {
- if (error_code == 0)
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- _(L"Invalid IO redirection"));
- tok_next(tok);
- }
- else if (type == TOK_REDIRECT_FD)
- {
- if (target == L"-")
- {
- new_io.reset(new io_close_t(fd));
- }
- else
- {
- wchar_t *end;
-
- errno = 0;
-
- int old_fd = fish_wcstoi(target.c_str(), &end, 10);
-
- if (old_fd < 0 || errno || *end)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- _(L"Requested redirection to something that is not a file descriptor %ls"),
- target.c_str());
-
- tok_next(tok);
- }
- else
- {
- new_io.reset(new io_fd_t(fd, old_fd));
- }
- }
- }
- else
- {
- int flags = 0;
- switch (type)
- {
- case TOK_REDIRECT_APPEND:
- flags = O_CREAT | O_APPEND | O_WRONLY;
- break;
-
- case TOK_REDIRECT_OUT:
- flags = O_CREAT | O_WRONLY | O_TRUNC;
- break;
-
- case TOK_REDIRECT_NOCLOB:
- flags = O_CREAT | O_EXCL | O_WRONLY;
- break;
-
- case TOK_REDIRECT_IN:
- flags = O_RDONLY;
- break;
-
- }
- io_file_t *new_io_file = new io_file_t(fd, target, flags);
- new_io.reset(new_io_file);
- }
- }
-
- if (new_io.get() != NULL)
- {
- process_io_chain.push_back(new_io);
- }
-
- }
- break;
-
- case TOK_ERROR:
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- TOK_ERR_MSG,
- tok_last(tok));
-
- return;
- }
-
- default:
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- UNEXPECTED_TOKEN_ERR_MSG,
- tok_get_desc(tok_last_type(tok)));
-
- tok_next(tok);
- break;
- }
-
- if ((is_finished) || (error_code != 0))
- break;
-
- tok_next(tok);
- }
-
- if (!error_code)
+ profile_item_t *result = NULL;
+ if (g_profiling_active)
{
- if (unmatched_wildcard && !matched_wildcard)
- {
- job_set_flag(j, JOB_WILDCARD_ERROR, 1);
- proc_set_last_status(STATUS_UNMATCHED_WILDCARD);
- if (get_is_interactive() && !is_block)
- {
- int tmp;
-
- debug(1, WILDCARD_ERR_MSG, unmatched.c_str());
- tmp = current_tokenizer_pos;
- current_tokenizer_pos = unmatched_pos;
-
- fwprintf(stderr, L"%ls", parser_t::current_line());
-
- current_tokenizer_pos=tmp;
- }
-
- }
+ result = new profile_item_t();
+ profile_items.push_back(result);
}
-
- /* Store our IO chain. The existing chain should be empty. */
- assert(p->io_chain().empty());
- p->set_io_chain(process_io_chain);
+ return result;
}
-/*
- static void print_block_stack( block_t *b )
- {
- if( !b )
- return;
- print_block_stack( b->outer );
- debug( 0, L"Block type %ls, skip: %d", parser_get_block_desc( b->type ), b->skip );
- }
-*/
-
-/**
- Fully parse a single job. Does not call exec on it, but any command substitutions in the job will be executed.
-
- \param p The process structure that should be used to represent the first process in the job.
- \param j The job structure to contain the parsed job
- \param tok tokenizer to read from
-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::eval(const wcstring &cmd, const io_chain_t &io, enum block_type_t block_type)
{
- 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
- int use_builtin = 1; // May builtins be considered when checking what action this command represents
- int use_command = 1; // May commands be considered when checking what action this command represents
- int is_new_block=0; // Does this command create a new block?
- bool unskip = false; // Maybe we are an elseif inside an if block; if so we may want to evaluate this even if the if block is currently set to skip
- bool allow_bogus_command = false; // If we are an elseif that will not be executed, or an AND or OR that will have been short circuited, don't complain about non-existent commands
-
- block_t *prev_block = current_block;
- scoped_push<int> tokenizer_pos_push(&current_tokenizer_pos, tok_get_pos(tok));
-
- while (args.empty())
- {
- wcstring nxt;
- bool has_nxt = false;
- bool consumed = false; // Set to one if the command requires a second command, like e.g. while does
- int mark; // Use to save the position of the beginning of the token
-
- switch (tok_last_type(tok))
- {
- case TOK_STRING:
- {
- nxt = tok_last(tok);
- has_nxt = expand_one(nxt, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES);
-
- if (! has_nxt)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- ILLEGAL_CMD_ERR_MSG,
- tok_last(tok));
-
- return 0;
- }
- break;
- }
-
- case TOK_ERROR:
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- TOK_ERR_MSG,
- tok_last(tok));
-
- return 0;
- }
-
- case TOK_PIPE:
- {
- const wchar_t *str = tok_string(tok);
- if (tok_get_pos(tok)>0 && str[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)));
- }
-
- return 0;
- }
-
- default:
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- CMD_ERR_MSG,
- tok_get_desc(tok_last_type(tok)));
-
- return 0;
- }
- }
-
- mark = tok_get_pos(tok);
-
- if (contains(nxt,
- L"command",
- L"builtin",
- L"not",
- L"and",
- L"or",
- L"exec"))
- {
- int sw;
- int is_exec = nxt == L"exec";
-
- if (is_exec && (p != j->first_process))
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- EXEC_ERR_MSG);
- return 0;
- }
-
- tok_next(tok);
- sw = parser_keywords_is_switch(tok_last(tok));
-
- if (sw == ARG_SWITCH)
- {
- tok_set_pos(tok, mark);
- }
- else
- {
- if (sw == ARG_SKIP)
- {
- tok_next(tok);
- }
-
- consumed = true;
-
- if (nxt == L"command" || nxt == L"builtin")
- {
- use_function = 0;
- if (nxt == L"command")
- {
- use_builtin = 0;
- use_command = 1;
- }
- else
- {
- use_builtin = 1;
- use_command = 0;
- }
- }
- else if (nxt == L"not")
- {
- job_set_flag(j, JOB_NEGATE, !job_get_flag(j, JOB_NEGATE));
- }
- else if (nxt == L"and")
- {
- bool skip = (proc_get_last_status() != 0);
- job_set_flag(j, JOB_SKIP, skip);
- allow_bogus_command = skip;
- }
- else if (nxt == L"or")
- {
- bool skip = (proc_get_last_status() == 0);
- job_set_flag(j, JOB_SKIP, skip);
- allow_bogus_command = skip;
- }
- else if (is_exec)
- {
- use_function = 0;
- use_builtin=0;
- p->type=INTERNAL_EXEC;
- tokenizer_pos_push.restore();
- }
- }
- }
- else if (nxt == L"while")
- {
- bool new_block = false;
- tok_next(tok);
- while_block_t *wb = NULL;
-
- if ((current_block->type() != WHILE))
- {
- new_block = true;
- }
- else if ((wb = static_cast<while_block_t*>(current_block))->status == WHILE_TEST_AGAIN)
- {
- wb->status = WHILE_TEST_FIRST;
- }
- else
- {
- new_block = true;
- }
-
- if (new_block)
- {
- while_block_t *wb = new while_block_t();
- wb->status = WHILE_TEST_FIRST;
- wb->tok_pos = mark;
- this->push_block(wb);
- }
-
- consumed = true;
- is_new_block=1;
-
- }
- else if (nxt == L"if")
- {
- tok_next(tok);
-
- if_block_t *ib = new if_block_t();
- this->push_block(ib);
- ib->tok_pos = mark;
-
- is_new_block=1;
- consumed = true;
- }
- else if (nxt == L"else")
- {
- /* Record where the else is for error reporting */
- const int else_pos = tok_get_pos(tok);
- /* See if we have any more arguments, that is, whether we're ELSE IF ... or just ELSE. */
- tok_next(tok);
- if (tok_last_type(tok) == TOK_STRING && current_block->type() == IF)
- {
- const if_block_t *ib = static_cast<const if_block_t *>(current_block);
-
- /* If we've already encountered an else, complain */
- if (ib->else_evaluated)
- {
- error(SYNTAX_ERROR,
- else_pos,
- INVALID_ELSEIF_PAST_ELSE_ERR_MSG,
- L"else if");
-
- }
- else
- {
-
- job_set_flag(j, JOB_ELSEIF, 1);
- consumed = true;
-
- /* We're at the IF. Go past it. */
- tok_next(tok);
-
- /* We want to execute this ELSEIF if the IF expression was evaluated, it failed, and so has every other ELSEIF (if any) */
- unskip = (ib->if_expr_evaluated && ! ib->any_branch_taken);
-
- /* But if we're not executing it, don't complain about its command if it doesn't exist */
- if (! unskip)
- allow_bogus_command = true;
- }
- }
- }
-
- /*
- Test if we need another command
- */
- if (consumed)
- {
- /*
- Yes we do, around in the loop for another lap, then!
- */
- continue;
- }
-
- if (use_function && (unskip || ! current_block->skip))
- {
- bool nxt_forbidden=false;
- wcstring forbid;
-
- int is_function_call=0;
-
- /*
- This is a bit fragile. It is a test to see if we are
- inside of function call, but not inside a block in that
- function call. If, in the future, the rules for what
- block scopes are pushed on function invocation changes,
- then this check will break.
- */
- if ((current_block->type() == TOP) &&
- (current_block->outer) &&
- (current_block->outer->type() == FUNCTION_CALL))
- is_function_call = 1;
-
- /*
- If we are directly in a function, and this is the first
- command of the block, then the function we are executing
- may not be called, since that would mean an infinite
- recursion.
- */
- if (is_function_call && !current_block->had_command)
- {
- forbid = forbidden_function.empty() ? wcstring(L"") : forbidden_function.back();
- if (forbid == nxt)
- {
- /* Infinite recursive loop */
- nxt_forbidden = true;
- error(SYNTAX_ERROR, tok_get_pos(tok), INFINITE_RECURSION_ERR_MSG);
- }
- }
-
- if (!nxt_forbidden && has_nxt && function_exists(nxt))
- {
- /*
- Check if we have reached the maximum recursion depth
- */
- if (forbidden_function.size() > MAX_RECURSION_DEPTH)
- {
- error(SYNTAX_ERROR, tok_get_pos(tok), OVERFLOW_RECURSION_ERR_MSG);
- }
- else
- {
- p->type = INTERNAL_FUNCTION;
- }
- }
- }
- args.push_back(completion_t(nxt));
- }
-
- if (error_code == 0)
- {
- if (!p->type)
- {
- if (use_builtin &&
- builtin_exists(args.at(0).completion))
- {
- p->type = INTERNAL_BUILTIN;
- is_new_block |= parser_keywords_is_block(args.at(0).completion);
- }
- }
-
- if ((!p->type || (p->type == INTERNAL_EXEC)))
- {
- /*
- If we are not executing the current block, allow
- non-existent commands.
- */
- if (current_block->skip && ! unskip)
- allow_bogus_command = true; //note this may already be true for other reasons
-
- if (allow_bogus_command)
- {
- p->actual_cmd.clear();
- }
- else
- {
- int err;
- bool has_command = path_get_path(args.at(0).completion, &p->actual_cmd);
- err = errno;
-
- bool use_implicit_cd = false;
- if (! has_command)
- {
- /* If the specified command does not exist, try using an implicit cd. */
- wcstring implicit_cd_path;
- use_implicit_cd = path_can_be_implicit_cd(args.at(0).completion, &implicit_cd_path);
- if (use_implicit_cd)
- {
- args.clear();
- args.push_back(completion_t(L"cd"));
- args.push_back(completion_t(implicit_cd_path));
-
- /* If we have defined a wrapper around cd, use it, otherwise use the cd builtin */
- if (use_function && function_exists(L"cd"))
- p->type = INTERNAL_FUNCTION;
- else
- p->type = INTERNAL_BUILTIN;
- }
- }
-
- // Disabled pending discussion in https://github.com/fish-shell/fish-shell/issues/367
-#if 0
- if (! has_command && ! use_implicit_cd)
- {
- if (fish_openSUSE_dbus_hack_hack_hack_hack(&args))
- {
- has_command = true;
- p->type = INTERNAL_BUILTIN;
- }
- }
-#endif
-
- /* Check if the specified command exists */
- if (! has_command && ! use_implicit_cd)
- {
-
- const wchar_t *cmd = args.at(0).completion.c_str();
-
- /*
- We couldn't find the specified command.
-
- What we want to happen now is that the
- specified job won't get executed, and an
- error message is printed on-screen, but
- otherwise, the parsing/execution of the
- file continues. Because of this, we don't
- want to call error(), since that would stop
- execution of the file. Instead we let
- p->actual_command be 0 (null), which will
- cause the job to silently not execute. We
- also print an error message and set the
- status to 127 (This is the standard number
- for this, used by other shells like bash
- and zsh).
- */
-
- const wchar_t * const equals_ptr = wcschr(cmd, L'=');
- if (equals_ptr != NULL)
- {
- /* Try to figure out if this is a pure variable assignment (foo=bar), or if this appears to be running a command (foo=bar ruby...) */
-
- const wcstring name_str = wcstring(cmd, equals_ptr - cmd); //variable name, up to the =
- const wcstring val_str = wcstring(equals_ptr + 1); //variable value, past the =
-
- wcstring next_str;
- if (tok_peek_next(tok, &next_str) == TOK_STRING && ! next_str.empty())
- {
- wcstring ellipsis_str = wcstring(1, ellipsis_char);
- if (ellipsis_str == L"$")
- ellipsis_str = L"...";
-
- /* Looks like a command */
- debug(0,
- _( L"Unknown command '%ls'. Did you mean to run %ls with a modified environment? Try 'env %ls=%ls %ls%ls'. See the help section on the set command by typing 'help set'."),
- cmd,
- next_str.c_str(),
- name_str.c_str(),
- val_str.c_str(),
- next_str.c_str(),
- ellipsis_str.c_str());
- }
- else
- {
- debug(0,
- COMMAND_ASSIGN_ERR_MSG,
- cmd,
- name_str.c_str(),
- val_str.c_str());
- }
- }
- else if (cmd[0]==L'$' || cmd[0] == VARIABLE_EXPAND || cmd[0] == VARIABLE_EXPAND_SINGLE)
- {
-
- const env_var_t val_wstr = env_get_string(cmd+1);
- const wchar_t *val = val_wstr.missing() ? NULL : val_wstr.c_str();
- if (val)
- {
- debug(0,
- _(L"Variables may not be used as commands. Instead, define a function like 'function %ls; %ls $argv; end' or use the eval builtin instead, like 'eval %ls'. See the help section for the function command by typing 'help function'."),
- cmd+1,
- val,
- cmd,
- cmd);
- }
- else
- {
- debug(0,
- _(L"Variables may not be used as commands. Instead, define a function or use the eval builtin instead, like 'eval %ls'. See the help section for the function command by typing 'help function'."),
- cmd,
- cmd);
- }
- }
- else if (wcschr(cmd, L'$'))
- {
- debug(0,
- _(L"Commands may not contain variables. Use the eval builtin instead, like 'eval %ls'. See the help section for the eval command by typing 'help eval'."),
- cmd,
- cmd);
- }
- else if (err!=ENOENT)
- {
- debug(0,
- _(L"The file '%ls' is not executable by this user"),
- cmd?cmd:L"UNKNOWN");
- }
- else
- {
- /*
- Handle unrecognized commands with standard
- command not found handler that can make better
- error messages
- */
-
- wcstring_list_t event_args;
- event_args.push_back(args.at(0).completion);
- event_fire_generic(L"fish_command_not_found", &event_args);
- }
-
- int tmp = current_tokenizer_pos;
- current_tokenizer_pos = tok_get_pos(tok);
-
- fwprintf(stderr, L"%ls", parser_t::current_line());
-
- current_tokenizer_pos=tmp;
-
- job_set_flag(j, JOB_SKIP, 1);
-
- proc_set_last_status(err==ENOENT?STATUS_UNKNOWN_COMMAND:STATUS_NOT_EXECUTABLE);
- }
- }
- }
-
- if ((p->type == EXTERNAL) && !use_command)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- UNKNOWN_BUILTIN_ERR_MSG,
- args.back().completion.c_str());
- }
- }
-
+ CHECK_BLOCK(1);
- if (is_new_block)
+ if (block_type != TOP && block_type != SUBST)
{
-
- const wchar_t *end=parser_find_end(tok_string(tok) +
- current_tokenizer_pos);
- int make_sub_block = j->first_process != p;
-
- if (!end)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- BLOCK_END_ERR_MSG);
-
- }
- else
- {
-
- if (!make_sub_block)
- {
- int done=0;
-
- tokenizer_t subtok(end, 0);
- for (; ! done && tok_has_next(&subtok); tok_next(&subtok))
- {
-
- switch (tok_last_type(&subtok))
- {
- case TOK_END:
- done = 1;
- break;
-
- case TOK_REDIRECT_OUT:
- case TOK_REDIRECT_NOCLOB:
- case TOK_REDIRECT_APPEND:
- case TOK_REDIRECT_IN:
- case TOK_REDIRECT_FD:
- case TOK_PIPE:
- {
- done = 1;
- make_sub_block = 1;
- break;
- }
-
- case TOK_STRING:
- {
- break;
- }
-
- default:
- {
- done = 1;
- error(SYNTAX_ERROR,
- current_tokenizer_pos,
- BLOCK_END_ERR_MSG);
- }
- }
- }
- }
-
- if (make_sub_block)
- {
-
- long end_pos = end-tok_string(tok);
- const wcstring sub_block(tok_string(tok) + current_tokenizer_pos, end_pos - current_tokenizer_pos);
-
- p->type = INTERNAL_BLOCK;
- args.at(0) = completion_t(sub_block);
-
- tok_set_pos(tok, (int)end_pos);
-
- while (prev_block != current_block)
- {
- parser_t::pop_block();
- }
-
- }
- else tok_next(tok);
- }
-
+ debug(1, INVALID_SCOPE_ERR_MSG, parser_t::get_block_desc(block_type));
+ bugreport();
+ return 1;
}
- else tok_next(tok);
- if (!error_code)
+ /* Parse the source into a tree, if we can */
+ parse_node_tree_t tree;
+ parse_error_list_t error_list;
+ if (! parse_tree_from_string(cmd, parse_flag_none, &tree, this->show_errors ? &error_list : NULL))
{
- if (p->type == INTERNAL_BUILTIN && parser_keywords_skip_arguments(args.at(0).completion))
+ if (this->show_errors)
{
- if (!p->get_argv())
- p->set_argv(completions_to_wcstring_list(args));
- }
- else
- {
- parse_job_argument_list(p, j, tok, args, unskip);
- }
- }
+ /* Get a backtrace */
+ wcstring backtrace_and_desc;
+ this->get_backtrace(cmd, error_list, &backtrace_and_desc);
- if (!error_code)
- {
- if (!is_new_block)
- {
- current_block->had_command = true;
+ /* Print it */
+ fprintf(stderr, "%ls", backtrace_and_desc.c_str());
}
- }
- if (error_code)
- {
- /*
- Make sure the block stack is consistent
- */
- while (prev_block != current_block)
- {
- parser_t::pop_block();
- }
+ return 1;
}
- return !error_code;
-}
-/**
- Do skipped execution of command. This means that only limited
- execution of block level commands such as end and switch should be
- preformed.
+ //print_stderr(block_stack_description());
- \param j the job to execute
-*/
-void parser_t::skipped_exec(job_t * j)
-{
- process_t *p;
+ /* Determine the initial eval level. If this is the first context, it's -1; otherwise it's the eval level of the top context. This is sort of wonky because we're stitching together a global notion of eval level from these separate objects. A better approach would be some profile object that all contexts share, and that tracks the eval levels on its own. */
+ int exec_eval_level = (execution_contexts.empty() ? -1 : execution_contexts.back()->current_eval_level());
- /* Handle other skipped guys */
- for (p = j->first_process; p; p=p->next)
- {
- if (p->type == INTERNAL_BUILTIN)
- {
- if ((wcscmp(p->argv0(), L"for")==0) ||
- (wcscmp(p->argv0(), L"switch")==0) ||
- (wcscmp(p->argv0(), L"begin")==0) ||
- (wcscmp(p->argv0(), L"function")==0))
- {
- this->push_block(new fake_block_t());
- }
- else if (wcscmp(p->argv0(), L"end")==0)
- {
- if (!current_block->outer->skip)
- {
- exec_job(*this, j);
- return;
- }
- parser_t::pop_block();
- }
- else if (wcscmp(p->argv0(), L"else")==0)
- {
- if (current_block->type() == IF)
- {
- /* Evaluate this ELSE if the IF expression failed, and so has every ELSEIF (if any) expression thus far */
- const if_block_t *ib = static_cast<const if_block_t*>(current_block);
- if (ib->if_expr_evaluated && ! ib->any_branch_taken)
- {
- exec_job(*this, j);
- return;
- }
- }
- }
- else if (wcscmp(p->argv0(), L"case")==0)
- {
- if (current_block->type() == SWITCH)
- {
- exec_job(*this, j);
- return;
- }
- }
- }
- }
- job_free(j);
-}
+ /* Append to the execution context stack */
+ parse_execution_context_t *ctx = new parse_execution_context_t(tree, cmd, this, exec_eval_level);
+ execution_contexts.push_back(ctx);
-/* Return whether we should skip the current block, if it is an elseif. */
-static bool job_should_skip_elseif(const job_t *job, const block_t *current_block)
-{
- if (current_block->type() != IF)
+ /* Execute the first node */
+ if (! tree.empty())
{
- /* Not an IF block, so just honor the skip property */
- return current_block->skip;
+ this->eval_block_node(0, io, block_type);
}
- else
- {
- /* We are an IF block */
- const if_block_t *ib = static_cast<const if_block_t *>(current_block);
- /* Execute this ELSEIF if the IF expression has been evaluated, it evaluated to false, and all ELSEIFs so far have evaluated to false. */
- bool execute_elseif = (ib->if_expr_evaluated && ! ib->any_branch_taken);
+ /* Clean up the execution context stack */
+ assert(! execution_contexts.empty() && execution_contexts.back() == ctx);
+ execution_contexts.pop_back();
+ delete ctx;
- /* Invert the sense */
- return ! execute_elseif;
- }
+ return 0;
}
-/**
- Evaluates a job from the specified tokenizer. First calls
- parse_job to parse the job and then calls exec to execute it.
-
- \param tok The tokenizer to read tokens from
-*/
-
-void parser_t::eval_job(tokenizer_t *tok)
+int parser_t::eval_block_node(node_offset_t node_idx, const io_chain_t &io, enum block_type_t block_type)
{
- ASSERT_IS_MAIN_THREAD();
-
- int start_pos = job_start_pos = tok_get_pos(tok);
- long long t1=0, t2=0, t3=0;
-
-
- profile_item_t *profile_item = NULL;
- bool skip = false;
- int job_begin_pos;
- const bool do_profile = profile;
+ /* 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 two trees were to be interleaved? Fortunately that cannot happen (yet); in the future we probably want some sort of reference counted trees.
+ */
+ parse_execution_context_t *ctx = execution_contexts.back();
+ assert(ctx != NULL);
- if (do_profile)
- {
- profile_item = new profile_item_t();
- profile_item->skipped = 1;
- profile_items.push_back(profile_item);
- t1 = get_time();
- }
+ CHECK_BLOCK(1);
- switch (tok_last_type(tok))
+ /* Handle cancellation requests. If our block stack is currently empty, then we already did successfully cancel (or there was nothing to cancel); clear the flag. If our block stack is not empty, we are still in the process of cancelling; refuse to evaluate anything */
+ if (this->cancellation_requested)
{
- case TOK_STRING:
- {
- 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;
-
- if (get_is_interactive())
- {
- if (tcgetattr(0, &j->tmodes))
- {
- tok_next(tok);
- wperror(L"tcgetattr");
- job_free(j);
- break;
- }
- }
-
- j->first_process = new process_t();
- job_begin_pos = tok_get_pos(tok);
-
- if (parse_job(j->first_process, j, tok) &&
- j->first_process->get_argv())
- {
- if (job_start_pos < tok_get_pos(tok))
- {
- long stop_pos = tok_get_pos(tok);
- const wchar_t *newline = wcschr(tok_string(tok)+start_pos, L'\n');
- if (newline)
- stop_pos = mini<long>(stop_pos, newline - tok_string(tok));
-
- j->set_command(wcstring(tok_string(tok)+start_pos, stop_pos-start_pos));
- }
- else
- j->set_command(L"");
-
- 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;
- }
-
- case TOK_END:
- {
- if (tok_has_next(tok))
- tok_next(tok);
- break;
- }
-
- case TOK_BACKGROUND:
- {
- const wchar_t *str = tok_string(tok);
- if (tok_get_pos(tok)>0 && str[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)));
- }
-
- return;
- }
-
- case TOK_ERROR:
+ if (! block_stack.empty())
{
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- TOK_ERR_MSG,
- tok_last(tok));
-
- return;
+ return 1;
}
-
- default:
+ else
{
- error(SYNTAX_ERROR,
- tok_get_pos(tok),
- CMD_ERR_MSG,
- tok_get_desc(tok_last_type(tok)));
-
- return;
+ this->cancellation_requested = false;
}
}
- job_reap(0);
-
-}
-
-int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type_t block_type)
-{
- const wchar_t * const cmd = cmdStr.c_str();
- size_t forbid_count;
- int code;
- block_t *start_current_block = current_block;
-
- /* 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);
-
- if (block_type == SUBST)
- {
- forbidden_function.clear();
- }
-
- CHECK_BLOCK(1);
-
- forbid_count = forbidden_function.size();
-
- job_reap(0);
-
- debug(4, L"eval: %ls", cmd);
-
- if (!cmd)
- {
- debug(1,
- EVAL_NULL_ERR_MSG);
- bugreport();
- return 1;
- }
-
+ /* Only certain blocks are allowed */
if ((block_type != TOP) &&
(block_type != SUBST))
{
@@ -2617,31 +937,20 @@ int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type
return 1;
}
- eval_level++;
-
- this->push_block(new scope_block_t(block_type));
-
- tokenizer_t local_tokenizer(cmd, 0);
- scoped_push<tokenizer_t *> tokenizer_push(&current_tokenizer, &local_tokenizer);
-
- error_code = 0;
-
- event_fire(NULL);
-
- while (tok_has_next(current_tokenizer) &&
- !error_code &&
- !sanity_check() &&
- !exit_status())
- {
- this->eval_job(current_tokenizer);
- event_fire(NULL);
- }
+ /* Not sure why we reap jobs here */
+ job_reap(0);
- parser_t::pop_block();
+ /* Start it up */
+ const block_t * const start_current_block = current_block();
+ block_t *scope_block = new scope_block_t(block_type);
+ this->push_block(scope_block);
+ int result = ctx->eval_node_at_offset(node_idx, scope_block, io);
- while (start_current_block != current_block)
+ /* Clean up the block stack */
+ this->pop_block();
+ while (start_current_block != current_block())
{
- if (current_block == 0)
+ if (current_block() == NULL)
{
debug(0,
_(L"End of block mismatch. Program terminating."));
@@ -2649,1112 +958,212 @@ int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type
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->pop_block();
}
- this->print_errors_stderr();
-
- tokenizer_push.restore();
-
- while (forbidden_function.size() > forbid_count)
- parser_t::allow_function();
-
- /*
- Restore previous eval state
- */
- eval_level--;
-
- code=error_code;
- error_code=0;
-
+ /* Reap again */
job_reap(0);
- return code;
+ return result;
}
-
-/**
- \return the block type created by the specified builtin, or -1 on error.
-*/
-block_type_t parser_get_block_type(const wcstring &cmd)
+bool parser_t::detect_errors_in_argument_list(const wcstring &arg_list_src, wcstring *out, const wchar_t *prefix)
{
- for (size_t i=0; block_lookup[i].desc; i++)
- {
- if (block_lookup[i].name && cmd == block_lookup[i].name)
- {
- return block_lookup[i].type;
- }
- }
- return (block_type_t)-1;
-}
+ bool errored = false;
+ parse_error_list_t errors;
-/**
- \return the block command that createa the specified block type, or null on error.
-*/
-const wchar_t *parser_get_block_command(int type)
-{
- for (size_t i=0; block_lookup[i].desc; i++)
+ /* Use empty string for the prefix if it's NULL */
+ if (prefix == NULL)
{
- if (block_lookup[i].type == type)
- {
- return block_lookup[i].name;
- }
+ prefix = L"";
}
- return NULL;
-}
-
-/**
- Test if this argument contains any errors. Detected errors include
- syntax errors in command substitutions, improperly escaped
- characters and improper use of the variable expansion operator.
-*/
-int parser_t::parser_test_argument(const wchar_t *arg, wcstring *out, const wchar_t *prefix, int offset)
-{
- wchar_t *unesc;
- wchar_t *pos;
- int err=0;
-
- wchar_t *paran_begin, *paran_end;
- wchar_t *arg_cpy;
- int do_loop = 1;
- CHECK(arg, 1);
-
- arg_cpy = wcsdup(arg);
-
- while (do_loop)
+ /* Parse the string as an argument list */
+ parse_node_tree_t tree;
+ if (! parse_tree_from_string(arg_list_src, parse_flag_none, &tree, &errors, symbol_freestanding_argument_list))
{
- switch (parse_util_locate_cmdsubst(arg_cpy,
- &paran_begin,
- &paran_end,
- false))
- {
- case -1:
- err=1;
- if (out)
- {
- error(SYNTAX_ERROR,
- offset,
- L"Mismatched parenthesis");
- this->print_errors(*out, prefix);
- }
- free(arg_cpy);
- return err;
-
- case 0:
- do_loop = 0;
- break;
-
- case 1:
- {
-
- wchar_t *subst = wcsndup(paran_begin+1, paran_end-paran_begin-1);
- wcstring tmp;
-
- tmp.append(arg_cpy, paran_begin - arg_cpy);
- tmp.push_back(INTERNAL_SEPARATOR);
- tmp.append(paran_end+1);
-
-// debug( 1, L"%ls -> %ls %ls", arg_cpy, subst, tmp.buff );
-
- err |= parser_t::test(subst, 0, out, 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;
- }
- }
+ /* Failed to parse. */
+ errored = true;
}
- unesc = unescape(arg_cpy, 1);
- if (!unesc)
- {
- if (out)
- {
- error(SYNTAX_ERROR,
- offset,
- L"Invalid token '%ls'", arg_cpy);
- print_errors(*out, prefix);
- }
- return 1;
- }
- else
+ if (! errored)
{
- /*
- Check for invalid variable expansions
- */
- for (pos = unesc; *pos; pos++)
+ /* Get the root argument list */
+ assert(! tree.empty());
+ const parse_node_t *arg_list = &tree.at(0);
+ assert(arg_list->type == symbol_freestanding_argument_list);
+
+ /* Extract arguments from it */
+ while (arg_list != NULL && ! errored)
{
- switch (*pos)
+ const parse_node_t *arg_node = tree.next_node_in_node_list(*arg_list, symbol_argument, &arg_list);
+ if (arg_node != NULL)
{
- case VARIABLE_EXPAND:
- case VARIABLE_EXPAND_SINGLE:
+ const wcstring arg_src = arg_node->get_source(arg_list_src);
+ if (parse_util_detect_errors_in_argument(*arg_node, arg_src, &errors))
{
- wchar_t n = *(pos+1);
-
- if (n != VARIABLE_EXPAND &&
- n != VARIABLE_EXPAND_SINGLE &&
- !wcsvarchr(n))
- {
- err=1;
- if (out)
- {
- expand_variable_error(*this, unesc, pos-unesc, offset);
- print_errors(*out, prefix);
- }
- }
-
- break;
+ errored = true;
}
}
}
}
- free(arg_cpy);
-
- free(unesc);
- return err;
-
+ if (! errors.empty() && out != NULL)
+ {
+ out->assign(errors.at(0).describe_with_prefix(arg_list_src, prefix, false /* not interactive */, false /* don't skip caret */));
+ }
+ return errored;
}
-int parser_t::test_args(const wchar_t * buff, wcstring *out, const wchar_t *prefix)
+void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &errors, wcstring *output) const
{
- int do_loop = 1;
- int err = 0;
-
- CHECK(buff, 1);
+ assert(output != NULL);
+ if (! errors.empty())
+ {
+ const parse_error_t &err = errors.at(0);
- tokenizer_t tok(buff, 0);
- scoped_push<tokenizer_t*> tokenizer_push(&current_tokenizer, &tok);
- scoped_push<int> tokenizer_pos_push(&current_tokenizer_pos);
+ const bool is_interactive = get_is_interactive();
- for (; do_loop && tok_has_next(&tok); tok_next(&tok))
- {
- current_tokenizer_pos = tok_get_pos(&tok);
- switch (tok_last_type(&tok))
+ // Determine if we want to try to print a caret to point at the source error
+ // The err.source_start <= src.size() check is due to the nasty way that slices work,
+ // which is by rewriting the source (!)
+ size_t which_line = 0;
+ bool skip_caret = true;
+ if (err.source_start != SOURCE_LOCATION_UNKNOWN && err.source_start <= src.size())
{
+ // Determine which line we're on
+ which_line = 1 + std::count(src.begin(), src.begin() + err.source_start, L'\n');
- case TOK_STRING:
- {
- err |= parser_test_argument(tok_last(&tok), out, prefix, tok_get_pos(&tok));
- break;
- }
+ // 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
+ skip_caret = (is_interactive && which_line == 1 && err.source_start == 0);
+ }
- case TOK_END:
+ wcstring prefix;
+ const wchar_t *filename = this->current_filename();
+ if (filename)
+ {
+ if (which_line > 0)
{
- break;
+ prefix = format_string(_(L"%ls (line %lu): "), user_presentable_path(filename).c_str(), which_line);
}
-
- case TOK_ERROR:
+ else
{
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- TOK_ERR_MSG,
- tok_last(&tok));
- print_errors(*out, prefix);
- }
- err=1;
- do_loop=0;
- break;
+ prefix = format_string(_(L"%ls: "), user_presentable_path(filename).c_str());
}
+ }
+ else
+ {
+ prefix = L"fish: ";
+ }
- default:
- {
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- UNEXPECTED_TOKEN_ERR_MSG,
- tok_get_desc(tok_last_type(&tok)));
- print_errors(*out, prefix);
- }
- err=1;
- do_loop=0;
- break;
- }
+ const wcstring description = err.describe_with_prefix(src, prefix, is_interactive, skip_caret);
+ if (! description.empty())
+ {
+ output->append(description);
+ output->push_back(L'\n');
}
+ this->stack_trace(0, *output);
}
-
- error_code=0;
-
- return err;
}
-// helper type used in parser::test below
-struct block_info_t
+block_t::block_t(block_type_t t) :
+ block_type(t),
+ skip(),
+ tok_pos(),
+ node_offset(NODE_OFFSET_INVALID),
+ loop_status(LOOP_NORMAL),
+ job(),
+ src_filename(),
+ src_lineno(),
+ wants_pop_env(false),
+ event_blocks()
{
- 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)
+block_t::~block_t()
{
- 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))
+wcstring block_t::description() const
+{
+ wcstring result;
+ switch (this->type())
{
- 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 WHILE:
+ result.append(L"while");
+ 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));
+ case FOR:
+ result.append(L"for");
+ break;
+ case IF:
+ result.append(L"if");
+ break;
- print_errors(*out, prefix);
- }
- err = 1;
- }
+ case FUNCTION_DEF:
+ result.append(L"function_def");
+ break;
- break;
- }
+ case FUNCTION_CALL:
+ result.append(L"function_call");
+ 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;
+ case FUNCTION_CALL_NO_SHADOW:
+ result.append(L"function_call_no_shadow");
+ break;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- BUILTIN_FOR_ERR_COUNT,
- L"for",
- arg_count);
+ case SWITCH:
+ result.append(L"switch");
+ break;
- 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);
+ case FAKE:
+ result.append(L"fake");
+ break;
- print_errors(*out, prefix);
+ case SUBST:
+ result.append(L"substitution");
+ break;
- }
- }
- }
+ case TOP:
+ result.append(L"top");
+ break;
- }
+ case BEGIN:
+ result.append(L"begin");
+ break;
- if (!tok_has_next(&tok))
+ case SOURCE:
+ result.append(L"source");
break;
- }
+ case EVENT:
+ result.append(L"event");
+ break;
- if (needs_cmd)
- {
- err=1;
- if (out)
- {
- error(SYNTAX_ERROR,
- tok_get_pos(&tok),
- COND_ERR_MSG);
+ case BREAKPOINT:
+ result.append(L"breakpoint");
+ break;
- print_errors(*out, prefix);
- }
+ default:
+ append_format(result, L"unknown type %ld", (long)this->type());
+ break;
}
-
- if (out != NULL && ! block_infos.empty())
+ if (this->src_lineno >= 0)
{
- 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());
- }
- }
-
-
+ append_format(result, L" (line %d)", this->src_lineno);
}
-
- /*
- 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)
+ if (this->src_filename != NULL)
{
- int last_level = 0;
- size_t i, len = wcslen(buff);
- for (i=0; i<len; i++)
- {
- 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;
- }
-
- /*
- 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--)
- {
- if (!wcschr(L" \n\t\r", buff[suffix_idx]))
- break;
- block_level[suffix_idx] = last_indent;
- }
+ append_format(result, L" (file %ls)", this->src_filename);
}
-
- /*
- 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) :
- block_type(t),
- made_fake(false),
- skip(),
- had_command(),
- tok_pos(),
- loop_status(),
- job(),
- src_filename(),
- src_lineno(),
- wants_pop_env(false),
- event_blocks(),
- outer(NULL)
-{
-}
-
-block_t::~block_t()
-{
+ return result;
}
/* Various block constructors */
-if_block_t::if_block_t() :
- block_t(IF),
- if_expr_evaluated(false),
- is_elseif_entry(false),
- any_branch_taken(false),
- else_evaluated(false)
+if_block_t::if_block_t() : block_t(IF)
{
}
@@ -3777,45 +1186,27 @@ source_block_t::source_block_t(const wchar_t *src) :
{
}
-for_block_t::for_block_t(const wcstring &var) :
- block_t(FOR),
- variable(var),
- sequence()
+for_block_t::for_block_t() : block_t(FOR)
{
}
-while_block_t::while_block_t() :
- block_t(WHILE),
- status(0)
+while_block_t::while_block_t() : block_t(WHILE)
{
}
-switch_block_t::switch_block_t(const wcstring &sv) :
- block_t(SWITCH),
- switch_taken(false),
- switch_value(sv)
+switch_block_t::switch_block_t() : block_t(SWITCH)
{
}
-fake_block_t::fake_block_t() :
- block_t(FAKE)
+fake_block_t::fake_block_t() : block_t(FAKE)
{
}
-function_def_block_t::function_def_block_t() :
- block_t(FUNCTION_DEF),
- function_data()
-{
-}
-
-scope_block_t::scope_block_t(block_type_t type) :
- block_t(type)
+scope_block_t::scope_block_t(block_type_t type) : block_t(type)
{
assert(type == BEGIN || type == TOP || type == SUBST);
}
-breakpoint_block_t::breakpoint_block_t() :
- block_t(BREAKPOINT)
+breakpoint_block_t::breakpoint_block_t() : block_t(BREAKPOINT)
{
}
-