Remove detect_errors2
This commit is contained in:
parent
af21dfd294
commit
384987cd5b
719
parser.cpp
719
parser.cpp
@ -2935,725 +2935,6 @@ void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &erro
|
||||
}
|
||||
}
|
||||
|
||||
parser_test_error_bits_t parser_t::detect_errors2(const wchar_t *buff, wcstring *out, const wchar_t *prefix)
|
||||
{
|
||||
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;
|
||||
|
||||
// This is very nearly a stack, but sometimes we have to inspect non-top elements (e.g. return)
|
||||
std::vector<struct block_info_t> block_infos;
|
||||
|
||||
/*
|
||||
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);
|
||||
|
||||
tokenizer_t tok(buff, 0);
|
||||
|
||||
scoped_push<tokenizer_t*> tokenizer_push(¤t_tokenizer, &tok);
|
||||
scoped_push<int> tokenizer_pos_push(¤t_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
|
||||
{
|
||||
block_infos.pop_back();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Handle block commands
|
||||
*/
|
||||
if (parser_keywords_is_block(command))
|
||||
{
|
||||
struct block_info_t info = {current_tokenizer_pos, parser_get_block_type(command)};
|
||||
block_infos.push_back(info);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate exit status
|
||||
*/
|
||||
if (! block_infos.empty())
|
||||
unfinished = 1;
|
||||
|
||||
parser_test_error_bits_t res = 0;
|
||||
|
||||
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),
|
||||
|
1
parser.h
1
parser.h
@ -482,7 +482,6 @@ public:
|
||||
\param out if non-null, any errors in the command will be filled out into this buffer
|
||||
\param prefix the prefix string to prepend to each error message written to the \c out buffer
|
||||
*/
|
||||
parser_test_error_bits_t detect_errors2(const wchar_t *buff, wcstring *out_error_desc, const wchar_t *prefix);
|
||||
void get_backtrace(const wcstring &src, const parse_error_list_t &errors, wcstring *output) const;
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user